diff --git a/tests/unit/datepicker/options.js b/tests/unit/datepicker/options.js index e5e938a0f7b..e58b9a75df5 100644 --- a/tests/unit/datepicker/options.js +++ b/tests/unit/datepicker/options.js @@ -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: "", + nextText: "", + currentText: "", + closeText: "", + buttonText: "", + appendText: "" + } ); + + var dp = $( "#ui-datepicker-div" ); + + testHelper.onFocus( inp, function() { + assert.equal( dp.find( ".ui-datepicker-prev" ).text().trim(), + "", + "prevText escaped" ); + assert.equal( dp.find( ".ui-datepicker-next" ).text().trim(), + "", + "nextText escaped" ); + assert.equal( dp.find( ".ui-datepicker-current" ).text().trim(), + "", + "currentText escaped" ); + assert.equal( dp.find( ".ui-datepicker-close" ).text().trim(), + "", + "closeText escaped" ); + + assert.equal( qf.find( ".ui-datepicker-trigger" ).text().trim(), + "", + "buttonText escaped" ); + assert.equal( qf.find( ".ui-datepicker-append" ).text().trim(), + "", + "appendText escaped" ); + + assert.deepEqual( window.uiGlobalXss, [], "No XSS" ); + + delete window.uiGlobalXss; + inp.datepicker( "hide" ).datepicker( "destroy" ); + done(); + } ); +} ); + } ); diff --git a/ui/widgets/datepicker.js b/ui/widgets/datepicker.js index f03e075cd02..4fd8843cfc7 100644 --- a/ui/widgets/datepicker.js +++ b/ui/widgets/datepicker.js @@ -240,7 +240,9 @@ $.extend( Datepicker.prototype, { inst.append.remove(); } if ( appendText ) { - inst.append = $( "" + appendText + "" ); + inst.append = $( "" ) + .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" ) ? - $( "" ).addClass( this._triggerClass ). - attr( { src: buttonImage, alt: buttonText, title: buttonText } ) : - $( "" ).addClass( this._triggerClass ). - html( !buttonImage ? buttonText : $( "" ).attr( - { src:buttonImage, alt:buttonText, title:buttonText } ) ) ); + + if ( this._get( inst, "buttonImageOnly" ) ) { + inst.trigger = $( "" ) + .addClass( this._triggerClass ) + .attr( { + src: buttonImage, + alt: buttonText, + title: buttonText + } ); + } else { + inst.trigger = $( "" ) + .addClass( this._triggerClass ); + if ( buttonImage ) { + inst.trigger.html( + $( "" ) + .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 ) ? - "" + prevText + "" : - ( hideIfNoPrevNext ? "" : "" + prevText + "" ) ); + if ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ) { + prev = $( "" ) + .attr( { + "class": "ui-datepicker-prev ui-corner-all", + "data-handler": "prev", + "data-event": "click", + title: prevText + } ) + .append( + $( "" ) + .addClass( "ui-icon ui-icon-circle-triangle-" + + ( isRTL ? "e" : "w" ) ) + .text( prevText ) + )[ 0 ].outerHTML; + } else if ( hideIfNoPrevNext ) { + prev = ""; + } else { + prev = $( "" ) + .attr( { + "class": "ui-datepicker-prev ui-corner-all ui-state-disabled", + title: prevText + } ) + .append( + $( "" ) + .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 ) ? - "" + nextText + "" : - ( hideIfNoPrevNext ? "" : "" + nextText + "" ) ); + if ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ) { + next = $( "" ) + .attr( { + "class": "ui-datepicker-next ui-corner-all", + "data-handler": "next", + "data-event": "click", + title: nextText + } ) + .append( + $( "" ) + .addClass( "ui-icon ui-icon-circle-triangle-" + + ( isRTL ? "w" : "e" ) ) + .text( nextText ) + )[ 0 ].outerHTML; + } else if ( hideIfNoPrevNext ) { + next = ""; + } else { + next = $( "" ) + .attr( { + "class": "ui-datepicker-next ui-corner-all ui-state-disabled", + title: nextText + } ) + .append( + $( "" ) + .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 ? "" + - this._get( inst, "closeText" ) + "" : "" ); - - buttonPanel = ( showButtonPanel ) ? "" + ( isRTL ? controls : "" ) + - ( this._isInRange( inst, gotoDate ) ? "" + currentText + "" : "" ) + ( isRTL ? "" : controls ) + "" : ""; + controls = ""; + if ( !inst.inline ) { + controls = $( "" ) + .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 = $( "" ) + .append( isRTL ? controls : "" ) + .append( this._isInRange( inst, gotoDate ) ? + $( "" ) + .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 );