jenkins-bot has submitted this change and it was merged.

Change subject: Replace pipes in input before feeding them into a template
......................................................................


Replace pipes in input before feeding them into a template

Bug: T140901
Change-Id: I0c01db0a3d79f060173d88f10ca80c9fbc44a2fb
---
M extension.json
M resources/details/uw.DescriptionDetailsWidget.js
A resources/mw.Escaper.js
M resources/mw.UploadWizardDetails.js
4 files changed, 157 insertions(+), 3 deletions(-)

Approvals:
  MarkTraceur: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/extension.json b/extension.json
index d6e5c60..ad51989 100644
--- a/extension.json
+++ b/extension.json
@@ -172,6 +172,7 @@
                                "mediawiki.api.messages",
                                "mediawiki.api.parse",
                                "mediawiki.confirmCloseWindow",
+                               "mediawiki.RegExp",
                                "mediawiki.Title",
                                "mediawiki.user",
                                "mediawiki.feedback",
@@ -213,6 +214,7 @@
                                "resources/mw.units.js",
                                "resources/mw.canvas.js",
                                "resources/mw.errorDialog.js",
+                               "resources/mw.Escaper.js",
                                "resources/mw.DestinationChecker.js",
                                "resources/mw.QuickTitleChecker.js",
                                "resources/mw.Firefogg.js",
diff --git a/resources/details/uw.DescriptionDetailsWidget.js 
b/resources/details/uw.DescriptionDetailsWidget.js
index c105da6..63e7680 100644
--- a/resources/details/uw.DescriptionDetailsWidget.js
+++ b/resources/details/uw.DescriptionDetailsWidget.js
@@ -154,7 +154,7 @@
                        language = 
mw.UploadWizard.config.languageTemplateFixups[ language ];
                }
 
-               return '{{' + language + '|1=' + description + '}}';
+               return '{{' + language + '|1=' + mw.Escaper.escapeForTemplate( 
description ) + '}}';
        };
 
        /**
diff --git a/resources/mw.Escaper.js b/resources/mw.Escaper.js
new file mode 100644
index 0000000..cd7d01e
--- /dev/null
+++ b/resources/mw.Escaper.js
@@ -0,0 +1,151 @@
+( function ( mw, OO ) {
+       mw.Escaper = {
+               /**
+                * Escapes wikitext for use inside {{templates}}.
+                *
+                * @param {string} wikitext
+                * @return {string}
+                */
+               escapeForTemplate: function ( wikitext ) {
+                       return this.escapePipes( wikitext );
+               },
+
+               /**
+                * Escapes pipe characters, which could be problematic when the 
content is
+                * inserted in a template.
+                *
+                * @param {string} wikitext
+                * @return {string}
+                */
+               escapePipes: function ( wikitext ) {
+                       var extractedTemplates, extractedLinks;
+
+                       // Pipes (`|`) must be escaped because we'll be 
inserting this
+                       // content into a templates & pipes would mess up the 
syntax.
+                       // First, urlencode pipes inside links:
+                       wikitext = wikitext.replace( /\bhttps?:\/\/[^\s]+/g, 
function ( match ) {
+                               return match.replace( /\|/g, '%7C' );
+                       } );
+
+                       // Second, pipes can be valid inside other templates or 
links in
+                       // wikitext, so we'll first extract those from the 
content, then
+                       // replace the pipes, then restore the original 
(extracted) content:
+                       extractedTemplates = this.extractTemplates( wikitext );
+                       extractedLinks = this.extractLinks( extractedTemplates[ 
0 ] );
+                       wikitext = extractedLinks[ 0 ].replace( /\|/g, '{{!}}' 
);
+                       return this.restoreExtracts( wikitext, $.extend( 
extractedTemplates[ 1 ], extractedLinks[ 1 ] ) );
+               },
+
+               /**
+                * Extract all {{templates}} from wikitext, replacing them with
+                * placeholder content in the form of {{1}}, {{2}}.
+                *
+                * Nested templates will safely be extracted by first replacing 
inner
+                * templates, then moving outwards, ensuring we don't get 
closing
+                * bracket mismatches.
+                *
+                * Restoring the content is as simple as feeding the returned 
content &
+                * replacements back into this.restoreExtracts.
+                *
+                * @param {string} wikitext
+                * @return {array} [{string} wikitext, {Object} replacements]
+                */
+               extractTemplates: function ( wikitext ) {
+                       var extracts = {},
+                               previousExtracts = {},
+                               extracted = wikitext,
+                               // the regex explained:
+                               // * `[^\{]`: character can not be {
+                               // * `\{(?!\{)`: or if it is, it can't be 
followed by another {
+                               // this excludes template opening brackets: {{
+                               // * `\{\{[0-9]+\}\}`: unless it's a complete 
{{[0-9]+}}
+                               //   sequence, generated by an earlier run of 
this regex
+                               regex = 
/\{\{([^\{]|\{(?!\{)|\{\{[0-9]+\}\})*?\}\}/g,
+                               callback = function ( match ) {
+                                       var replacement = '{{' + Object.keys( 
extracts ).length + '}}';
+
+                                       // safeguard for not replacing 
already-replaced matches
+                                       // this makes sure that when real 
content contains something
+                                       // like {{1}}, it will still be 
replaced, while {{1}}
+                                       // generated by this code can be 
recognized & ignored
+                                       if ( match in previousExtracts ) {
+                                               return match;
+                                       }
+
+                                       extracts[ replacement ] = match;
+                                       return replacement;
+                               };
+
+                       do {
+                               wikitext = extracted;
+                               previousExtracts = OO.copy( extracts );
+                               extracted = wikitext.replace( regex, callback );
+                       } while ( wikitext !== extracted );
+
+                       return [ wikitext, extracts ];
+               },
+
+               /**
+                * Extract all [[links]] from wikitext, replacing them with 
placeholder
+                * content in the form of [[1]], [[2]].
+                *
+                * Restoring the content is as simple as feeding the returned 
content &
+                * replacements back into this.restoreExtracts.
+                *
+                * @param {string} wikitext
+                * @return {array} [{string} wikitext, {Object} replacements]
+                */
+               extractLinks: function ( wikitext ) {
+                       var extracts = {};
+
+                       wikitext = wikitext.replace( /\[\[.*?\]\]/g, function ( 
match ) {
+                               var replacement = '[[' + Object.keys( extracts 
).length + ']]';
+                               extracts[ replacement ] = match;
+                               return replacement;
+                       } );
+
+                       return [ wikitext, extracts ];
+               },
+
+               /**
+                * Restores content that was extracted from wikitext.
+                *
+                * @param {string} wikitext
+                * @param {Object} replacements
+                * @return {string}
+                */
+               restoreExtracts: function ( wikitext, replacements ) {
+                       // turn search keys into a regular expression, allowing 
us to match
+                       // all of them at once
+                       var searchValues = Object.keys( replacements ).map( 
mw.RegExp.escape ),
+                               searchRegex = new RegExp( '(' + 
searchValues.join( '|' ) + ')', 'g' ),
+                               callback = function ( match ) {
+                                       var replacement = replacements[ match ];
+
+                                       // we matched something that has no 
replacement, must be valid
+                                       // user input that just happens to look 
like on of the
+                                       // replacement values
+                                       if ( replacement === undefined ) {
+                                               return match;
+                                       }
+
+                                       // if we find the replacement itself 
matches a search value, we
+                                       // also don't want to go recursive: 
nesting doesn't work like
+                                       // that, it's just a coincidence where 
user input happens to
+                                       // look just like a replacement value 
(e.g. `{{1}}`)
+                                       if ( replacement in replacements ) {
+                                               return replacement;
+                                       }
+
+                                       // we must not replace this one again, 
to avoid getting stuck in
+                                       // endless recursion
+                                       delete replacements[ match ];
+
+                                       // go recursive, there may be more 
replacements nested down there
+                                       return this.restoreExtracts( 
replacement, replacements );
+                               }.bind( this );
+
+                       return wikitext.replace( searchRegex, callback );
+               }
+       };
+} )( mediaWiki, OO );
diff --git a/resources/mw.UploadWizardDetails.js 
b/resources/mw.UploadWizardDetails.js
index 2f01f34..efc62f8 100644
--- a/resources/mw.UploadWizardDetails.js
+++ b/resources/mw.UploadWizardDetails.js
@@ -304,7 +304,7 @@
                getThumbnailCaption: function () {
                        var descriptions = 
this.descriptionsDetails.getSerialized().descriptions;
                        if ( descriptions.length > 0 ) {
-                               return descriptions[ 0 ].description.trim();
+                               return mw.Escaper.escapeForTemplate( 
descriptions[ 0 ].description.trim() );
                        } else {
                                return '';
                        }
@@ -661,7 +661,8 @@
                        info = '';
 
                        for ( key in information ) {
-                               info += '|' + key.replace( /:/g, '_' ) + '=' + 
information[ key ] + '\n';
+                               info += '|' + key.replace( /:/g, '_' );
+                               info += '=' + mw.Escaper.escapeForTemplate( 
information[ key ] ) + '\n';
                        }
 
                        wikiText += '=={{int:filedesc}}==\n';

-- 
To view, visit https://gerrit.wikimedia.org/r/307115
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I0c01db0a3d79f060173d88f10ca80c9fbc44a2fb
Gerrit-PatchSet: 12
Gerrit-Project: mediawiki/extensions/UploadWizard
Gerrit-Branch: master
Gerrit-Owner: Matthias Mullie <[email protected]>
Gerrit-Reviewer: Bartosz DziewoƄski <[email protected]>
Gerrit-Reviewer: Dereckson <[email protected]>
Gerrit-Reviewer: MarkTraceur <[email protected]>
Gerrit-Reviewer: Matthias Mullie <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to