diff --git a/delivery/common/js/views/PercArchiveListView.js b/delivery/common/js/views/PercArchiveListView.js index a7a9d5e23f..779dc3821c 100644 --- a/delivery/common/js/views/PercArchiveListView.js +++ b/delivery/common/js/views/PercArchiveListView.js @@ -30,6 +30,12 @@ $.PercArchiveListView = { updateArchiveList : updateArchiveList }; + function percSafeUrl(url) + { + var u = String(url); + if (/^\s*(?:javascript|vbscript|data)\s*:/i.test(u)) {return "#";} + return u; + } function updateArchiveList() { $(".perc-archive-list").each(function(){ @@ -128,7 +134,7 @@ encodedQuery = "&query=" + encodeURIComponent(JSON.stringify(query)); href = baseURL + pageResult + "?filter="+ encodeURIComponent(row.year) + encodedQuery; anchorYear = $("") - .attr("href",href) + .attr("href",percSafeUrl(href)) .text(linkYearText); } @@ -188,7 +194,7 @@ encodedQuery = "&query=" + encodeURIComponent(JSON.stringify(query)); href = baseURL + pageResult + "?filter="+ encodeURIComponent(row2.month + " " + row.year )+ encodedQuery; a = $("") - .attr("href",href ) + .attr("href",percSafeUrl(href) ) .text(linkText); } @@ -276,7 +282,7 @@ var encodedQuery = "&query=" + encodeURIComponent(JSON.stringify(query)); var href = baseURL + pageResult + "?filter="+ encodeURIComponent(row2.month +" "+ row.year) + encodedQuery; a = $("") - .attr("href", href) + .attr("href", percSafeUrl(href)) .text(linkText); } diff --git a/delivery/common/js/views/PercBlogPostView.js b/delivery/common/js/views/PercBlogPostView.js index 1dc6d58cb5..ab5c8b28d7 100644 --- a/delivery/common/js/views/PercBlogPostView.js +++ b/delivery/common/js/views/PercBlogPostView.js @@ -138,19 +138,19 @@ // Tags $('.perc-blog-post-tag-container').find('a').each(function(){ - var tag = ($(this).html().trim()).replace(",", ""); + var tag = ($(this).text().trim()).replace(",", ""); var jsonQuery = {'criteria':["perc:tags LIKE '" + tag + "'"]}; var encodedQuery = "&query=" + encodeURIComponent(JSON.stringify(jsonQuery)); - $(this).attr("href", blogIndexPage + "?filter="+ tag + encodedQuery); + $(this).attr("href", blogIndexPage + "?filter="+ encodeURIComponent(tag) + encodedQuery); }); // Categories $('.perc-blog-post-category-container').find('a').each(function(){ var categoryPath = $(this).attr('data-categories'); - var category = ($(this).html().trim()).replace(",", ""); + var category = ($(this).text().trim()).replace(",", ""); var jsonQuery = {'criteria':["perc:category LIKE '" + categoryPath + "'"]}; var encodedQuery = "&query=" + encodeURIComponent(JSON.stringify(jsonQuery)); - $(this).attr("href", blogIndexPage + "?filter="+ category + encodedQuery); + $(this).attr("href", blogIndexPage + "?filter="+ encodeURIComponent(category) + encodedQuery); }); }); } diff --git a/delivery/common/js/views/PercCategoryListView.js b/delivery/common/js/views/PercCategoryListView.js index 2d8eedd971..aab3cf97cf 100644 --- a/delivery/common/js/views/PercCategoryListView.js +++ b/delivery/common/js/views/PercCategoryListView.js @@ -40,6 +40,12 @@ var isPreviewMode; var baseURL; var strJSON; var nRow; + function percSafeUrl(url) + { + var u = String(url); + if (/^\s*(?:javascript|vbscript|data)\s*:/i.test(u)) {return "#";} + return u; + } function updateCategoryList() { $(".perc-category-list").each(function(){ @@ -185,15 +191,15 @@ var isPreviewMode; if(isEditMode === "true" || "undefined" === typeof (pageResult) || "" === pageResult ){ href = "#"; }else{ - href = baseURL + pageResult + "?filter=" + node.category + encodedQuery; + href = baseURL + pageResult + "?filter=" + encodeURIComponent(node.category) + encodedQuery; } var a = $("") - .attr("href", href) + .attr("href", percSafeUrl(href)) .attr("data-count", countTotal) .attr("title", nodeStr) .addClass("perc-node") - .append(nodeStr); + .text(nodeStr); return $("
  • ") .addClass("perc-category-element") diff --git a/delivery/common/js/views/PercMostReadBlogPostsView.js b/delivery/common/js/views/PercMostReadBlogPostsView.js index 05ca9e1301..f93d460363 100644 --- a/delivery/common/js/views/PercMostReadBlogPostsView.js +++ b/delivery/common/js/views/PercMostReadBlogPostsView.js @@ -61,10 +61,18 @@ }); } + function safeHeadingElem(elemName, defaultName) + { + if ("string" === typeof (elemName) && /^h[1-6]$/i.test(elemName)) { + return elemName.toLowerCase(); + } + return defaultName; + } + function createTitleHtml(settings) { var title = ""; - var elemName = settings.headingStyle || "h2"; + var elemName = safeHeadingElem(settings.headingStyle, "h2"); if (settings.listTitle) { title = $("
    ") .append($("<" + elemName + "/>") @@ -81,7 +89,7 @@ */ function createEntryHtml(settings, entry) { - var headingElem = settings.headingStyle || 'h3'; + var headingElem = safeHeadingElem(settings.headingStyle, 'h3'); var pagePath = entry.folder + entry.name; var promotedPaths = settings.promotedPagePaths || ""; promotedPaths = promotedPaths.split(";"); diff --git a/delivery/common/js/views/PercRegistrationView.js b/delivery/common/js/views/PercRegistrationView.js index 27190e7bf6..6a5a71bec6 100644 --- a/delivery/common/js/views/PercRegistrationView.js +++ b/delivery/common/js/views/PercRegistrationView.js @@ -33,6 +33,16 @@ $.PercRegistrationView = { init : init }; + /** + * Returns the given url if it uses a safe scheme, otherwise returns the site root. + */ + function percSafeUrl(url) { + var u = String(url); + if (/^\s*(?:javascript|vbscript|data)\s*:/i.test(u)) { + return "/"; + } + return u; + } /** * Initialize and configure each instance of registration widget in the page. */ @@ -69,7 +79,7 @@ if(!redirectUrl || "" === redirectUrl) { redirectUrl = "/"; } - window.location.href=redirectUrl; + window.location.href=percSafeUrl(redirectUrl); } else { $(".perc-reg-confirmation-message").text(data.message); @@ -257,7 +267,7 @@ if ($.param.querystring()) { params = '?' + $.param.querystring(); } - window.location = confirmation_page + params; + window.location = percSafeUrl(confirmation_page + params); } } else diff --git a/delivery/common/js/views/PercRssView.js b/delivery/common/js/views/PercRssView.js index cb44461d90..dff011f8d5 100644 --- a/delivery/common/js/views/PercRssView.js +++ b/delivery/common/js/views/PercRssView.js @@ -24,6 +24,27 @@ $.PercRssView = { updateRssFeed : updateRssFeed }; + + //Escapes a value so it can be safely used as html text or in an attribute value + function escapeHtml(value) + { + var s = ("undefined" === typeof (value) || null === value) ? "" : String(value); + return $('
    ').text(s).html().replace(/"/g, '"'); + } + + //Only allows simple element names, falls back to the given default + function safeElem(name, defaultName) + { + return ("string" === typeof (name) && /^[a-zA-Z][a-zA-Z0-9]*$/.test(name)) ? name : defaultName; + } + + //Blocks script bearing urls + function safeUrl(url) + { + var u = ("undefined" === typeof (url) || null === url) ? "" : String(url); + if (/^\s*(?:javascript|vbscript|data)\s*:/i.test(u)) {return "#";} + return escapeHtml(u); + } function updateRssFeed() { @@ -39,9 +60,9 @@ { var feedTitleId = queryString.feedId +"_title"; currentFeedWidget.attr('aria-labelledby', feedTitleId); - var titleElement = queryString.titleElement; - var feedTitle = '<' + titleElement + ' id="' + feedTitleId + '" '; - feedTitle += ' title="' + feed.title + '" class="perc-feed-title"> ' + feed.title + ' '; + var titleElement = safeElem(queryString.titleElement, "h2"); + var feedTitle = '<' + titleElement + ' id="' + escapeHtml(feedTitleId) + '" '; + feedTitle += ' title="' + escapeHtml(feed.title) + '" class="perc-feed-title"> ' + escapeHtml(feed.title) + ' '; currentFeedWidget.append(feedTitle); } else if (null !== feed.title && "undefined" !== typeof (feed.title)) { currentFeedWidget.attr('aria-label', feed.title); @@ -71,11 +92,11 @@ function renderFeedItems(feedItems, queryString, itemLimit) { var feedItem = ''; var itemAriaRole = queryString.itemAriaRole; - var wrappingElement = queryString.wrappingElement; - var itemElement = queryString.itemElement; - var itemTitleElement = queryString.itemTitleElement; - var itemDateElement = queryString.itemDateElement; - var itemDescriptionElement = queryString.itemDescriptionElement; + var wrappingElement = safeElem(queryString.wrappingElement, 'none'); + var itemElement = safeElem(queryString.itemElement, 'div'); + var itemTitleElement = safeElem(queryString.itemTitleElement, 'h3'); + var itemDateElement = safeElem(queryString.itemDateElement, 'p'); + var itemDescriptionElement = safeElem(queryString.itemDescriptionElement, 'p'); var itemIdPrefix = queryString.feedId +"_item_"; var wrappingElemAriaLabel = queryString.wrappingElemAriaLabel; @@ -87,7 +108,7 @@ feedItem += ' role="list" '; } if (null !== wrappingElemAriaLabel && "undefined" !== typeof (wrappingElemAriaLabel) && ("" !== wrappingElemAriaLabel)) { - feedItem += ' aria-label="' + wrappingElemAriaLabel + '" >'; + feedItem += ' aria-label="' + escapeHtml(wrappingElemAriaLabel) + '" >'; }else { feedItem += ' >'; } @@ -101,11 +122,11 @@ if(queryString.showItemTitle && null !== item.title) { - label='aria-labelledby="' + title_id + '"'; + label='aria-labelledby="' + escapeHtml(title_id) + '"'; } else { - label='aria-label="' + item.title + '"'; + label='aria-label="' + escapeHtml(item.title) + '"'; } feedItem += '<' +itemElement+' '; @@ -114,12 +135,12 @@ feedItem += 'role="listitem" '; } - feedItem += 'id="'+id+'" class="perc-feed-item"' + label + '>'; + feedItem += 'id="'+escapeHtml(id)+'" class="perc-feed-item"' + label + '>'; if(queryString.showItemTitle && item.title) { - feedItem += '<'+itemTitleElement+' id="'+title_id+'" class="perc-feed-item-title">' + item.title + ' '; + feedItem += '<'+itemTitleElement+' id="'+escapeHtml(title_id)+'" class="perc-feed-item-title">' + escapeHtml(item.title) + ' '; } if(queryString.showItemDate && item.updated) @@ -132,26 +153,26 @@ formattedDate = formattedDate.replace('hh', date.getHours()); formattedDate = formattedDate.replace('nn', date.getMinutes()); - feedItem += '<'+itemDateElement+' class="perc-feed-item-date">' + formattedDate + ''; + feedItem += '<'+itemDateElement+' class="perc-feed-item-date">' + escapeHtml(formattedDate) + ''; } if(queryString.showItemDescription && item.description) { - var description = $("<"+itemDescriptionElement+">").html(item.description); + var description = $("<"+itemDescriptionElement+">").text(item.description); if(queryString.itemRemoveHtml) { - description.html(description.text()); + description.text(description.text()); } if(!queryString.itemDescriptionEmpty) { //Truncate description - description.html(description.html().substring(0, queryString.itemDescriptionLength)); + description.text(description.text().substring(0, queryString.itemDescriptionLength)); //If there are more description if('' !== item.description.substring(queryString.itemDescriptionLength + 1)) { - description.html(description.html().trim() + "..."); + description.text(description.text().trim() + "..."); } } diff --git a/delivery/common/js/views/PercTagListView.js b/delivery/common/js/views/PercTagListView.js index 82512dd2e3..82754c7cdf 100644 --- a/delivery/common/js/views/PercTagListView.js +++ b/delivery/common/js/views/PercTagListView.js @@ -112,10 +112,10 @@ var query = JSON.parse( strJSON ); query.criteria.push("perc:tags = '" + tagEntry.tagName + "'"); var encodedQuery = "&query=" + encodeURIComponent(JSON.stringify(query)); - newListElem.find("a").attr("href", baseURL + pageResult + "?filter="+tagEntry.tagName + encodedQuery).html(linkText); + newListElem.find("a").attr("href", baseURL + pageResult + "?filter="+encodeURIComponent(tagEntry.tagName) + encodedQuery).text(linkText); } else{ - newListElem.find("a").html(linkText); + newListElem.find("a").text(linkText); } listRoot.append(newListElem); }