Skip to content

Commit 406d423

Browse files
committed
Remove inline HTML from code example
Close #2447
1 parent 195a12d commit 406d423

File tree

2 files changed

+31
-97
lines changed

2 files changed

+31
-97
lines changed

docs/python/tutorial-django.md

Lines changed: 13 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,11 @@ During your work with Django or any other library, you may want to examine the c
334334
335335
## Use a template to render a page
336336
337-
The app you've created so far in this tutorial generates only plain text web pages from Python code. Although it's possible to generate HTML directly in code, developers typically avoid such a practice because it's vulnerable to cross-site scripting (XSS) attacks. Instead, developers separate HTML markup from the code-generated data that gets inserted into that markup. **Templates** are a common approach to achieve this separation.
337+
The app you've created so far in this tutorial generates only plain text web pages from Python code. Although it's possible to generate HTML directly in code, developers avoid such a practice because it opens the app to [cross-site scripting (XSS) attacks](https://en.wikipedia.org/wiki/Cross-site_scripting). In the `hello_there` function of this tutorial, for example, one might think to format the output in code with something like `content = "<h1>Hello there, " + clean_name + "!</h1>`, where the result in `content` is given directly to a browser. This opening allows an attacker to place malicious HTML, including JavaScript code, in the URL that ends up in `clean_name` and thus ends up being run in the browser.
338338
339-
A template is an HTML file that contains placeholders for values that the code provides at run time. The Django templating engine takes care of making the substitutions when rendering the page. The code, therefore, concerns itself only with data values and the template concerns itself only with markup. Django templates provide flexible options such as template inheritance, which allows you to define a base page with common markup and then build upon that base with page-specific additions.
339+
A much better practice is to keep HTML out of your code entirely by using **templates**, so that your code is concerned only with data values and not with rendering.
340+
341+
In Django, a template is an HTML file that contains placeholders for values that the code provides at run time. The Django templating engine then takes care of making the substitutions when rendering the page, and provides automatic escaping to prevent XSS attacks (that is, if you tried using HTML in a data value, you would see the HTML rendered only as plain text). The code, therefore, concerns itself only with data values and the template concerns itself only with markup. Django templates provide flexible options such as template inheritance, which allows you to define a base page with common markup and then build upon that base with page-specific additions.
340342
341343
In this section, you start by creating a single page using a template. In subsequent sections, you configure the app to serve static files and then create multiple pages to the app that each contains a nav bar from a base template. Django templates also support control flow and iteration, as you see later in this tutorial in the context of template debugging.
342344
@@ -348,17 +350,17 @@ In this section, you start by creating a single page using a template. In subseq
348350
349351
1. Inside the `hello` folder, create a folder named `templates`, and then another subfolder named `hello` to match the app name (this two-tiered folder structure is typical Django convention).
350352
351-
1. In the `templates/hello` folder, create a file named `hello_there.html` with the contents below. This template contains two placeholders named "title" and "content", which are delineated by pairs of curly braces, `\{{` and `}}`.
353+
1. In the `templates/hello` folder, create a file named `hello_there.html` with the contents below. This template contains two placeholders for data values named "name", and "date", which are delineated by pairs of curly braces, `\{{` and `}}`. All other invariant text is part of the template, along with formatting markup (such as `<strong>`). As you can see, template placeholders can also include formatting, the expressions after the pipe `|` symbols, in this case using Django's built-in [date filter](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#date) and [time filter](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#time). The code, then needs only to pass the datetime *value* rather than a pre-formatted string:
352354
353355
```html
354356
<!DOCTYPE html>
355357
<html>
356358
<head>
357359
<meta charset="utf-8" />
358-
<title>\{{ title }}</title>
360+
<title>Hello, Django</title>
359361
</head>
360362
<body>
361-
\{{ content }}
363+
<strong>Hello there, \{{ name }}!</strong> It's \{{ date | date:'l, d F, Y' }} at \{{ date | time:'H:i:s' }}
362364
</body>
363365
</html>
364366
```
@@ -369,48 +371,7 @@ In this section, you start by creating a single page using a template. In subseq
369371
from django.shortcuts import render
370372
```
371373
372-
1. Also in `views.py`, modify the `hello_there` function to use `django.shortcuts.render` method to load a template and to provide the *template context* (the set of variables for use within the template). `render_template` assumes that the first argument is relative to the `templates` folder. Typically, developers name the templates the same as the functions that use them, but matching names are not required because you always refer to the exact filename in your code.
373-
374-
```python
375-
# You can also remove the import re statement that's no longer used
376-
377-
def hello_there(request, name):
378-
now = datetime.now()
379-
formatted_now = now.strftime("%A, %d %B, %Y at %X")
380-
381-
# BAD CODE! Avoid inline HTML for security reason, plus templates automatically escape HTML content.
382-
content = "<strong>Hello there, " + name + "!</strong> It's " + formatted_now
383-
384-
return render(
385-
request,
386-
'hello/hello_there.html',
387-
{
388-
'title': 'Hello, Django',
389-
'content': content,
390-
}
391-
)
392-
```
393-
394-
1. Start the program (inside or outside of the debugger, using `kb(workbench.action.debug.run)`), navigate to a /hello/name URL, and observe the results. Notice that the inline HTML, if you happen to write bad code like this, doesn't get rendered as HTML because the templating engine automatically escapes values used in placeholders. Automatic escaping prevent accidental vulnerabilities to injection attacks: developers often gather input from one page, or the URL, and use it as a value in another page through a template placeholder. Escaping also serves as a reminder that it's again best to keep HTML out of the code entirely.
395-
396-
For this reason, modify the template and view function as follows to make each piece of content more specifically. While you're at it, move more of the text (including the title) into the template, along with the date/time formatting (using Django's built-in [date filter](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#date) and [time filter](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#time)):
397-
398-
Replace the contents of `templates/hello/hello_there.html` with the following:
399-
400-
```html
401-
<!DOCTYPE html>
402-
<html>
403-
<head>
404-
<meta charset="utf-8" />
405-
<title>Hello, Django</title>
406-
</head>
407-
<body>
408-
<strong>Hello there, \{{ name }}!</strong> It's \{{ date | date:'l, d F, Y' }} at \{{ date | time:'H:i:s' }}
409-
</body>
410-
</html>
411-
```
412-
413-
Replace the `hello_there` function in `views.py` with the following:
374+
1. Also in `views.py`, modify the `hello_there` function to use `django.shortcuts.render` method to load a template and to provide the *template context*. The context is simply the set of variables for use within the template. The `render` function takes the request object, followed by the path to to the template *relative to the `templates` folder*, then the context object. (Developers typically name the templates the same as the functions that use them, but matching names are not required because you always refer to the exact filename in your code.)
414375
415376
```python
416377
def hello_there(request, name):
@@ -424,7 +385,11 @@ In this section, you start by creating a single page using a template. In subseq
424385
)
425386
```
426387
427-
1. Run the app again and navigate to a /hello/name URL to observe the expected result, then stop the app when you're done.
388+
You can see that the code is now much simpler, and concerned only with data values, because the markup and formatting is all contained in the template.
389+
390+
1. Start the program (inside or outside of the debugger, using `kb(workbench.action.debug.run)`), navigate to a /hello/name URL, and observe the results.
391+
392+
1. Also try navigating to a /hello/name URL using a name like `<a%20value%20that%20could%20be%20HTML>` to see Django's automatic escaping at work. The "name" value shows up as plain text in the browser rather than as rendering an actual element.
428393
429394
## Serve static files
430395

docs/python/tutorial-flask.md

Lines changed: 18 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -284,59 +284,18 @@ During your work with Flask or any other library, you may want to examine the co
284284
285285
## Use a template to render a page
286286
287-
The app you've created so far in this tutorial generates only plain text web pages from Python code. Although it's possible to generate HTML directly in code, developers typically avoid such a practice because it's vulnerable to cross-site scripting (XSS) attacks. Instead, developers separate HTML markup from the code-generated data that gets inserted into that markup. **Templates** are a common approach to achieve this separation.
287+
The app you've created so far in this tutorial generates only plain text web pages from Python code. Although it's possible to generate HTML directly in code, developers avoid such a practice because it opens the app to [cross-site scripting (XSS) attacks](http://flask.pocoo.org/docs/1.0/security/#cross-site-scripting-xss). In the `hello_there` function of this tutorial, for example, one might think to format the output in code with something like `content = "<h1>Hello there, " + clean_name + "!</h1>`, where the result in `content` is given directly to a browser. This opening allows an attacker to place malicious HTML, including JavaScript code, in the URL that ends up in `clean_name` and thus ends up being run in the browser.
288+
289+
A much better practice is to keep HTML out of your code entirely by using **templates**, so that your code is concerned only with data values and not with rendering.
288290
289291
- A template is an HTML file that contains placeholders for values that the code provides at run time. The templating engine takes care of making the substitutions when rendering the page. The code, therefore, concerns itself only with data values and the template concerns itself only with markup.
290-
- The default templating engine for Flask is [Jinja](http://jinja.pocoo.org/), which is installed automatically when you install Flask. This engine provides flexible options including template inheritance. With inheritance, you can define a base page with common markup and then build upon that base with page-specific additions.
292+
- The default templating engine for Flask is [Jinja](http://jinja.pocoo.org/), which is installed automatically when you install Flask. This engine provides flexible options including automatic escapting (to prevent XSS attacks) and template inheritance. With inheritance, you can define a base page with common markup and then build upon that base with page-specific additions.
291293
292294
In this section, you create a single page using a template. In the sections that follow, you configure the app to serve static files, and then create multiple pages to the app that each contains a nav bar from a base template.
293295
294296
1. Inside the `hello_flask` folder, create a folder named `templates`, which is where Flask looks for templates by default.
295297
296-
1. In the `templates` folder, create a file named `hello_there.html` with the contents below. This template contains two placeholders named "title" and "content", which are delineated by pairs of curly braces, `\{{` and `}}`.
297-
298-
```html
299-
<!DOCTYPE html>
300-
<html>
301-
<head>
302-
<meta charset="utf-8" />
303-
<title>\{{ title }}</title>
304-
</head>
305-
<body>
306-
\{{ content }}
307-
</body>
308-
</html>
309-
```
310-
311-
1. In `app.py`, import Flask's `render_template` function near the top of the file:
312-
313-
```python
314-
from flask import render_template
315-
```
316-
317-
1. Also in `app.py`, modify the `hello_there` function to use `render_template` to load a template and apply the named values. `render_template` assumes that the first argument is relative to the `templates` folder. Typically, developers name the templates the same as the functions that use them, but matching names are not required because you always refer to the exact filename in your code.
318-
319-
```python
320-
@app.route("/hello/<name>")
321-
def hello_there(name):
322-
now = datetime.now()
323-
formatted_now = now.strftime("%A, %d %B, %Y at %X")
324-
325-
# BAD CODE! Avoid inline HTML for security reason, plus templates automatically escape HTML content.
326-
content = "<strong>Hello there, " + name + "!</strong> It's " + formatted_now
327-
328-
return render_template(
329-
"hello_there.html",
330-
title="Hello, Flask",
331-
content=content
332-
)
333-
```
334-
335-
1. Start the program (inside or outside of the debugger, using `kb(workbench.action.debug.run)`), navigate to a /hello/name URL, and observe the results. Notice that the inline HTML, if you happen to write bad code like this, doesn't get rendered as HTML because the templating engine automatically escapes values used in placeholders. Automatic escaping prevent accidental vulnerabilities to injection attacks: developers often gather input from one page, or the URL, and use it as a value in another page through a template placeholder. Escaping also serves as a reminder that it's again best to keep HTML out of the code entirely.
336-
337-
For this reason, modify the template and view function as follows to identify each piece of content more specifically. While you're at it, also move more of the text (including the title) and formatting concerns into the template and also handle the case where no name is given:
338-
339-
In `templates/hello_there.html`:
298+
1. In the `templates` folder, create a file named `hello_there.html` with the contents below. This template contains two placeholders named "name" and "date", which are delineated by pairs of curly braces, `\{{` and `}}`. As you can see, you can also include formatting code in the template directly:
340299
341300
```html
342301
<!DOCTYPE html>
@@ -355,7 +314,15 @@ In this section, you create a single page using a template. In the sections that
355314
</html>
356315
```
357316
358-
In `app.py`, change how you invoke the template and add a route to recognize the case without a name:
317+
> **Tip**: Flask developers often use the [flask-babel](https://pythonhosted.org/Flask-Babel/) extension for date formatting, rather than `strftime`, as flask-babel takes locales and timezones into consideration.
318+
319+
1. In `app.py`, import Flask's `render_template` function near the top of the file:
320+
321+
```python
322+
from flask import render_template
323+
```
324+
325+
1. Also in `app.py`, modify the `hello_there` function to use `render_template` to load a template and apply the named values (and add a route to recognize the case without a name). `render_template` assumes that the first argument is relative to the `templates` folder. Typically, developers name the templates the same as the functions that use them, but matching names are not required because you always refer to the exact filename in your code.
359326
360327
```python
361328
@app.route("/hello/")
@@ -368,9 +335,11 @@ In this section, you create a single page using a template. In the sections that
368335
)
369336
```
370337
371-
> **Tip**: Flask developers often use the [flask-babel](https://pythonhosted.org/Flask-Babel/) extension for date formatting, rather than `strftime`, as flask-babel takes locales and timezones into consideration.
338+
You can see that the code is now much simpler, and concerned only with data values, because the markup and formatting is all contained in the template.
339+
340+
1. Start the program (inside or outside of the debugger, using `kb(workbench.action.debug.run)`), navigate to a /hello/name URL, and observe the results.
372341
373-
1. Run the app again and navigate to a /hello/name URL to observe the expected result, then stop the app when you're done.
342+
1. Also try navigating to a /hello/name URL using a name like `<a%20value%20that%20could%20be%20HTML>` to see Flask's automatic escaping at work. The "name" value shows up as plain text in the browser rather than as rendering an actual element.
374343
375344
## Serve static files
376345

0 commit comments

Comments
 (0)