Skip to content

Commit

Permalink
WebUI: migrate away from inline HTML code
Browse files Browse the repository at this point in the history
`innerHTML` &  `outerHTML` setter will more or less evaluate the value which could be used to
inject malicious code. So replace them with safer alternatives.
  • Loading branch information
Chocobo1 committed Aug 7, 2024
1 parent 2d9e3b3 commit 609477d
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 114 deletions.
79 changes: 55 additions & 24 deletions src/webui/www/private/scripts/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -466,15 +466,26 @@ window.addEventListener("DOMContentLoaded", () => {
margin_left = (category_path.length - 1) * 20;
}

const html = `<span class="link" href="#" style="margin-left: ${margin_left}px;" onclick="setCategoryFilter(${hash}); return false;">`
+ '<img src="images/view-categories.svg"/>'
+ window.qBittorrent.Misc.escapeHtml(display_name) + " (" + count + ")" + "</span>";
const el = new Element("li", {
id: hash,
html: html
const span = document.createElement("span");
span.classList.add("link");
span.href = "#";
span.style.marginLeft = `${margin_left}px`;
span.textContent = `${display_name} (${count})`;
span.addEventListener("click", (event) => {
event.preventDefault();
setCategoryFilter(hash);
});
window.qBittorrent.Filters.categoriesFilterContextMenu.addTarget(el);
return el;

const img = document.createElement("img");
img.src = "images/view-categories.svg";
span.prepend(img);

const listItem = document.createElement("li");
listItem.id = hash;
listItem.appendChild(span);

window.qBittorrent.Filters.categoriesFilterContextMenu.addTarget(listItem);
return listItem;
};

const all = torrentsTable.getRowIds().length;
Expand Down Expand Up @@ -547,15 +558,25 @@ window.addEventListener("DOMContentLoaded", () => {
tagFilterList.getChildren().each(c => c.destroy());

const createLink = function(hash, text, count) {
const html = `<span class="link" href="#" onclick="setTagFilter(${hash}); return false;">`
+ '<img src="images/tags.svg"/>'
+ window.qBittorrent.Misc.escapeHtml(text) + " (" + count + ")" + "</span>";
const el = new Element("li", {
id: hash,
html: html
const span = document.createElement("span");
span.classList.add("link");
span.href = "#";
span.textContent = `${text} (${count})`;
span.addEventListener("click", (event) => {
event.preventDefault();
setTagFilter(hash);
});
window.qBittorrent.Filters.tagsFilterContextMenu.addTarget(el);
return el;

const img = document.createElement("img");
img.src = "images/tags.svg";
span.prepend(img);

const listItem = document.createElement("li");
listItem.id = hash;
listItem.appendChild(span);

window.qBittorrent.Filters.tagsFilterContextMenu.addTarget(listItem);
return listItem;
};

const torrentsCount = torrentsTable.getRowIds().length;
Expand Down Expand Up @@ -623,15 +644,25 @@ window.addEventListener("DOMContentLoaded", () => {
trackerFilterList.getChildren().each(c => c.destroy());

const createLink = function(hash, text, count) {
const html = '<span class="link" href="#" onclick="setTrackerFilter(' + hash + ');return false;">'
+ '<img src="images/trackers.svg"/>'
+ window.qBittorrent.Misc.escapeHtml(text.replace("%1", count)) + "</span>";
const el = new Element("li", {
id: hash,
html: html
const span = document.createElement("span");
span.classList.add("link");
span.href = "#";
span.textContent = text.replace("%1", count);
span.addEventListener("click", (event) => {
event.preventDefault();
setTrackerFilter(hash);
});
window.qBittorrent.Filters.trackersFilterContextMenu.addTarget(el);
return el;

const img = document.createElement("img");
img.src = "images/trackers.svg";
span.prepend(img);

const listItem = document.createElement("li");
listItem.id = hash;
listItem.appendChild(span);

window.qBittorrent.Filters.trackersFilterContextMenu.addTarget(listItem);
return listItem;
};

const torrentsCount = torrentsTable.getRowIds().length;
Expand Down
102 changes: 72 additions & 30 deletions src/webui/www/private/scripts/contextmenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ window.qBittorrent.ContextMenu ??= (() => {

const contextTagList = $("contextTagList");
tagList.forEach((tag, tagHash) => {
const checkbox = contextTagList.getElement(`a[href="#Tag/${tagHash}"] input[type="checkbox"]`);
const checkbox = contextTagList.getElement(`a[href="#Tag/${tag.name}"] input[type="checkbox"]`);
const count = tagCount.get(tag.name);
const hasCount = (count !== undefined);
const isLesser = (count < selectedRows.length);
Expand All @@ -438,7 +438,7 @@ window.qBittorrent.ContextMenu ??= (() => {

const contextCategoryList = document.getElementById("contextCategoryList");
category_list.forEach((category, categoryHash) => {
const categoryIcon = contextCategoryList.querySelector(`a[href$="(${categoryHash});"] img`);
const categoryIcon = contextCategoryList.querySelector(`a[href$="#Category/${category.name}"] img`);
const count = categoryCount.get(category.name);
const isEqual = ((count !== undefined) && (count === selectedRows.length));
categoryIcon.classList.toggle("highlightedCategoryIcon", isEqual);
Expand All @@ -448,12 +448,24 @@ window.qBittorrent.ContextMenu ??= (() => {
updateCategoriesSubMenu: function(categoryList) {
const contextCategoryList = $("contextCategoryList");
contextCategoryList.getChildren().each(c => c.destroy());
contextCategoryList.appendChild(new Element("li", {
html: '<a href="javascript:torrentNewCategoryFN();"><img src="images/list-add.svg" alt="QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget]"/>QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget]</a>'
}));
contextCategoryList.appendChild(new Element("li", {
html: '<a href="javascript:torrentSetCategoryFN(0);"><img src="images/edit-clear.svg" alt="QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget]"/>QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget]</a>'
}));

const createMenuItem = (text, imgURL, clickFn) => {
const anchor = document.createElement("a");
anchor.textContent = text;
anchor.addEventListener("click", clickFn);

const img = document.createElement("img");
img.src = imgURL;
img.alt = text;
anchor.prepend(img);

const item = document.createElement("li");
item.appendChild(anchor);

return item;
};
contextCategoryList.appendChild(createMenuItem("QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget]", "images/list-add.svg", torrentNewCategoryFN));
contextCategoryList.appendChild(createMenuItem("QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget]", "images/edit-clear.svg", () => { torrentSetCategoryFN(0); }));

const sortedCategories = [];
categoryList.forEach((category, hash) => sortedCategories.push({
Expand All @@ -465,14 +477,25 @@ window.qBittorrent.ContextMenu ??= (() => {

let first = true;
for (const { categoryName, categoryHash } of sortedCategories) {
const el = new Element("li", {
html: `<a href="javascript:torrentSetCategoryFN(${categoryHash});"><img src="images/view-categories.svg"/>${window.qBittorrent.Misc.escapeHtml(categoryName)}</a>`
const anchor = document.createElement("a");
anchor.href = `#Category/${categoryName}`;
anchor.textContent = categoryName;
anchor.addEventListener("click", (event) => {
torrentSetCategoryFN(categoryHash);
});

const img = document.createElement("img");
img.src = "images/view-categories.svg";
anchor.prepend(img);

const setCategoryItem = document.createElement("li");
setCategoryItem.appendChild(anchor);
if (first) {
el.addClass("separator");
setCategoryItem.addClass("separator");
first = false;
}
contextCategoryList.appendChild(el);

contextCategoryList.appendChild(setCategoryItem);
}
},

Expand All @@ -481,18 +504,23 @@ window.qBittorrent.ContextMenu ??= (() => {
while (contextTagList.firstChild !== null)
contextTagList.removeChild(contextTagList.firstChild);

contextTagList.appendChild(new Element("li", {
html: '<a href="javascript:torrentAddTagsFN();">'
+ '<img src="images/list-add.svg" alt="QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]"/>'
+ " QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]"
+ "</a>"
}));
contextTagList.appendChild(new Element("li", {
html: '<a href="javascript:torrentRemoveAllTagsFN();">'
+ '<img src="images/edit-clear.svg" alt="QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]"/>'
+ " QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]"
+ "</a>"
}));
const createMenuItem = (text, imgURL, clickFn) => {
const anchor = document.createElement("a");
anchor.textContent = text;
anchor.addEventListener("click", clickFn);

const img = document.createElement("img");
img.src = imgURL;
img.alt = text;
anchor.prepend(img);

const item = document.createElement("li");
item.appendChild(anchor);

return item;
};
contextTagList.appendChild(createMenuItem("QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]", "images/list-add.svg", torrentAddTagsFN));
contextTagList.appendChild(createMenuItem("QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]", "images/edit-clear.svg", torrentRemoveAllTagsFN));

const sortedTags = [];
tagList.forEach((tag, hash) => sortedTags.push({
Expand All @@ -503,14 +531,28 @@ window.qBittorrent.ContextMenu ??= (() => {

for (let i = 0; i < sortedTags.length; ++i) {
const { tagName, tagHash } = sortedTags[i];
const el = new Element("li", {
html: `<a href="#Tag/${tagHash}" onclick="event.preventDefault(); torrentSetTagsFN(${tagHash}, !event.currentTarget.getElement('input[type=checkbox]').checked);">`
+ '<input type="checkbox" onclick="this.checked = !this.checked;"> ' + window.qBittorrent.Misc.escapeHtml(tagName)
+ "</a>"

const input = document.createElement("input");
input.type = "checkbox";
input.addEventListener("click", (event) => {
input.checked = !input.checked;
});

const anchor = document.createElement("a");
anchor.href = `#Tag/${tagName}`;
anchor.textContent = tagName;
anchor.addEventListener("click", (event) => {
event.preventDefault();
torrentSetTagsFN(tagHash, !input.checked);
});
anchor.prepend(input);

const setTagItem = document.createElement("li");
setTagItem.appendChild(anchor);
if (i === 0)
el.addClass("separator");
contextTagList.appendChild(el);
setTagItem.addClass("separator");

contextTagList.appendChild(setTagItem);
}
}
});
Expand Down
28 changes: 17 additions & 11 deletions src/webui/www/private/scripts/dynamicTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,10 +333,18 @@ window.qBittorrent.DynamicTable ??= (() => {
});

const createLi = function(columnName, text) {
const html = '<a href="#' + columnName + '" ><img src="images/checked-completed.svg"/>' + window.qBittorrent.Misc.escapeHtml(text) + "</a>";
return new Element("li", {
html: html
});
const anchor = document.createElement("a");
anchor.href = `#${columnName}`;
anchor.textContent = text;

const img = document.createElement("img");
img.src = "images/checked-completed.svg";
anchor.prepend(img);

const listItem = document.createElement("li");
listItem.appendChild(anchor);

return listItem;
};

const actions = {};
Expand Down Expand Up @@ -2076,8 +2084,7 @@ window.qBittorrent.DynamicTable ??= (() => {
},
id: dirImgId
});
const html = dirImg.outerHTML + span.outerHTML;
td.innerHTML = html;
td.replaceChildren(dirImg, span);
}
}
else { // is file
Expand All @@ -2089,7 +2096,7 @@ window.qBittorrent.DynamicTable ??= (() => {
"margin-left": ((node.depth + 1) * 20)
}
});
td.innerHTML = span.outerHTML;
td.replaceChildren(span);
}
};

Expand All @@ -2103,7 +2110,7 @@ window.qBittorrent.DynamicTable ??= (() => {
text: value,
id: fileNameRenamedId,
});
td.innerHTML = span.outerHTML;
td.replaceChildren(span);
};
},

Expand Down Expand Up @@ -2409,8 +2416,7 @@ window.qBittorrent.DynamicTable ??= (() => {
},
id: dirImgId
});
const html = collapseIcon.outerHTML + dirImg.outerHTML + span.outerHTML;
td.innerHTML = html;
td.replaceChildren(collapseIcon, dirImg, span);
}
}
else {
Expand All @@ -2422,7 +2428,7 @@ window.qBittorrent.DynamicTable ??= (() => {
"margin-left": ((node.depth + 1) * 20)
}
});
td.innerHTML = span.outerHTML;
td.replaceChildren(span);
}
};

Expand Down
31 changes: 15 additions & 16 deletions src/webui/www/private/scripts/prop-files.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,32 +165,31 @@ window.qBittorrent.PropFiles ??= (() => {
return ($("comboPrio" + id) !== null);
};

const createPriorityOptionElement = function(priority, selected, html) {
const elem = new Element("option");
elem.value = priority.toString();
elem.innerHTML = html;
if (selected)
elem.selected = true;
return elem;
};
const createPriorityCombo = (id, fileId, selectedPriority) => {
const createOption = (priority, isSelected, text) => {
const option = document.createElement("option");
option.value = priority.toString();
option.selected = isSelected;
option.textContent = text;
return option;
};

const createPriorityCombo = function(id, fileId, selectedPriority) {
const select = new Element("select");
const select = document.createElement("select");
select.id = "comboPrio" + id;
select.setAttribute("data-id", id);
select.setAttribute("data-file-id", fileId);
select.addClass("combo_priority");
select.addEventListener("change", fileComboboxChanged);

createPriorityOptionElement(FilePriority.Ignored, (FilePriority.Ignored === selectedPriority), "QBT_TR(Do not download)QBT_TR[CONTEXT=PropListDelegate]").injectInside(select);
createPriorityOptionElement(FilePriority.Normal, (FilePriority.Normal === selectedPriority), "QBT_TR(Normal)QBT_TR[CONTEXT=PropListDelegate]").injectInside(select);
createPriorityOptionElement(FilePriority.High, (FilePriority.High === selectedPriority), "QBT_TR(High)QBT_TR[CONTEXT=PropListDelegate]").injectInside(select);
createPriorityOptionElement(FilePriority.Maximum, (FilePriority.Maximum === selectedPriority), "QBT_TR(Maximum)QBT_TR[CONTEXT=PropListDelegate]").injectInside(select);
select.appendChild(createOption(FilePriority.Ignored, (FilePriority.Ignored === selectedPriority), "QBT_TR(Do not download)QBT_TR[CONTEXT=PropListDelegate]"));
select.appendChild(createOption(FilePriority.Normal, (FilePriority.Normal === selectedPriority), "QBT_TR(Normal)QBT_TR[CONTEXT=PropListDelegate]"));
select.appendChild(createOption(FilePriority.High, (FilePriority.High === selectedPriority), "QBT_TR(High)QBT_TR[CONTEXT=PropListDelegate]"));
select.appendChild(createOption(FilePriority.Maximum, (FilePriority.Maximum === selectedPriority), "QBT_TR(Maximum)QBT_TR[CONTEXT=PropListDelegate]"));

// "Mixed" priority is for display only; it shouldn't be selectable
const mixedPriorityOption = createPriorityOptionElement(FilePriority.Mixed, (FilePriority.Mixed === selectedPriority), "QBT_TR(Mixed)QBT_TR[CONTEXT=PropListDelegate]");
const mixedPriorityOption = createOption(FilePriority.Mixed, (FilePriority.Mixed === selectedPriority), "QBT_TR(Mixed)QBT_TR[CONTEXT=PropListDelegate]");
mixedPriorityOption.disabled = true;
mixedPriorityOption.injectInside(select);
select.appendChild(mixedPriorityOption);

return select;
};
Expand Down
2 changes: 1 addition & 1 deletion src/webui/www/private/scripts/prop-general.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ window.qBittorrent.PropGeneral ??= (() => {
$("torrent_hash_v1").textContent = "";
$("torrent_hash_v2").textContent = "";
$("save_path").textContent = "";
$("comment").innerHTML = "";
$("comment").textContent = "";
$("private").textContent = "";
piecesBar.clear();
};
Expand Down
Loading

0 comments on commit 609477d

Please sign in to comment.