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

Reply via email to