[IMP] reference: update markup documentation
closes odoo/documentation#3612 Related: odoo/odoo#111850 Related: odoo/enterprise#36728 Signed-off-by: Martin Trigaux (mat) <mat@odoo.com>
This commit is contained in:
parent
afe0282420
commit
1e39949363
@ -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 <https://markupsafe.palletsprojects.com/>`_ 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('<em>Hello</em> ') + '<foo>'
|
||||
Markup('<em>Hello</em> <foo>')
|
||||
>>> Markup('<em>Hello</em> %s') % '<foo>'
|
||||
Markup('<em>Hello</em> <foo>')
|
||||
|
||||
though it is a very good thing, note that the effects can be odd at times:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Markup('<a>').replace('>', 'x')
|
||||
Markup('<a>')
|
||||
>>> Markup('<a>').replace(Markup('>'), 'x')
|
||||
Markup('<ax')
|
||||
>>> Markup('<a>').replace('>', 'x')
|
||||
Markup('<ax')
|
||||
>>> Markup('<a>').replace('>', '&')
|
||||
Markup('<a&')
|
||||
|
||||
.. tip:: Most of the content-safe APIs actually return a
|
||||
:class:`~markupsafe.Markup` with all that implies.
|
||||
|
||||
The :class:`~markupsafe.escape` method (and its
|
||||
alias :class:`~odoo.tools.misc.html_escape`) turns a `str` into
|
||||
a :class:`~markupsafe.Markup` and escapes its content. It will not escape the
|
||||
content of a :class:`~markupsafe.Markup` object.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def get_name(self, to_html=False):
|
||||
if to_html:
|
||||
return Markup("<strong>%s</strong>") % self.name # escape the name
|
||||
else:
|
||||
return self.name
|
||||
|
||||
>>> record.name = "<R&D>"
|
||||
>>> escape(record.get_name())
|
||||
Markup("<R&D>")
|
||||
>>> escape(record.get_name(True))
|
||||
Markup("<strong><R&D></strong>") # HTML is kept
|
||||
|
||||
When generating HTML code, it is important to separate the structure (tags) from
|
||||
the content (text).
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Markup("<p>") + "Hello <R&D>" + Markup("</p>")
|
||||
Markup('<p>Hello <R&D></p>')
|
||||
>>> Markup("%s <br/> %s") % ("<R&D>", Markup("<p>Hello</p>"))
|
||||
Markup('<R&D> <br/> <p>Hello</p>')
|
||||
>>> escape("<R&D>")
|
||||
Markup('<R&D>')
|
||||
>>> escape(_("List of Tasks on project %s: %s")) % (
|
||||
... project.name,
|
||||
... Markup("<ul>%s</ul>") % Markup().join([Markup("<li>%s</li>") % t.name for t in project.task_ids])
|
||||
... )
|
||||
Markup('Liste de tâches pour le projet <R&D>: <ul><li>First <R&D> task</li></ul>')
|
||||
|
||||
>>> Markup("<p>Foo %</p>" % bar) # bad, bar is not escaped
|
||||
>>> Markup("<p>Foo %</p>") % bar # good, bar is escaped if text and kept if markup
|
||||
|
||||
>>> link = Markup("<a>%s</a>") % 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"<p>Foo {self.bar}</p>") # bad, bar is inserted before escaping
|
||||
>>> Markup("<p>Foo {bar}</p>").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="<strong>%s</strong>" % code)
|
||||
>>> self.website_description = Markup("<strong>%s</strong>") % 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)
|
||||
'<p><R&D></p>'
|
||||
Markup('<p><R&D></p>')
|
||||
|
||||
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 = "<p class='text-warning'>Important Information</p>"
|
||||
>>> code = "<p class='text-warning'>Important Information</p>"
|
||||
# this will remove the style, which may break features
|
||||
# but is necessary if the source is untrusted
|
||||
>> html_sanitize(code, strip_classes=True)
|
||||
'<p>Important Information</p>'
|
||||
>>> html_sanitize(code, strip_classes=True)
|
||||
Markup('<p>Important Information</p>')
|
||||
|
||||
Evaluating content
|
||||
------------------
|
||||
|
@ -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('<em>Hello</em> ') + '<foo>'
|
||||
Markup('<em>Hello</em> <foo>')
|
||||
>>> Markup('<em>Hello</em> %s') % '<foo>'
|
||||
Markup('<em>Hello</em> <foo>')
|
||||
|
||||
though it is a very good thing, note that the effects can be odd at times:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> Markup('<a>').replace('>', 'x')
|
||||
Markup('<a>')
|
||||
>>> Markup('<a>').replace(Markup('>'), 'x')
|
||||
Markup('<ax')
|
||||
>>> Markup('<a>').replace('>', 'x')
|
||||
Markup('<ax')
|
||||
>>> Markup('<a>').replace('>', '&')
|
||||
Markup('<a&')
|
||||
|
||||
.. tip:: Most of the content-safe APIs actually return a
|
||||
:class:`~markupsafe.Markup` with all that implies.
|
||||
|
||||
Javascript
|
||||
----------
|
||||
|
||||
.. todo:: what APIs do we end up considering OK there?
|
||||
.. todo:: talk about vdom thingies?
|
||||
|
||||
.. warning::
|
||||
|
||||
Due to the lack of operator overriding, :js:class:`Markup` is a much more
|
||||
limited type than :class:`~markupsafe.Markup`.
|
||||
|
||||
Therefore it doesn't override methods either, and any operation involving
|
||||
:js:class:`Markup` will return a normal :js:class:`String` (and in reality
|
||||
not even that, but a "primitive string").
|
||||
|
||||
This means the fallback is safe, but it is easy to trigger double-escaping
|
||||
when working with :js:class:`Markup` objects.
|
||||
|
||||
forcing double-escaping
|
||||
-----------------------
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user