6

Lets say i have the following script in a html template of a flask application

<script type="text/javascript">
  console.log('{{ url_for('root', arg1='hello', arg2='world') }}')
<script>

and i have a flask endpoint root()

@app.route('/', methods=['GET'])
def root():
  print(print(request.args))

when i call my page the internal script is rendered to

console.log('/api/art_structure?arg2=world&amp;arg1=hello')

When I call this url my request.args dict is:

ImmutableMultiDict([('amp;arg1', 'hello'), ('arg2', 'world')])

Which is not correct since the key of arg1 is wrong. Any clues how I can prevent jinja2 from converting & to &amp;?

2 Answers 2

13

The ampersand

The correct URL is /api/art_structure?arg2=world&arg1=hello.

The problem with the above URL is that ampersand (&) cannot be written directly in HTML, because the ampersand is used for entity references. For example, to write < character in HTML, which is not a tag start, one can write &lt;. Because of that, to write &, one should escape it, i.e. write it as &amp;.

Jinja2 templating engine does that by default. So, you can dump the contents of any string variable even if it contains special characters and they will be correctly escaped, e.g. & will become &amp; as in your case.

How does that work?

So, if you actually put this in your jinja template: <a href="{{ url_for('root', arg1='hello', arg2='world') }}">link</a>, it would write the following HTML code: <a href="/api/art_structure?arg2=world&amp;arg1=hello">link</a>, and if you clicked on the link in the browser, it would correctly replace &amp; with & and open /api/art_structure?arg2=world&arg1=hello.

(note, however, that writing plain & as it is into HTML may sometimes also work, because browsers may guess how to fix the error)

So why does it not simply work in this case?

Because you are generating JavaScript and not HTML (you are generating code within <script> and </script>. In JavaScript, & is fine and should not be escaped, so you are free to write it as it is. To tell Jinja that you do not want it to escape the string, you can use the safe filter, i.e. {{ url_for('root', arg1='hello', arg2='world') | safe}}.

3

Try using safe

Ex:

console.log("{{ url_for('root', arg1='hello', arg2='world') | safe}}")

MoreInfo

5
  • Although a MoreInfo link is somewhat useful, it would be better to explain in the answer itself what &amp; is, why jinja creates it, why it is wrong within <script> block and what safe does.
    – zvone
    Commented Aug 10, 2018 at 10:01
  • Don't use | safe with url_for as it potentially allows for XSS: stackoverflow.com/a/39246205/3812928 Commented Mar 3, 2023 at 10:38
  • @NotoriousPyro That link says nothing about url_for, or about escaping JavaScript code. It is about escaping user input (or other external data) when generating HTML (using JavaScript, but that is not the point). url_for is a flask function and I don't see how it could be hijacked for XSS.
    – zvone
    Commented Mar 17, 2023 at 6:57
  • Because if you use | safe withurl_for you will end up stopping un-escaping that url_for does. Since url_for can end up with user-inputted data, it is possible that it will end up with XSS. E.g. you allow the user to submit a form and you generate a url on the target page using their form data but you escaped url_for... Commented Mar 17, 2023 at 11:23
  • You should not need to use | safe with url_for. If you need to use it inside a script, you should be careful what data is allowed to make it into that call to url_for. Commented Mar 17, 2023 at 11:23

Not the answer you're looking for? Browse other questions tagged or ask your own question.