* If the user has the 'deletedhistory' right, action=query's revids parameter
will now recognize deleted revids.
* prop=revisions may be used as a generator, generating revids.
+* (bug 66776) format=json results will no longer be corrupted when
+ $wgMangleFlashPolicy is in effect. format=php results will cleanly return an
+ error instead of returning invalid serialized data.
=== Action API internal changes in 1.25 ===
* ApiHelp has been rewritten to support i18n and paginated HTML output.
* <cross-domain-policy>. Without this, an attacker can send their own
* cross-domain policy unless it is prevented by the crossdomain.xml file at
* the domain root.
+ *
+ * @since 1.25
*/
$wgMangleFlashPolicy = true;
$this->getIsHtml(),
$params['utf8'] ? FormatJson::ALL_OK : FormatJson::XMLMETA_OK
);
+
+ // Bug 66776: wfMangleFlashPolicy() is needed to avoid a nasty bug in
+ // Flash, but what it does isn't friendly for the API, so we need to
+ // work around it.
+ if ( preg_match( '/\<\s*cross-domain-policy\s*\>/i', $json ) ) {
+ $json = preg_replace(
+ '/\<(\s*cross-domain-policy\s*)\>/i', '\\u003C$1\\u003E', $json
+ );
+ }
+
$callback = $params['callback'];
if ( $callback !== null ) {
$callback = preg_replace( "/[^][.\\'\\\"_A-Za-z0-9]/", '', $callback );
}
public function execute() {
- $this->printText( serialize( $this->getResultData() ) );
+ $text = serialize( $this->getResultData() );
+
+ // Bug 66776: wfMangleFlashPolicy() is needed to avoid a nasty bug in
+ // Flash, but what it does isn't friendly for the API. There's nothing
+ // we can do here that isn't actively broken in some manner, so let's
+ // just be broken in a useful manner.
+ if ( $this->getConfig()->get( 'MangleFlashPolicy' ) &&
+ in_array( 'wfOutputHandler', ob_list_handlers(), true ) &&
+ preg_match( '/\<\s*cross-domain-policy\s*\>/i', $text )
+ ) {
+ $this->dieUsage(
+ 'This response cannot be represented using format=php. See https://bugzilla.wikimedia.org/show_bug.cgi?id=66776',
+ 'internalerror'
+ );
+ }
+
+ $this->printText( $text );
}
}
if ( $this->singleLang ) {
$this->resolveMultilangMetadata( $extendedMetadata );
}
+ $this->discardMultipleValues( $extendedMetadata );
// Make sure the metadata won't break the API when an XML format is used.
// This is an API-specific function so it would be cleaner to call it from
// outside fetchExtendedMetadata, but this way we don't need to redo the
return null;
}
+ /**
+ * Turns an XMP-style multivalue array into a single value by dropping all but the first value.
+ * If the value is not a multivalue array (or a multivalue array inside a multilang array), it is returned unchanged.
+ * See mediawiki.org/wiki/Manual:File_metadata_handling#Multi-language_array_format
+ * @param mixed $value
+ * @return mixed The value, or the first value if there were multiple ones
+ * @since 1.25
+ */
+ protected function resolveMultivalueValue( $value ) {
+ if ( !is_array( $value ) ) {
+ return $value;
+ } elseif ( isset( $value['_type'] ) && $value['_type'] === 'lang' ) { // if this is a multilang array, process fields separately
+ $newValue = array();
+ foreach ( $value as $k => $v ) {
+ $newValue[$k] = $this->resolveMultivalueValue( $v );
+ }
+ return $newValue;
+ } else { // _type is 'ul' or 'ol' or missing in which case it defaults to 'ul'
+ list( $k, $v ) = each( $value );
+ if ( $k === '_type' ) {
+ $v = current( $value );
+ }
+ return $v;
+ }
+ }
+
/**
* Takes an array returned by the getExtendedMetadata* functions,
* and resolves multi-language values in it.
}
}
+ /**
+ * Takes an array returned by the getExtendedMetadata* functions,
+ * and turns all fields into single-valued ones by dropping extra values.
+ * @param array $metadata
+ * @since 1.25
+ */
+ protected function discardMultipleValues( &$metadata ) {
+ if ( !is_array( $metadata ) ) {
+ return;
+ }
+ foreach ( $metadata as $key => &$field ) {
+ if ( $key === 'Software' || $key === 'Contact' ) {
+ // we skip some fields which have composite values. They are not particularly interesting
+ // and you can get them via the metadata / commonmetadata APIs anyway.
+ continue;
+ }
+ if ( isset( $field['value'] ) ) {
+ $field['value'] = $this->resolveMultivalueValue( $field['value'] );
+ }
+ }
+
+ }
+
/**
* Makes sure the given array is a valid API response fragment
* (can be transformed into XML)
// TODO: more test cases
);
}
+
+ /**
+ * @param mixed $input
+ * @param mixed $output
+ * @dataProvider provideResolveMultivalueValue
+ * @covers FormatMetadata::resolveMultivalueValue
+ */
+ public function testResolveMultivalueValue( $input, $output ) {
+ $formatMetadata = new FormatMetadata();
+ $class = new ReflectionClass( 'FormatMetadata' );
+ $method = $class->getMethod( 'resolveMultivalueValue' );
+ $method->setAccessible( true );
+ $actualInput = $method->invoke( $formatMetadata, $input );
+ $this->assertEquals( $output, $actualInput );
+ }
+
+ public function provideResolveMultivalueValue() {
+ return array(
+ 'nonArray' => array( 'foo', 'foo' ),
+ 'multiValue' => array( array( 'first', 'second', 'third', '_type' => 'ol' ), 'first' ),
+ 'noType' => array( array( 'first', 'second', 'third' ), 'first' ),
+ 'typeFirst' => array( array( '_type' => 'ol', 'first', 'second', 'third' ), 'first' ),
+ 'multilang' => array(
+ array( 'en' => 'first', 'de' => 'Erste', '_type' => 'lang' ),
+ array( 'en' => 'first', 'de' => 'Erste', '_type' => 'lang' ),
+ ),
+ 'multilang-multivalue' => array(
+ array( 'en' => array( 'first', 'second' ), 'de' => array( 'Erste', 'Zweite' ), '_type' => 'lang' ),
+ array( 'en' => 'first', 'de' => 'Erste', '_type' => 'lang' ),
+ ),
+ );
+ }
}