]> source.dussan.org Git - jquery-ui.git/commitdiff
Datepicker: Make sure text option are text, shorten HTML strings
authorMichał Gołębiowski-Owczarek <m.goleb@gmail.com>
Tue, 11 May 2021 22:59:42 +0000 (00:59 +0200)
committerGitHub <noreply@github.com>
Tue, 11 May 2021 22:59:42 +0000 (00:59 +0200)
Instead of using enormous HTML strings, various elements are now constructed
using jQuery APIs. This makes it more obvious user-provided data is used
correctly.

Fixes #15284
Closes gh-1953

tests/unit/datepicker/options.js
ui/widgets/datepicker.js

index e5e938a0f7b47d49d377a6672a708b7f2b0eff90..e58b9a75df5a8940d76acca16808fa1d4df996f1 100644 (file)
@@ -1171,4 +1171,55 @@ QUnit.test( "Ticket 7602: Stop datepicker from appearing with beforeShow event h
        inp.datepicker( "destroy" );
 } );
 
+QUnit.test( "Ticket #15284: escaping text parameters", function( assert ) {
+       assert.expect( 7 );
+
+       var done = assert.async();
+
+       var qf = $( "#qunit-fixture" );
+
+       window.uiGlobalXss = [];
+
+       var inp = testHelper.init( "#inp", {
+               showButtonPanel: true,
+               showOn: "both",
+               prevText: "<script>uiGlobalXss = uiGlobalXss.concat( [ 'prevText XSS' ] )</script>",
+               nextText: "<script>uiGlobalXss = uiGlobalXss.concat( [ 'nextText XSS' ] )</script>",
+               currentText: "<script>uiGlobalXss = uiGlobalXss.concat( [ 'currentText XSS' ] )</script>",
+               closeText: "<script>uiGlobalXss = uiGlobalXss.concat( [ 'closeText XSS' ] )</script>",
+               buttonText: "<script>uiGlobalXss = uiGlobalXss.concat( [ 'buttonText XSS' ] )</script>",
+               appendText: "<script>uiGlobalXss = uiGlobalXss.concat( [ 'appendText XSS' ] )</script>"
+       } );
+
+       var dp = $( "#ui-datepicker-div" );
+
+       testHelper.onFocus( inp, function() {
+               assert.equal( dp.find( ".ui-datepicker-prev" ).text().trim(),
+                       "<script>uiGlobalXss = uiGlobalXss.concat( [ 'prevText XSS' ] )</script>",
+                       "prevText escaped" );
+               assert.equal( dp.find( ".ui-datepicker-next" ).text().trim(),
+                       "<script>uiGlobalXss = uiGlobalXss.concat( [ 'nextText XSS' ] )</script>",
+                       "nextText escaped" );
+               assert.equal( dp.find( ".ui-datepicker-current" ).text().trim(),
+                       "<script>uiGlobalXss = uiGlobalXss.concat( [ 'currentText XSS' ] )</script>",
+                       "currentText escaped" );
+               assert.equal( dp.find( ".ui-datepicker-close" ).text().trim(),
+                       "<script>uiGlobalXss = uiGlobalXss.concat( [ 'closeText XSS' ] )</script>",
+                       "closeText escaped" );
+
+               assert.equal( qf.find( ".ui-datepicker-trigger" ).text().trim(),
+                       "<script>uiGlobalXss = uiGlobalXss.concat( [ 'buttonText XSS' ] )</script>",
+                       "buttonText escaped" );
+               assert.equal( qf.find( ".ui-datepicker-append" ).text().trim(),
+                       "<script>uiGlobalXss = uiGlobalXss.concat( [ 'appendText XSS' ] )</script>",
+                       "appendText escaped" );
+
+               assert.deepEqual( window.uiGlobalXss, [], "No XSS" );
+
+               delete window.uiGlobalXss;
+               inp.datepicker( "hide" ).datepicker( "destroy" );
+               done();
+       } );
+} );
+
 } );
index f03e075cd0266ff797be23b4ec6fa42494b1b050..4fd8843cfc759bba0f8845b73ed501a545a24e51 100644 (file)
@@ -240,7 +240,9 @@ $.extend( Datepicker.prototype, {
                        inst.append.remove();
                }
                if ( appendText ) {
-                       inst.append = $( "<span class='" + this._appendClass + "'>" + appendText + "</span>" );
+                       inst.append = $( "<span>" )
+                               .addClass( this._appendClass )
+                               .text( appendText );
                        input[ isRTL ? "before" : "after" ]( inst.append );
                }
 
@@ -257,12 +259,32 @@ $.extend( Datepicker.prototype, {
                if ( showOn === "button" || showOn === "both" ) { // pop-up date picker when button clicked
                        buttonText = this._get( inst, "buttonText" );
                        buttonImage = this._get( inst, "buttonImage" );
-                       inst.trigger = $( this._get( inst, "buttonImageOnly" ) ?
-                               $( "<img/>" ).addClass( this._triggerClass ).
-                                       attr( { src: buttonImage, alt: buttonText, title: buttonText } ) :
-                               $( "<button type='button'></button>" ).addClass( this._triggerClass ).
-                                       html( !buttonImage ? buttonText : $( "<img/>" ).attr(
-                                       { src:buttonImage, alt:buttonText, title:buttonText } ) ) );
+
+                       if ( this._get( inst, "buttonImageOnly" ) ) {
+                               inst.trigger = $( "<img>" )
+                                       .addClass( this._triggerClass )
+                                       .attr( {
+                                               src: buttonImage,
+                                               alt: buttonText,
+                                               title: buttonText
+                                       } );
+                       } else {
+                               inst.trigger = $( "<button type='button'>" )
+                                       .addClass( this._triggerClass );
+                               if ( buttonImage ) {
+                                       inst.trigger.html(
+                                               $( "<img>" )
+                                                       .attr( {
+                                                               src: buttonImage,
+                                                               alt: buttonText,
+                                                               title: buttonText
+                                                       } )
+                                       );
+                               } else {
+                                       inst.trigger.text( buttonText );
+                               }
+                       }
+
                        input[ isRTL ? "before" : "after" ]( inst.trigger );
                        inst.trigger.on( "click", function() {
                                if ( $.datepicker._datepickerShowing && $.datepicker._lastInput === input[ 0 ] ) {
@@ -1703,32 +1725,104 @@ $.extend( Datepicker.prototype, {
                        this._daylightSavingAdjust( new Date( drawYear, drawMonth - stepMonths, 1 ) ),
                        this._getFormatConfig( inst ) ) );
 
-               prev = ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ?
-                       "<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" +
-                       " title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w" ) + "'>" + prevText + "</span></a>" :
-                       ( hideIfNoPrevNext ? "" : "<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w" ) + "'>" + prevText + "</span></a>" ) );
+               if ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ) {
+                       prev = $( "<a>" )
+                               .attr( {
+                                       "class": "ui-datepicker-prev ui-corner-all",
+                                       "data-handler": "prev",
+                                       "data-event": "click",
+                                       title: prevText
+                               } )
+                               .append(
+                                       $( "<span>" )
+                                               .addClass( "ui-icon ui-icon-circle-triangle-" +
+                                                       ( isRTL ? "e" : "w" ) )
+                                               .text( prevText )
+                               )[ 0 ].outerHTML;
+               } else if ( hideIfNoPrevNext ) {
+                       prev = "";
+               } else {
+                       prev = $( "<a>" )
+                               .attr( {
+                                       "class": "ui-datepicker-prev ui-corner-all ui-state-disabled",
+                                       title: prevText
+                               } )
+                               .append(
+                                       $( "<span>" )
+                                               .addClass( "ui-icon ui-icon-circle-triangle-" +
+                                                       ( isRTL ? "e" : "w" ) )
+                                               .text( prevText )
+                               )[ 0 ].outerHTML;
+               }
 
                nextText = this._get( inst, "nextText" );
                nextText = ( !navigationAsDateFormat ? nextText : this.formatDate( nextText,
                        this._daylightSavingAdjust( new Date( drawYear, drawMonth + stepMonths, 1 ) ),
                        this._getFormatConfig( inst ) ) );
 
-               next = ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ?
-                       "<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" +
-                       " title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e" ) + "'>" + nextText + "</span></a>" :
-                       ( hideIfNoPrevNext ? "" : "<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e" ) + "'>" + nextText + "</span></a>" ) );
+               if ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ) {
+                       next = $( "<a>" )
+                               .attr( {
+                                       "class": "ui-datepicker-next ui-corner-all",
+                                       "data-handler": "next",
+                                       "data-event": "click",
+                                       title: nextText
+                               } )
+                               .append(
+                                       $( "<span>" )
+                                               .addClass( "ui-icon ui-icon-circle-triangle-" +
+                                                       ( isRTL ? "w" : "e" ) )
+                                               .text( nextText )
+                               )[ 0 ].outerHTML;
+               } else if ( hideIfNoPrevNext ) {
+                       next = "";
+               } else {
+                       next = $( "<a>" )
+                               .attr( {
+                                       "class": "ui-datepicker-next ui-corner-all ui-state-disabled",
+                                       title: nextText
+                               } )
+                               .append(
+                                       $( "<span>" )
+                                               .attr( "class", "ui-icon ui-icon-circle-triangle-" +
+                                                       ( isRTL ? "w" : "e" ) )
+                                               .text( nextText )
+                               )[ 0 ].outerHTML;
+               }
 
                currentText = this._get( inst, "currentText" );
                gotoDate = ( this._get( inst, "gotoCurrent" ) && inst.currentDay ? currentDate : today );
                currentText = ( !navigationAsDateFormat ? currentText :
                        this.formatDate( currentText, gotoDate, this._getFormatConfig( inst ) ) );
 
-               controls = ( !inst.inline ? "<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>" +
-                       this._get( inst, "closeText" ) + "</button>" : "" );
-
-               buttonPanel = ( showButtonPanel ) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + ( isRTL ? controls : "" ) +
-                       ( this._isInRange( inst, gotoDate ) ? "<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'" +
-                       ">" + currentText + "</button>" : "" ) + ( isRTL ? "" : controls ) + "</div>" : "";
+               controls = "";
+               if ( !inst.inline ) {
+                       controls = $( "<button>" )
+                               .attr( {
+                                       type: "button",
+                                       "class": "ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all",
+                                       "data-handler": "hide",
+                                       "data-event": "click"
+                               } )
+                               .text( this._get( inst, "closeText" ) )[ 0 ].outerHTML;
+               }
+
+               buttonPanel = "";
+               if ( showButtonPanel ) {
+                       buttonPanel = $( "<div class='ui-datepicker-buttonpane ui-widget-content'>" )
+                               .append( isRTL ? controls : "" )
+                               .append( this._isInRange( inst, gotoDate ) ?
+                                       $( "<button>" )
+                                               .attr( {
+                                                       type: "button",
+                                                       "class": "ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all",
+                                                       "data-handler": "today",
+                                                       "data-event": "click"
+                                               } )
+                                               .text( currentText ) :
+                                       "" )
+                               .append( isRTL ? "" : controls )[ 0 ].outerHTML;
+               }
 
                firstDay = parseInt( this._get( inst, "firstDay" ), 10 );
                firstDay = ( isNaN( firstDay ) ? 0 : firstDay );