diff --git a/content/developer/reference/backend/security.rst b/content/developer/reference/backend/security.rst index 59714e722..5daef4abb 100644 --- a/content/developer/reference/backend/security.rst +++ b/content/developer/reference/backend/security.rst @@ -353,6 +353,87 @@ While formatting the template differently would prevent such vulnerabilities. font-weight: bold; } +Creating safe content using :class:`~markupsafe.Markup` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +See the `official documentation `_ for +explanations, but the big advantage of +:class:`~markupsafe.Markup` is that it's a very rich type overrinding +:class:`str` operations to *automatically escape parameters*. + +This means that it's easy to create *safe* html snippets by using +:class:`~markupsafe.Markup` on a string literal and "formatting in" +user-provided (and thus potentially unsafe) content: + +.. code-block:: pycon + + >>> Markup('Hello ') + '' + Markup('Hello <foo>') + >>> Markup('Hello %s') % '' + Markup('Hello <foo>') + +though it is a very good thing, note that the effects can be odd at times: + +.. code-block:: pycon + + >>> Markup('').replace('>', 'x') + Markup('') + >>> Markup('').replace(Markup('>'), 'x') + Markup('>> Markup('', 'x') + Markup('>> Markup('', '&') + Markup('%s") % self.name # escape the name + else: + return self.name + + >>> record.name = "" + >>> escape(record.get_name()) + Markup("<R&D>") + >>> escape(record.get_name(True)) + Markup("<R&D>") # HTML is kept + +When generating HTML code, it is important to separate the structure (tags) from +the content (text). + +.. code-block:: pycon + + >>> Markup("

") + "Hello " + Markup("

") + Markup('

Hello <R&D>

') + >>> Markup("%s
%s") % ("", Markup("

Hello

")) + Markup('<R&D>

Hello

') + >>> escape("") + Markup('<R&D>') + >>> escape(_("List of Tasks on project %s: %s")) % ( + ... project.name, + ... Markup("
    %s
") % Markup().join([Markup("
  • %s
  • ") % t.name for t in project.task_ids]) + ... ) + Markup('Liste de tâches pour le projet <R&D>:
    • First <R&D> task
    ') + + >>> Markup("

    Foo %

    " % bar) # bad, bar is not escaped + >>> Markup("

    Foo %

    ") % bar # good, bar is escaped if text and kept if markup + + >>> link = Markup("
    %s") % self.name + >>> message = "Click %s" % link # bad, message is text and Markup did nothing + >>> message = escape("Click %s") % link # good, format two markup objects together + + >>> Markup(f"

    Foo {self.bar}

    ") # bad, bar is inserted before escaping + >>> Markup("

    Foo {bar}

    ").format(bar=self.bar) # good, sorry no fstring + Escaping vs Sanitizing ---------------------- @@ -380,10 +461,10 @@ variable contains *TEXT* and which contains *CODE*. # Escaping turns it into CODE, good! >>> code = html_escape(data) >>> code - '<R&D>' + Markup('<R&D>') # Now you can mix it with other code... - >>> self.message_post(body="%s" % code) + >>> self.website_description = Markup("%s") % code **Sanitizing** converts *CODE* to *SAFER CODE* (but not necessary *safe* code). It does not work on *TEXT*. Sanitizing is only necessary when *CODE* is @@ -398,11 +479,11 @@ expected. # Sanitizing without escaping is BROKEN: data is corrupted! >>> html_sanitize(data) - '' + Markup('') # Sanitizing *after* escaping is OK! >>> html_sanitize(code) - '

    <R&D>

    ' + Markup('

    <R&D>

    ') Sanitizing can break features, depending on whether the *CODE* is expected to contain patterns that are not safe. That's why `fields.Html` and @@ -414,11 +495,11 @@ likely it is to break things. .. code-block:: python - >>code = "

    Important Information

    " + >>> code = "

    Important Information

    " # this will remove the style, which may break features # but is necessary if the source is untrusted - >> html_sanitize(code, strip_classes=True) - '

    Important Information

    ' + >>> html_sanitize(code, strip_classes=True) + Markup('

    Important Information

    ') Evaluating content ------------------ diff --git a/content/developer/reference/frontend/qweb.rst b/content/developer/reference/frontend/qweb.rst index 96d6fec16..6b5b56572 100644 --- a/content/developer/reference/frontend/qweb.rst +++ b/content/developer/reference/frontend/qweb.rst @@ -372,58 +372,6 @@ templates: * :func:`~odoo.tools.pycompat.to_text` does not mark the content as safe, but will not strip that information from safe content. -Creating safe content using :class:`~markupsafe.Markup` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -See the official documentation for explanations, but the big advantage of -:class:`~markupsafe.Markup` is that it's a very rich type overrinding -:class:`str` operations to *automatically escape parameters*. - -This means that it's easy to create *safe* html snippets by using -:class:`~markupsafe.Markup` on a string literal and "formatting in" -user-provided (and thus potentially unsafe) content: - -.. code-block:: pycon - - >>> Markup('Hello ') + '' - Markup('Hello <foo>') - >>> Markup('Hello %s') % '' - Markup('Hello <foo>') - -though it is a very good thing, note that the effects can be odd at times: - -.. code-block:: pycon - - >>> Markup('').replace('>', 'x') - Markup('') - >>> Markup('').replace(Markup('>'), 'x') - Markup('>> Markup('', 'x') - Markup('>> Markup('', '&') - Markup('