Skip to content

Commit

Permalink
Datepicker: Make sure text option are text, shorten HTML strings
Browse files Browse the repository at this point in the history
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
  • Loading branch information
mgol committed May 11, 2021
1 parent effa323 commit afe20b7
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 21 deletions.
51 changes: 51 additions & 0 deletions tests/unit/datepicker/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
} );
} );

} );
136 changes: 115 additions & 21 deletions ui/widgets/datepicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}

Expand All @@ -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 ] ) {
Expand Down Expand Up @@ -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 );
Expand Down

0 comments on commit afe20b7

Please sign in to comment.