Skip to content

Commit

Permalink
Merge pull request from GHSA-qhqx-5j25-rv48
Browse files Browse the repository at this point in the history
  • Loading branch information
stsewd committed Jan 15, 2024
1 parent 8f4d111 commit 7680399
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 60 deletions.
5 changes: 5 additions & 0 deletions docs/user/api/v2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,11 @@ Embed
Retrieve HTML-formatted content from documentation page or section.

.. warning::

The content will be returned as is, without any sanitization or escaping.
You should not include content from arbitrary projects, or projects you do not trust.

**Example request**:

.. prompt:: bash $
Expand Down
4 changes: 4 additions & 0 deletions docs/user/api/v3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1927,6 +1927,10 @@ Embed
Passing ``?doctool=`` and ``?doctoolversion=`` may improve the response,
since the endpoint will know more about the exact structure of the HTML and can make better decisions.

.. warning::

The content will be returned as is, without any sanitization or escaping.
You should not include content from arbitrary projects, or projects you do not trust.

Additional APIs
---------------
Expand Down
31 changes: 12 additions & 19 deletions docs/user/server-side-search/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,18 @@ API v3
:>json string domain: Canonical domain of the resulting page
:>json string path: Path to the resulting page
:>json object highlights: An object containing a list of substrings with matching terms.
Note that the text is HTML escaped with the matching terms inside a <span> tag.
Note that the text is HTML escaped with the matching terms inside a ``<span>`` tag.
:>json object blocks:

A list of block objects containing search results from the page.
Currently, there are two types of blocks:
Currently, there is one type of block:

- section: A page section with a linkable anchor (``id`` attribute).
- domain: A Sphinx :doc:`domain <sphinx:usage/restructuredtext/domains>`
with a linkable anchor (``id`` attribute).

.. warning::

Except for highlights, any other content is not HTML escaped,
you shouldn't include it in your page without escaping it first.

**Example request**:

Expand Down Expand Up @@ -198,16 +200,18 @@ API v2 (deprecated)
:>json string domain: Canonical domain of the resulting page
:>json string path: Path to the resulting page
:>json object highlights: An object containing a list of substrings with matching terms.
Note that the text is HTML escaped with the matching terms inside a <span> tag.
Note that the text is HTML escaped with the matching terms inside a ``<span>`` tag.
:>json object blocks:

A list of block objects containing search results from the page.
Currently, there are two types of blocks:
Currently, there is one type of block:

- section: A page section with a linkable anchor (``id`` attribute).
- domain: A Sphinx :doc:`domain <sphinx:usage/restructuredtext/domains>`
with a linkable anchor (``id`` attribute).

.. warning::

Except for highlights, any other content is not HTML escaped,
you shouldn't include it in your page without escaping it first.

**Example request**:

Expand Down Expand Up @@ -265,17 +269,6 @@ API v2 (deprecated)
"You can <span>search</span> all projects at https:&#x2F;&#x2F;readthedocs.org&#x2F;<span>search</span>&#x2F"
]
}
},
{
"type": "domain",
"role": "http:get",
"name": "/_/api/v2/search/",
"id": "get--_-api-v2-search-",
"content": "Retrieve search results for docs",
"highlights": {
"name": [""],
"content": ["Retrieve <span>search</span> results for docs"]
}
}
]
},
Expand Down
86 changes: 46 additions & 40 deletions readthedocs/core/static-src/core/js/doc-embed/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,50 @@
*/

const rtddata = require('./rtd-data');
const xss = require('xss/lib/index');
const { createDomNode, domReady } = require("./utils");
const MAX_RESULT_PER_SECTION = 3;
const MAX_SUBSTRING_LIMIT = 100;

/**
* Mark a string as safe to be used as HTML in setNodeContent.
* @class
*/
function SafeHtmlString(value) {
this.value = value;
this.isSafe = true;
}

/**
* Create a SafeHtmlString instance from a string.
*
* @param {String} value
*/
function markAsSafe(value) {
return new SafeHtmlString(value);
}

/**
* Set the content of an element as text or HTML.
*
* @param {Element} element
* @param {String|SafeHtmlString} content
*/
function setElementContent(element, content) {
if (content.isSafe) {
element.innerHTML = content.value;
} else {
element.innerText = content;
}
}


/*
* Search query override for hitting our local API instead of the standard
* Sphinx indexer. This will fall back to the standard indexer on an API
* failure,
* failure.
*
* Except for highlights, which are HTML encoded, with `<span>` tags surrounding the highlight,
* all other data shouldn't be considered safe to be used as HTML.
*/
function attach_elastic_search_query_sphinx(data) {
var project = data.project;
Expand Down Expand Up @@ -54,20 +88,22 @@ function attach_elastic_search_query_sphinx(data) {
*
* ...
*
* @param {String} title.
* @param {String|SafeHtmlString} title.
* @param {String} link.
* @param {Array} contents.
* @param {String[]|SafeHtmlString[]} contents.
*/
// Watch our for XSS in title and contents!
const buildSection = function (title, link, contents) {
var div_title = document.createElement("div");
var a_element = document.createElement("a");
a_element.href = link;
a_element.innerHTML = title;
setElementContent(a_element, title);

div_title.appendChild(a_element);
let elements = [div_title];
for (let content of contents) {
let div_content = document.createElement("div");
div_content.innerHTML = content;
setElementContent(div_content, content);
elements.push(div_content);
}
return elements;
Expand All @@ -94,13 +130,13 @@ function attach_elastic_search_query_sphinx(data) {
var title = result.title;
// if highlighted title is present, use that.
if (result.highlights.title.length) {
title = xss(result.highlights.title[0]);
title = markAsSafe(result.highlights.title[0]);
}

var link = result.path + "?highlight=" + encodeURIComponent(query);

let item = createDomNode('a', {href: link});
item.innerHTML = title;
setElementContent(item, title);
for (let element of item.getElementsByTagName('span')) {
element.className = 'highlighted';
}
Expand All @@ -126,7 +162,7 @@ function attach_elastic_search_query_sphinx(data) {
var section_content = [section.content.substr(0, MAX_SUBSTRING_LIMIT) + " ..."];

if (section.highlights.title.length) {
section_subtitle = xss(section.highlights.title[0]);
section_subtitle = markAsSafe(section.highlights.title[0]);
}

if (section.highlights.content.length) {
Expand All @@ -137,7 +173,7 @@ function attach_elastic_search_query_sphinx(data) {
k < content.length && k < MAX_RESULT_PER_SECTION;
k += 1
) {
section_content.push("... " + xss(content[k]) + " ...");
section_content.push(markAsSafe("... " + content[k] + " ..."));
}
}
let sections = buildSection(
Expand All @@ -148,36 +184,6 @@ function attach_elastic_search_query_sphinx(data) {
sections.forEach(element => { contents.appendChild(element); });
}

// if the result is a sphinx domain object
if (current_block.type === "domain") {
var domain = current_block;
var domain_role_name = domain.role;
var domain_subtitle_link = link + "#" + domain.id;
var domain_name = domain.name;
var domain_content = "";

if (domain.content !== "") {
domain_content = domain.content.substr(0, MAX_SUBSTRING_LIMIT) + " ...";
}

if (domain.highlights.content.length) {
domain_content = "... " + xss(domain.highlights.content[0]) + " ...";
}

if (domain.highlights.name.length) {
domain_name = xss(domain.highlights.name[0]);
}

var domain_subtitle = "[" + domain_role_name + "]: " + domain_name;

let sections = buildSection(
domain_subtitle,
domain_subtitle_link,
[domain_content]
);
sections.forEach(element => { contents.appendChild(element); });
}

for (let element of contents.getElementsByTagName('span')) {
element.className = 'highlighted';
}
Expand Down
2 changes: 1 addition & 1 deletion readthedocs/core/static/core/js/readthedocs-doc-embed.js

Large diffs are not rendered by default.

0 comments on commit 7680399

Please sign in to comment.