Skip to content

Commit afe20b7

Browse files
authored
Datepicker: Make sure text option are text, shorten HTML strings
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
1 parent effa323 commit afe20b7

File tree

Image for: File tree

2 files changed

Image for: 2 files changed
+166
-21
lines changed

2 files changed

Image for: 2 files changed
+166
-21
lines changed

‎tests/unit/datepicker/options.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,4 +1171,55 @@ QUnit.test( "Ticket 7602: Stop datepicker from appearing with beforeShow event h
11711171
inp.datepicker( "destroy" );
11721172
} );
11731173

1174+
QUnit.test( "Ticket #15284: escaping text parameters", function( assert ) {
1175+
assert.expect( 7 );
1176+
1177+
var done = assert.async();
1178+
1179+
var qf = $( "#qunit-fixture" );
1180+
1181+
window.uiGlobalXss = [];
1182+
1183+
var inp = testHelper.init( "#inp", {
1184+
showButtonPanel: true,
1185+
showOn: "both",
1186+
prevText: "<script>uiGlobalXss = uiGlobalXss.concat( [ 'prevText XSS' ] )</script>",
1187+
nextText: "<script>uiGlobalXss = uiGlobalXss.concat( [ 'nextText XSS' ] )</script>",
1188+
currentText: "<script>uiGlobalXss = uiGlobalXss.concat( [ 'currentText XSS' ] )</script>",
1189+
closeText: "<script>uiGlobalXss = uiGlobalXss.concat( [ 'closeText XSS' ] )</script>",
1190+
buttonText: "<script>uiGlobalXss = uiGlobalXss.concat( [ 'buttonText XSS' ] )</script>",
1191+
appendText: "<script>uiGlobalXss = uiGlobalXss.concat( [ 'appendText XSS' ] )</script>"
1192+
} );
1193+
1194+
var dp = $( "#ui-datepicker-div" );
1195+
1196+
testHelper.onFocus( inp, function() {
1197+
assert.equal( dp.find( ".ui-datepicker-prev" ).text().trim(),
1198+
"<script>uiGlobalXss = uiGlobalXss.concat( [ 'prevText XSS' ] )</script>",
1199+
"prevText escaped" );
1200+
assert.equal( dp.find( ".ui-datepicker-next" ).text().trim(),
1201+
"<script>uiGlobalXss = uiGlobalXss.concat( [ 'nextText XSS' ] )</script>",
1202+
"nextText escaped" );
1203+
assert.equal( dp.find( ".ui-datepicker-current" ).text().trim(),
1204+
"<script>uiGlobalXss = uiGlobalXss.concat( [ 'currentText XSS' ] )</script>",
1205+
"currentText escaped" );
1206+
assert.equal( dp.find( ".ui-datepicker-close" ).text().trim(),
1207+
"<script>uiGlobalXss = uiGlobalXss.concat( [ 'closeText XSS' ] )</script>",
1208+
"closeText escaped" );
1209+
1210+
assert.equal( qf.find( ".ui-datepicker-trigger" ).text().trim(),
1211+
"<script>uiGlobalXss = uiGlobalXss.concat( [ 'buttonText XSS' ] )</script>",
1212+
"buttonText escaped" );
1213+
assert.equal( qf.find( ".ui-datepicker-append" ).text().trim(),
1214+
"<script>uiGlobalXss = uiGlobalXss.concat( [ 'appendText XSS' ] )</script>",
1215+
"appendText escaped" );
1216+
1217+
assert.deepEqual( window.uiGlobalXss, [], "No XSS" );
1218+
1219+
delete window.uiGlobalXss;
1220+
inp.datepicker( "hide" ).datepicker( "destroy" );
1221+
done();
1222+
} );
1223+
} );
1224+
11741225
} );

‎ui/widgets/datepicker.js

Lines changed: 115 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,9 @@ $.extend( Datepicker.prototype, {
240240
inst.append.remove();
241241
}
242242
if ( appendText ) {
243-
inst.append = $( "<span class='" + this._appendClass + "'>" + appendText + "</span>" );
243+
inst.append = $( "<span>" )
244+
.addClass( this._appendClass )
245+
.text( appendText );
244246
input[ isRTL ? "before" : "after" ]( inst.append );
245247
}
246248

@@ -257,12 +259,32 @@ $.extend( Datepicker.prototype, {
257259
if ( showOn === "button" || showOn === "both" ) { // pop-up date picker when button clicked
258260
buttonText = this._get( inst, "buttonText" );
259261
buttonImage = this._get( inst, "buttonImage" );
260-
inst.trigger = $( this._get( inst, "buttonImageOnly" ) ?
261-
$( "<img/>" ).addClass( this._triggerClass ).
262-
attr( { src: buttonImage, alt: buttonText, title: buttonText } ) :
263-
$( "<button type='button'></button>" ).addClass( this._triggerClass ).
264-
html( !buttonImage ? buttonText : $( "<img/>" ).attr(
265-
{ src:buttonImage, alt:buttonText, title:buttonText } ) ) );
262+
263+
if ( this._get( inst, "buttonImageOnly" ) ) {
264+
inst.trigger = $( "<img>" )
265+
.addClass( this._triggerClass )
266+
.attr( {
267+
src: buttonImage,
268+
alt: buttonText,
269+
title: buttonText
270+
} );
271+
} else {
272+
inst.trigger = $( "<button type='button'>" )
273+
.addClass( this._triggerClass );
274+
if ( buttonImage ) {
275+
inst.trigger.html(
276+
$( "<img>" )
277+
.attr( {
278+
src: buttonImage,
279+
alt: buttonText,
280+
title: buttonText
281+
} )
282+
);
283+
} else {
284+
inst.trigger.text( buttonText );
285+
}
286+
}
287+
266288
input[ isRTL ? "before" : "after" ]( inst.trigger );
267289
inst.trigger.on( "click", function() {
268290
if ( $.datepicker._datepickerShowing && $.datepicker._lastInput === input[ 0 ] ) {
@@ -1703,32 +1725,104 @@ $.extend( Datepicker.prototype, {
17031725
this._daylightSavingAdjust( new Date( drawYear, drawMonth - stepMonths, 1 ) ),
17041726
this._getFormatConfig( inst ) ) );
17051727

1706-
prev = ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ?
1707-
"<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" +
1708-
" title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w" ) + "'>" + prevText + "</span></a>" :
1709-
( 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>" ) );
1728+
if ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ) {
1729+
prev = $( "<a>" )
1730+
.attr( {
1731+
"class": "ui-datepicker-prev ui-corner-all",
1732+
"data-handler": "prev",
1733+
"data-event": "click",
1734+
title: prevText
1735+
} )
1736+
.append(
1737+
$( "<span>" )
1738+
.addClass( "ui-icon ui-icon-circle-triangle-" +
1739+
( isRTL ? "e" : "w" ) )
1740+
.text( prevText )
1741+
)[ 0 ].outerHTML;
1742+
} else if ( hideIfNoPrevNext ) {
1743+
prev = "";
1744+
} else {
1745+
prev = $( "<a>" )
1746+
.attr( {
1747+
"class": "ui-datepicker-prev ui-corner-all ui-state-disabled",
1748+
title: prevText
1749+
} )
1750+
.append(
1751+
$( "<span>" )
1752+
.addClass( "ui-icon ui-icon-circle-triangle-" +
1753+
( isRTL ? "e" : "w" ) )
1754+
.text( prevText )
1755+
)[ 0 ].outerHTML;
1756+
}
17101757

17111758
nextText = this._get( inst, "nextText" );
17121759
nextText = ( !navigationAsDateFormat ? nextText : this.formatDate( nextText,
17131760
this._daylightSavingAdjust( new Date( drawYear, drawMonth + stepMonths, 1 ) ),
17141761
this._getFormatConfig( inst ) ) );
17151762

1716-
next = ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ?
1717-
"<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" +
1718-
" title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e" ) + "'>" + nextText + "</span></a>" :
1719-
( 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>" ) );
1763+
if ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ) {
1764+
next = $( "<a>" )
1765+
.attr( {
1766+
"class": "ui-datepicker-next ui-corner-all",
1767+
"data-handler": "next",
1768+
"data-event": "click",
1769+
title: nextText
1770+
} )
1771+
.append(
1772+
$( "<span>" )
1773+
.addClass( "ui-icon ui-icon-circle-triangle-" +
1774+
( isRTL ? "w" : "e" ) )
1775+
.text( nextText )
1776+
)[ 0 ].outerHTML;
1777+
} else if ( hideIfNoPrevNext ) {
1778+
next = "";
1779+
} else {
1780+
next = $( "<a>" )
1781+
.attr( {
1782+
"class": "ui-datepicker-next ui-corner-all ui-state-disabled",
1783+
title: nextText
1784+
} )
1785+
.append(
1786+
$( "<span>" )
1787+
.attr( "class", "ui-icon ui-icon-circle-triangle-" +
1788+
( isRTL ? "w" : "e" ) )
1789+
.text( nextText )
1790+
)[ 0 ].outerHTML;
1791+
}
17201792

17211793
currentText = this._get( inst, "currentText" );
17221794
gotoDate = ( this._get( inst, "gotoCurrent" ) && inst.currentDay ? currentDate : today );
17231795
currentText = ( !navigationAsDateFormat ? currentText :
17241796
this.formatDate( currentText, gotoDate, this._getFormatConfig( inst ) ) );
17251797

1726-
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'>" +
1727-
this._get( inst, "closeText" ) + "</button>" : "" );
1728-
1729-
buttonPanel = ( showButtonPanel ) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + ( isRTL ? controls : "" ) +
1730-
( 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'" +
1731-
">" + currentText + "</button>" : "" ) + ( isRTL ? "" : controls ) + "</div>" : "";
1798+
controls = "";
1799+
if ( !inst.inline ) {
1800+
controls = $( "<button>" )
1801+
.attr( {
1802+
type: "button",
1803+
"class": "ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all",
1804+
"data-handler": "hide",
1805+
"data-event": "click"
1806+
} )
1807+
.text( this._get( inst, "closeText" ) )[ 0 ].outerHTML;
1808+
}
1809+
1810+
buttonPanel = "";
1811+
if ( showButtonPanel ) {
1812+
buttonPanel = $( "<div class='ui-datepicker-buttonpane ui-widget-content'>" )
1813+
.append( isRTL ? controls : "" )
1814+
.append( this._isInRange( inst, gotoDate ) ?
1815+
$( "<button>" )
1816+
.attr( {
1817+
type: "button",
1818+
"class": "ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all",
1819+
"data-handler": "today",
1820+
"data-event": "click"
1821+
} )
1822+
.text( currentText ) :
1823+
"" )
1824+
.append( isRTL ? "" : controls )[ 0 ].outerHTML;
1825+
}
17321826

17331827
firstDay = parseInt( this._get( inst, "firstDay" ), 10 );
17341828
firstDay = ( isNaN( firstDay ) ? 0 : firstDay );

0 commit comments

Image for: 0 commit comments
Comments
 (0)