Skip to content

Commit daa142c

Browse files
authored
Enforce escaping in JS scripts (2/x)
1 parent 4b09b06 commit daa142c

File tree

23 files changed

+160
-145
lines changed

23 files changed

+160
-145
lines changed

js/RichText/UserMention.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
*/
3333

3434
/* global tinymce */
35+
/* global _ */
3536

3637
window.GLPI = window.GLPI || {};
3738
window.GLPI.RichText = window.GLPI.RichText || {};
@@ -157,6 +158,6 @@ window.GLPI.RichText.UserMention = class {
157158
generateUserMentionHtml(user) {
158159
return `<span contenteditable="false"
159160
data-user-mention="true"
160-
data-user-id="${user.id}">@${user.name}</span>&nbsp;`;
161+
data-user-id="${_.escape(user.id)}">@${_.escape(user.name)}</span>&nbsp;`;
161162
}
162163
};

js/cable.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ function refreshAssetBreadcrumb(itemtype, items_id, dom_to_update) {
4242
itemtype: itemtype,
4343
}
4444
}).done((html_breadcrum) => {
45-
$(`#${dom_to_update}`).empty();
46-
$(`#${dom_to_update}`).append(html_breadcrum);
45+
$(`#${CSS.escape(dom_to_update)}`).empty();
46+
$(`#${CSS.escape(dom_to_update)}`).append(html_breadcrum);
4747
});
4848

4949
}
@@ -59,8 +59,8 @@ function refreshNetworkPortDropdown(itemtype, items_id, dom_to_update) {
5959
itemtype: itemtype,
6060
}
6161
}).done((html_data) => {
62-
$(`#${dom_to_update}`).empty();
63-
$(`#${dom_to_update}`).append(html_data);
62+
$(`#${CSS.escape(dom_to_update)}`).empty();
63+
$(`#${CSS.escape(dom_to_update)}`).append(html_data);
6464
});
6565
}
6666

@@ -77,7 +77,7 @@ function refreshSocketDropdown(itemtype, items_id, socketmodels_id, dom_name) {
7777
dom_name: dom_name
7878
}
7979
}).done((html_data) => {
80-
const parent_dom = $(`select[name="${dom_name}"]`).parent().parent();
80+
const parent_dom = $(`select[name="${CSS.escape(dom_name)}"]`).parent().parent();
8181
parent_dom.empty();
8282
parent_dom.append(html_data);
8383
});

js/common.js

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ function displayOtherSelectOptions(select_object, other_option_name) {
174174
**/
175175
function checkAsCheckboxes(reference, container_id, checkboxes_selector = 'input[type="checkbox"]') {
176176
reference = typeof(reference) === 'string' ? document.getElementById(reference) : reference;
177-
$('#' + container_id + ' ' + checkboxes_selector + ':enabled')
177+
$('#' + CSS.escape(container_id) + ' ' + checkboxes_selector + ':enabled')
178178
.prop('checked', $(reference).is(':checked'));
179179

180180
return true;
@@ -236,14 +236,14 @@ function showHideDiv(id, img_name = '', img_src_close = '', img_src_open = '') {
236236
var _deco;
237237
var _img;
238238
if (!_awesome) {
239-
_img = $('img[name=' + img_name + ']');
239+
_img = $('img[name=' + CSS.escape(img_name) + ']');
240240
if (_elt.is(':visible')) {
241241
_img.attr('src', img_src_close);
242242
} else {
243243
_img.attr('src', img_src_open);
244244
}
245245
} else {
246-
_deco = $('#'+img_name);
246+
_deco = $('#' + CSS.escape(img_name));
247247
if (_elt.is(':visible')) {
248248
_deco
249249
.removeClass(img_src_open)
@@ -361,7 +361,8 @@ function submitGetLink(target, fields) {
361361
* @param id
362362
**/
363363
function selectAll(id) {
364-
var element =$('#'+id);var selected = [];
364+
var element = $('#'+CSS.escape(id));
365+
var selected = [];
365366
element.find('option').each(function(i,e){
366367
selected[selected.length]=$(e).attr('value');
367368
});
@@ -375,7 +376,7 @@ function selectAll(id) {
375376
* @param id
376377
**/
377378
function deselectAll(id) {
378-
$('#'+id).val('').trigger('change');
379+
$('#' + CSS.escape(id)).val('').trigger('change');
379380
}
380381

381382

@@ -393,7 +394,7 @@ function massiveUpdateCheckbox(criterion, reference) {
393394
if (typeof(reference) == 'boolean') {
394395
value = reference;
395396
} else if (typeof(reference) == 'string') {
396-
value = $('#' + reference).prop('checked');
397+
value = $('#' + CSS.escape(reference)).prop('checked');
397398
} else if (typeof(reference) == 'object') {
398399
value = $(reference).prop('checked');
399400
}
@@ -612,7 +613,7 @@ var getExtIcon = function(ext) {
612613
url = CFG_GLPI.root_doc+'/pics/icones/defaut-dist.png';
613614
}
614615

615-
return '<img src="'+url+'" title="'+ext+'">';
616+
return '<img src="' + _.escape(url) + '" title="' + _.escape(ext) + '">';
616617
};
617618

618619
/**
@@ -752,7 +753,7 @@ var initMap = function(parent_elt, map_id, height, initial_view = {position: [0,
752753
}
753754

754755
//add map, set a default arbitrary location
755-
parent_elt.append($('<div id="'+map_id+'" style="height: ' + height + '"></div>'));
756+
parent_elt.append($('<div id="'+_.escape(map_id)+'" style="height: ' + _.escape(height) + '"></div>'));
756757
var map = L.map(map_id, {fullscreenControl: true, minZoom: 2}).setView(initial_view.position, initial_view.zoom);
757758

758759
//setup tiles and © messages
@@ -764,7 +765,7 @@ var initMap = function(parent_elt, map_id, height, initial_view = {position: [0,
764765

765766
var showMapForLocation = function(elt) {
766767
var _id = $(elt).data('fid');
767-
var _items_id = $('#' + _id).val();
768+
var _items_id = $('#' + CSS.escape(_id)).val();
768769

769770
if (_items_id == 0) {
770771
return;
@@ -785,7 +786,7 @@ var showMapForLocation = function(elt) {
785786
url: CFG_GLPI.root_doc + '/ajax/getMapPoint.php',
786787
data: {
787788
itemtype: 'Location',
788-
items_id: $('#' + _id).val()
789+
items_id: $('#' + CSS.escape(_id)).val()
789790
}
790791
}).done(function(data) {
791792
if (data.success === false) {
@@ -850,7 +851,7 @@ var templateResult = function(result) {
850851
_elt.attr('title', result.title);
851852

852853
if (typeof query.term !== 'undefined' && typeof result.rendered_text !== 'undefined') {
853-
_elt.html(result.rendered_text);
854+
_elt.html(result.rendered_text); // rendered_text is expected to be a safe HTML string
854855
} else {
855856
if (!result.text) {
856857
return null;
@@ -1051,8 +1052,8 @@ var escapeMarkupText = function (text) {
10511052
* @return void
10521053
*/
10531054
function updateProgress(progressid) {
1054-
var progress = $("progress#progress"+progressid).first();
1055-
$("div[data-progressid='"+progressid+"']").each(function(i, item) {
1055+
var progress = $("#"+CSS.escape(progressid)).first();
1056+
$("div[data-progressid='"+CSS.escape(progressid)+"']").each(function(i, item) {
10561057
var j_item = $(item);
10571058
var fg = j_item.find(".progress-fg").first();
10581059
var calcWidth = (progress.attr('value') / progress.attr('max')) * 100;
@@ -1219,7 +1220,7 @@ function updateItemOnEvent(dropdown_ids, target, url, params = {}, events = ['ch
12191220
//TODO Manage buffer time
12201221

12211222
const cleaned_zone_id = zone.replace('[', '_').replace(']', '_');
1222-
const zone_obj = $(`#${cleaned_zone_id}`);
1223+
const zone_obj = $(`#${CSS.escape(cleaned_zone_id)}`);
12231224

12241225
zone_obj.on(event, () => {
12251226
const conditional = (min_size >= 0 || force_load_for.length > 0);
@@ -1233,9 +1234,9 @@ function updateItemOnEvent(dropdown_ids, target, url, params = {}, events = ['ch
12331234
if (typeof v === "string") {
12341235
const reqs = v.match(/^__VALUE(\d+)__$/);
12351236
if (reqs !== null) {
1236-
resolved_params[k] = $('#'+dropdown_ids[reqs[0]]).val();
1237+
resolved_params[k] = $('#'+CSS.escape(dropdown_ids[reqs[0]])).val();
12371238
} else if (v === '__VALUE__') {
1238-
resolved_params[k] = $('#'+dropdown_ids[0]).val();
1239+
resolved_params[k] = $('#'+CSS.escape(dropdown_ids[0])).val();
12391240
} else {
12401241
resolved_params[k] = v;
12411242
}
@@ -1354,11 +1355,11 @@ function tableToDetails(table) {
13541355
if (in_details) {
13551356
details += '</pre></details>';
13561357
}
1357-
details += `<details><summary>${e.innerText}</summary><pre>`;
1358+
details += `<details><summary>${_.escape(e.innerText)}</summary><pre>`;
13581359
in_details = true;
13591360
} else {
13601361
if (in_details) {
1361-
details += e.innerText;
1362+
details += _.escape(e.innerText);
13621363
}
13631364
}
13641365
});
@@ -1555,27 +1556,27 @@ $(document.body).on('shown.bs.tab', 'a[data-bs-toggle="tab"]', (e) => {
15551556
* @param {string} item The ID of the field to be shown
15561557
*/
15571558
function showDisclosablePasswordField(item) {
1558-
$("#" + item).prop("type", "text");
1559+
$("#" + CSS.escape(item)).prop("type", "text");
15591560
}
15601561

15611562
/**
15621563
* Converts a normal text field to a password field
15631564
* @param {string} item The ID of the field to be hidden
15641565
*/
15651566
function hideDisclosablePasswordField(item) {
1566-
$("#" + item).prop("type", "password");
1567+
$("#" + CSS.escape(item)).prop("type", "password");
15671568
}
15681569

15691570
/**
15701571
* Copies the password from a disclosable password field to the clipboard
15711572
* @param {string} item The ID of the field to be copied
15721573
*/
15731574
function copyDisclosablePasswordFieldToClipboard(item) {
1574-
const is_password_input = $("#" + item).prop("type") === "password";
1575+
const is_password_input = $("#" + CSS.escape(item)).prop("type") === "password";
15751576
if (is_password_input) {
15761577
showDisclosablePasswordField(item);
15771578
}
1578-
$("#" + item).select();
1579+
$("#" + CSS.escape(item)).select();
15791580
try {
15801581
document.execCommand("copy");
15811582
} catch {
@@ -1591,7 +1592,7 @@ function copyDisclosablePasswordFieldToClipboard(item) {
15911592
* @param element_id The ID of the table to be converted
15921593
*/
15931594
function initSortableTable(element_id) {
1594-
const element = $(`#${element_id}`);
1595+
const element = $(`#${CSS.escape(element_id)}`);
15951596
const sort_table = (column_index) => {
15961597
const current_sort = element.data('sort');
15971598
element.data('sort', column_index);
@@ -1700,7 +1701,7 @@ if (typeof GlpiCommonAjaxController == "function") {
17001701
function setupAjaxDropdown(config) {
17011702
// Field ID is used as a selector, so we need to escape special characters
17021703
// to avoid issues with jQuery.
1703-
const field_id = $.escapeSelector(config.field_id);
1704+
const field_id = CSS.escape(config.field_id);
17041705

17051706
const select2_el = $('#' + field_id).select2({
17061707
containerCssClass: config.container_css_class,
@@ -1796,7 +1797,7 @@ function setupAjaxDropdown(config) {
17961797

17971798
$('label[for=' + field_id + ']').on('click', function () { $('#' + field_id).select2('open'); });
17981799
$('#' + field_id).on('select2:open', function (e) {
1799-
const search_input = document.querySelector(`.select2-search__field[aria-controls='select2-${e.target.id}-results']`);
1800+
const search_input = document.querySelector(`.select2-search__field[aria-controls='select2-${CSS.escape(e.target.id)}-results']`);
18001801
if (search_input) {
18011802
search_input.focus();
18021803
}
@@ -1809,7 +1810,7 @@ function setupAdaptDropdown(config)
18091810
{
18101811
// Field ID is used as a selector, so we need to escape special characters
18111812
// to avoid issues with jQuery.
1812-
const field_id = $.escapeSelector(config.field_id);
1813+
const field_id = CSS.escape(config.field_id);
18131814

18141815
const options = {
18151816
width: config.width,
@@ -1909,8 +1910,8 @@ function setupAdaptDropdown(config)
19091910
$('label[for=' + field_id + ']').on('click', function () {
19101911
$('#' + field_id).select2('open');
19111912
});
1912-
$('#' + field_id).on('select2:open', function () {
1913-
const search_input = document.querySelector(`.select2-search__field[aria-controls='select2-\${e.target.id}-results']`);
1913+
$('#' + field_id).on('select2:open', function (e) {
1914+
const search_input = document.querySelector(`.select2-search__field[aria-controls='select2-${CSS.escape(e.target.id)}-results']`);
19141915
if (search_input) {
19151916
search_input.focus();
19161917
}

js/fileupload.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function uploadFile(file, editor) {
4343

4444
// Search for fileupload container.
4545
// First try to find an uplaoder having same name as editor element.
46-
var uploader = $(`[data-uploader-name="${editor.getElement().name}"]`);
46+
var uploader = $(`[data-uploader-name="${CSS.escape(editor.getElement().name)}"]`);
4747
if (uploader.length === 0) {
4848
// Fallback to uploader using default name
4949
uploader = $(editor.getElement()).closest('form').find('[data-uploader-name="filename"]');
@@ -81,7 +81,7 @@ var handleUploadedFile = function (files, files_data, input_name, container, edi
8181
editor = tinyMCE.get(editor_id);
8282
const uploaded_image = uploaded_images.find((entry) => entry.filename === file.name);
8383
const matching_image = uploaded_image !== undefined
84-
? editor.dom.select(`img[data-upload_id="${uploaded_image.upload_id}"]`)
84+
? editor.dom.select(`img[data-upload_id="${CSS.escape(uploaded_image.upload_id)}"]`)
8585
: [];
8686
if (matching_image.length > 0) {
8787
editor.dom.setAttrib(matching_image, 'id', tag_data.tag.replace(/#/g, ''));
@@ -123,7 +123,7 @@ var handleUploadedFile = function (files, files_data, input_name, container, edi
123123
* @param {Object} container The fileinfo container
124124
*/
125125
var displayUploadedFile = function(file, tag, editor, input_name, filecontainer) {
126-
var fileindex = $(`input[name^="_${input_name}["]`).length;
126+
var fileindex = $(`input[name^="_${CSS.escape(input_name)}["]`).length;
127127
var ext = file.name.split('.').pop();
128128

129129
var p = $('<p></p>')
@@ -173,7 +173,7 @@ var displayUploadedFile = function(file, tag, editor, input_name, filecontainer)
173173
var deleteImagePasted = function(elementsIdToRemove, tagToRemove, editor) {
174174
// Remove file display lines
175175
$.each(elementsIdToRemove, (index, element) => {
176-
$(`#${element}`).remove();
176+
$(`#${CSS.escape(element)}`).remove();
177177
});
178178

179179
if (typeof editor !== "undefined" && editor !== null

js/glpi_dialog.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
/* eslint no-var: 0 */
3535
/* global bootstrap */
36+
/* global _ */
3637

3738
/**
3839
* Create a dialog window
@@ -81,14 +82,14 @@ var glpi_html_dialog = function({
8182
var bclass = ("class" in button) ? button.class : 'btn-secondary';
8283

8384
buttons_html+= `
84-
<button type="button" id="${bid}"
85-
class="btn ${bclass}" data-bs-dismiss="modal">
85+
<button type="button" id="${_.escape(bid)}"
86+
class="btn ${_.escape(bclass)}" data-bs-dismiss="modal">
8687
${label}
8788
</button>`;
8889

8990
// add click event on button
9091
if ('click' in button) {
91-
$(document).on('click', `#${bid}`, (event) => {
92+
$(document).on('click', `#${CSS.escape(bid)}`, (event) => {
9293
button.click(event);
9394
});
9495
}
@@ -105,8 +106,8 @@ var glpi_html_dialog = function({
105106

106107
const data_bs_focus = !bs_focus ? 'data-bs-focus="false"' : '';
107108

108-
var modal = `<div class="modal fade ${modalclass}" id="${id}" role="dialog" ${data_bs_focus} aria-labelledby="${id}_title">
109-
<div class="modal-dialog ${dialogclass}">
109+
var modal = `<div class="modal fade ${_.escape(modalclass)}" id="${_.escape(id)}" role="dialog" ${data_bs_focus} aria-labelledby="${_.escape(id)}_title">
110+
<div class="modal-dialog ${_.escape(dialogclass)}">
110111
<div class="modal-content">
111112
<div class="modal-header">
112113
<h2 id="${id}_title" class="fs-4 modal-title" tabindex="-1">${title}</h2>
@@ -136,7 +137,7 @@ var glpi_html_dialog = function({
136137
// create global events
137138
myModalEl.addEventListener('shown.bs.modal', (event) => {
138139
// focus first element in modal
139-
$(`#${id}`).find("input, textarea, select").first().trigger("focus");
140+
$(`#${CSS.escape(id)}`).find("input, textarea, select").first().trigger("focus");
140141

141142
// call show event
142143
show(event);
@@ -150,7 +151,7 @@ var glpi_html_dialog = function({
150151
}
151152

152153
// remove html on modal close
153-
$(`#${id}`).remove();
154+
$(`#${CSS.escape(id)}`).remove();
154155
});
155156

156157
return id;
@@ -355,9 +356,9 @@ const glpi_toast = (title, message, css_class, options = {}) => {
355356
if (!valid_locations.includes(location)) {
356357
location = 'bottom-right';
357358
}
358-
const html = `<div class='toast-container ${location} p-3 messages_after_redirect'>
359-
<div id='toast_js_${toast_id}' class='toast ${animation_classes}' role='alert' aria-live='assertive' aria-atomic='true'>
360-
<div class='toast-header ${css_class}'>
359+
const html = `<div class='toast-container ${_.escape(location)} p-3 messages_after_redirect'>
360+
<div id='toast_js_${toast_id}' class='toast ${_.escape(animation_classes)}' role='alert' aria-live='assertive' aria-atomic='true'>
361+
<div class='toast-header ${_.escape(css_class)}'>
361362
<strong class='me-auto'>${title}</strong>
362363
<button type='button' class='btn-close' data-bs-dismiss='toast' aria-label='${__('Close')}'></button>
363364
</div>
@@ -368,7 +369,7 @@ const glpi_toast = (title, message, css_class, options = {}) => {
368369
</div>`;
369370
$('body').append(html);
370371

371-
const toast = new bootstrap.Toast(document.querySelector(`#toast_js_${toast_id}`), {
372+
const toast = new bootstrap.Toast(document.querySelector(`#toast_js_${CSS.escape(toast_id)}`), {
372373
delay: options.delay,
373374
});
374375
toast.show();

0 commit comments

Comments
 (0)