Anomie has uploaded a new change for review. https://gerrit.wikimedia.org/r/305126
Change subject: API: Use U+001F (Unit Separator) for separating multi-valued parameters ...................................................................... API: Use U+001F (Unit Separator) for separating multi-valued parameters When a multi-valued parameter's value begins with U+001F, the values will be split on that character instead of pipes. This will be useful for things such as action=options&change= or meta=allmessages&amargs=. Since MediaWiki doesn't otherwise accept C0 control characters (WebRequest::getVal() replaces them with �), there's no possibility that this will conflict with a literal use of U+001F. Special:ApiSandbox and mw.Api are updated to make use of this, with the latter having an option to disable the behavior in case something is depending on [ 'foo', 'bar|baz' ] turning into 'foo|bar|baz'. Pipe is still used as the separator when the value doesn't begin with U+001F, and will be forever since it's generally more human-friendly and is needed for backwards compatibility with basically every API client in existence. The requirement that the value begin with U+001F, rather than simply contain U+001F, is to avoid clients having to somehow special-case "param=foo|bar" where that's intended to be a single value "foo|bar" rather than two values "foo" and "bar". Bug: T141960 Change-Id: I45f69997667b48887a2b67e93906364a652ace5a --- M RELEASE-NOTES-1.28 M includes/WebRequest.php M includes/api/ApiBase.php M includes/api/i18n/en.json M resources/src/mediawiki.special/mediawiki.special.apisandbox.js M resources/src/mediawiki/api.js M resources/src/mediawiki/api/options.js 7 files changed, 80 insertions(+), 13 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core refs/changes/26/305126/1 diff --git a/RELEASE-NOTES-1.28 b/RELEASE-NOTES-1.28 index 80166ad..c20b6fb 100644 --- a/RELEASE-NOTES-1.28 +++ b/RELEASE-NOTES-1.28 @@ -41,6 +41,9 @@ * (T8948) Numeric sorting in categories is now supported by setting $wgCategoryCollation to uca-default-u-kn or uca-<langcode>-u-kn. If migrating from another collation, you will need to run the updateCollation.php maintenance script. +* mw.Api has a new option, useUS, to use U+001F (Unit Separator) when + appropriate for sending multi-valued parameters. This defaults to true when + the mw.Api instance seems to be for the local wiki. === External library changes in 1.28 === @@ -61,6 +64,9 @@ * The following response properties from action=login, deprecated in 1.27, are now removed: lgtoken, cookieprefix, sessionid. Clients should handle cookies to properly manage session state. +* (T141960) Multi-valued parameters may now be separated using U+001F (Unit Separator) + instead of the pipe character. This will be useful if some of the multiple + values need to contain pipes, e.g. for action=options. === Action API internal changes in 1.28 === * Added a new hook, 'ApiMakeParserOptions', to allow extensions to better diff --git a/includes/WebRequest.php b/includes/WebRequest.php index b5c57ee..6904b6e 100644 --- a/includes/WebRequest.php +++ b/includes/WebRequest.php @@ -395,6 +395,22 @@ } /** + * Fetch a value from the input without normalization, or return $default + * if it's not set. + * + * Unlike self::getVal() and self::getArray(), this does not perform any + * normalization. + * + * @since 1.28 + * @param string $name + * @param mixed $default Optional default + * @return mixed + */ + public function getRawVal( $name, $default = null ) { + return isset( $this->data[$name] ) ? $this->data[$name] : $default; + } + + /** * Fetch a scalar from the input or return $default if it's not set. * Returns a string. Arrays are discarded. Useful for * non-freeform text inputs (e.g. predefined internal text keys diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index b45eacb..7c2cb41 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -967,6 +967,20 @@ $type = $this->getModuleManager()->getNames( $paramName ); } } + + // Preserve U+001F for self::parseMultiValue() + if ( isset( $value ) && ( $multi || is_array( $type ) ) && + substr( $value, 0, 3 ) === "\xef\xbf\xbd" + ) { + $request = $this->getMain()->getRequest(); + $raw = $request->getRawVal( $encParamName, $default ); + if ( substr( $raw, 0, 1 ) === "\x1f" ) { + $value = join( + "\x1f", + array_map( [ $request, 'normalizeUnicode' ], explode( "\x1f", $raw ) ) + ); + } + } } if ( isset( $value ) && ( $multi || is_array( $type ) ) ) { @@ -1126,13 +1140,20 @@ * @return string|string[] (allowMultiple ? an_array_of_values : a_single_value) */ protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues ) { - if ( trim( $value ) === '' && $allowMultiple ) { + if ( ( trim( $value ) === '' || trim( $value ) === "\x1f" ) && $allowMultiple ) { return []; + } + + if ( strpos( $value, "\x1f" ) === 0 ) { + $sep = "\x1f"; + $value = substr( $value, 1 ); + } else { + $sep = '|'; } // This is a bit awkward, but we want to avoid calling canApiHighLimits() // because it unstubs $wgUser - $valuesList = explode( '|', $value, self::LIMIT_SML2 + 1 ); + $valuesList = explode( $sep, $value, self::LIMIT_SML2 + 1 ); $sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits() ? self::LIMIT_SML2 : self::LIMIT_SML1; diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json index 1815836..68147c2 100644 --- a/includes/api/i18n/en.json +++ b/includes/api/i18n/en.json @@ -1481,14 +1481,14 @@ "api-help-param-deprecated": "Deprecated.", "api-help-param-required": "This parameter is required.", "api-help-datatypes-header": "Data types", - "api-help-datatypes": "Some parameter types in API requests need further explanation:\n;boolean\n:Boolean parameters work like HTML checkboxes: if the parameter is specified, regardless of value, it is considered true. For a false value, omit the parameter entirely.\n;timestamp\n:Timestamps may be specified in several formats. ISO 8601 date and time is recommended. All times are in UTC, any included timezone is ignored.\n:* ISO 8601 date and time, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd> (punctuation and <kbd>Z</kbd> are optional)\n:* ISO 8601 date and time with (ignored) fractional seconds, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>.<var>00001</var>Z</kbd> (dashes, colons, and <kbd>Z</kbd> are optional)\n:* MediaWiki format, <kbd><var>2001</var><var>01</var><var>15</var><var>14</var><var>56</var><var>00</var></kbd>\n:* Generic numeric format, <kbd><var>2001</var>-<var>01</var>-<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd> (optional timezone of <kbd>GMT</kbd>, <kbd>+<var>##</var></kbd>, or <kbd>-<var>##</var></kbd> is ignored)\n:* EXIF format, <kbd><var>2001</var>:<var>01</var>:<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:*RFC 2822 format (timezone may be omitted), <kbd><var>Mon</var>, <var>15</var> <var>Jan</var> <var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* RFC 850 format (timezone may be omitted), <kbd><var>Monday</var>, <var>15</var>-<var>Jan</var>-<var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* C ctime format, <kbd><var>Mon</var> <var>Jan</var> <var>15</var> <var>14</var>:<var>56</var>:<var>00</var> <var>2001</var></kbd>\n:* Seconds since 1970-01-01T00:00:00Z as a 1 to 13 digit integer (excluding <kbd>0</kbd>)\n:* The string <kbd>now</kbd>", + "api-help-datatypes": "Some parameter types in API requests need further explanation:\n;boolean\n:Boolean parameters work like HTML checkboxes: if the parameter is specified, regardless of value, it is considered true. For a false value, omit the parameter entirely.\n;timestamp\n:Timestamps may be specified in several formats. ISO 8601 date and time is recommended. All times are in UTC, any included timezone is ignored.\n:* ISO 8601 date and time, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd> (punctuation and <kbd>Z</kbd> are optional)\n:* ISO 8601 date and time with (ignored) fractional seconds, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>.<var>00001</var>Z</kbd> (dashes, colons, and <kbd>Z</kbd> are optional)\n:* MediaWiki format, <kbd><var>2001</var><var>01</var><var>15</var><var>14</var><var>56</var><var>00</var></kbd>\n:* Generic numeric format, <kbd><var>2001</var>-<var>01</var>-<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd> (optional timezone of <kbd>GMT</kbd>, <kbd>+<var>##</var></kbd>, or <kbd>-<var>##</var></kbd> is ignored)\n:* EXIF format, <kbd><var>2001</var>:<var>01</var>:<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:*RFC 2822 format (timezone may be omitted), <kbd><var>Mon</var>, <var>15</var> <var>Jan</var> <var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* RFC 850 format (timezone may be omitted), <kbd><var>Monday</var>, <var>15</var>-<var>Jan</var>-<var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* C ctime format, <kbd><var>Mon</var> <var>Jan</var> <var>15</var> <var>14</var>:<var>56</var>:<var>00</var> <var>2001</var></kbd>\n:* Seconds since 1970-01-01T00:00:00Z as a 1 to 13 digit integer (excluding <kbd>0</kbd>)\n:* The string <kbd>now</kbd>\n;alternative multiple-value separator\n:Parameters that take multiple values are normally submitted with the values separated using the pipe character, e.g. <kbd>param=value1|value2</kbd> or <kbd>param=value1%7Cvalue2</kbd>. If a value must contain the pipe character, use U+001F (Unit Separator) as the separator ''and'' prefix the value with U+001F, e.g. <kbd>param=%1Fvalue1%1Fvalue2</kbd>.", "api-help-param-type-limit": "Type: integer or <kbd>max</kbd>", "api-help-param-type-integer": "Type: {{PLURAL:$1|1=integer|2=list of integers}}", "api-help-param-type-boolean": "Type: boolean ([[Special:ApiHelp/main#main/datatypes|details]])", "api-help-param-type-password": "", "api-help-param-type-timestamp": "Type: {{PLURAL:$1|1=timestamp|2=list of timestamps}} ([[Special:ApiHelp/main#main/datatypes|allowed formats]])", "api-help-param-type-user": "Type: {{PLURAL:$1|1=user name|2=list of user names}}", - "api-help-param-list": "{{PLURAL:$1|1=One of the following values|2=Values (separate with <kbd>{{!}}</kbd>)}}: $2", + "api-help-param-list": "{{PLURAL:$1|1=One of the following values|2=Values (separate with <kbd>{{!}}</kbd> or [[Special:ApiHelp/main#main/datatypes|alternative]])}}: $2", "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Must be empty|Can be empty, or $2}}", "api-help-param-limit": "No more than $1 allowed.", "api-help-param-limit2": "No more than $1 ($2 for bots) allowed.", @@ -1496,7 +1496,7 @@ "api-help-param-integer-max": "The {{PLURAL:$1|1=value|2=values}} must be no greater than $3.", "api-help-param-integer-minmax": "The {{PLURAL:$1|1=value|2=values}} must be between $2 and $3.", "api-help-param-upload": "Must be posted as a file upload using multipart/form-data.", - "api-help-param-multi-separate": "Separate values with <kbd>|</kbd>.", + "api-help-param-multi-separate": "Separate values with <kbd>|</kbd> or [[Special:ApiHelp/main#main/datatypes|alternative]].", "api-help-param-multi-max": "Maximum number of values is {{PLURAL:$1|$1}} ({{PLURAL:$2|$2}} for bots).", "api-help-param-default": "Default: $1", "api-help-param-default-empty": "Default: <span class=\"apihelp-empty\">(empty)</span>", diff --git a/resources/src/mediawiki.special/mediawiki.special.apisandbox.js b/resources/src/mediawiki.special/mediawiki.special.apisandbox.js index 3d90307..b3da8d6 100644 --- a/resources/src/mediawiki.special/mediawiki.special.apisandbox.js +++ b/resources/src/mediawiki.special/mediawiki.special.apisandbox.js @@ -116,10 +116,20 @@ capsuleWidget: { getApiValue: function () { - return this.getItemsData().join( '|' ); + var items = this.getItemsData(); + if ( items.join( '' ).indexOf( '|' ) === -1 ) { + return items.join( '|' ); + } else { + return '\x1f' + items.join( '\x1f' ); + } }, setApiValue: function ( v ) { - this.setItemsFromData( v === undefined || v === '' ? [] : String( v ).split( '|' ) ); + if ( v === undefined || v === '' || v === '\x1f' ) { + this.setItemsFromData( [] ); + } else { + v = String( v ); + this.setItemsFromData( v.split( v.indexOf( '\x1f' ) !== 0 ? '|' : '\x1f' ) ); + } }, apiCheckValid: function () { var ok = this.getApiValue() !== undefined || suppressErrors; diff --git a/resources/src/mediawiki/api.js b/resources/src/mediawiki/api.js index a8ee4c7..b7579ff 100644 --- a/resources/src/mediawiki/api.js +++ b/resources/src/mediawiki/api.js @@ -9,6 +9,9 @@ * `options` to mw.Api constructor. * @property {Object} defaultOptions.parameters Default query parameters for API requests. * @property {Object} defaultOptions.ajax Default options for jQuery#ajax. + * @property {boolean} defaultOptions.useUS Whether to use U+001F when joining multi-valued + * parameters (since 1.28). Default is true if ajax.url is not set, false otherwise for + * compatibility. * @private */ var defaultOptions = { @@ -95,6 +98,8 @@ options.ajax.url = String( options.ajax.url ); } + options = $.extend( { useUS: !options.ajax || !options.ajax.url }, options ); + options.parameters = $.extend( {}, defaultOptions.parameters, options.parameters ); options.ajax = $.extend( {}, defaultOptions.ajax, options.ajax ); @@ -147,14 +152,19 @@ * * @private * @param {Object} parameters (modified in-place) + * @param {boolean} useUS Whether to use U+001F when joining multi-valued parameters. */ - preprocessParameters: function ( parameters ) { + preprocessParameters: function ( parameters, useUS ) { var key; // Handle common MediaWiki API idioms for passing parameters for ( key in parameters ) { // Multiple values are pipe-separated if ( $.isArray( parameters[ key ] ) ) { - parameters[ key ] = parameters[ key ].join( '|' ); + if ( !useUS || parameters[ key ].join( '' ).indexOf( '|' ) === -1 ) { + parameters[ key ] = parameters[ key ].join( '|' ); + } else { + parameters[ key ] = '\x1f' + parameters[ key ].join( '\x1f' ); + } } // Boolean values are only false when not given at all if ( parameters[ key ] === false || parameters[ key ] === undefined ) { @@ -186,7 +196,7 @@ delete parameters.token; } - this.preprocessParameters( parameters ); + this.preprocessParameters( parameters, this.defaults.useUS ); // If multipart/form-data has been requested and emulation is possible, emulate it if ( diff --git a/resources/src/mediawiki/api/options.js b/resources/src/mediawiki/api/options.js index 0af2a75..069fbbf 100644 --- a/resources/src/mediawiki/api/options.js +++ b/resources/src/mediawiki/api/options.js @@ -41,9 +41,13 @@ value = options[ name ] === null ? null : String( options[ name ] ); // Can we bundle this option, or does it need a separate request? - bundleable = - ( value === null || value.indexOf( '|' ) === -1 ) && - ( name.indexOf( '|' ) === -1 && name.indexOf( '=' ) === -1 ); + if ( this.defaults.useUS ) { + bundleable = name.indexOf( '=' ) === -1; + } else { + bundleable = + ( value === null || value.indexOf( '|' ) === -1 ) && + ( name.indexOf( '|' ) === -1 && name.indexOf( '=' ) === -1 ); + } if ( bundleable ) { if ( value !== null ) { -- To view, visit https://gerrit.wikimedia.org/r/305126 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I45f69997667b48887a2b67e93906364a652ace5a Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/core Gerrit-Branch: master Gerrit-Owner: Anomie <bjor...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits