Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

wire.escape

import wire

wire exposes this as wire.escape, so import wire is enough and the names are called as wire.escape.*. import wire.escape reaches the same definitions directly.

Turning a value into something safe to write at a particular place in a page.

Escaping HTML is not one operation, it is five. The same string that is harmless between two tags will end a <script> early, or turn an href into a code execution, or break out of a stylesheet. Wire knows which of those places every interpolation sits in, because it compiles a real parsed tree rather than substituting into text, so it applies the escaping that place actually needs instead of one approximation everywhere.

Nothing here is optional or discoverable at render time. A template that wants unescaped output has to say so with the raw filter, or hand Wire a Safe value.

Constants

CONTEXT_TEXT

wire.escape.CONTEXT_TEXT = 'text'

Text between tags.

CONTEXT_ATTRIBUTE

wire.escape.CONTEXT_ATTRIBUTE = 'attribute'

An ordinary attribute value.

CONTEXT_URL

wire.escape.CONTEXT_URL = 'url'

An attribute the browser resolves as a URL.

CONTEXT_SCRIPT

wire.escape.CONTEXT_SCRIPT = 'script'

A place the browser reads as JavaScript: the body of a <script>, or the value of an on* handler attribute.

CONTEXT_STYLE

wire.escape.CONTEXT_STYLE = 'style'

The body of a <style> element.

URL_ATTRIBUTES

wire.escape.URL_ATTRIBUTES = [...]

The attributes whose value a browser resolves as a URL, and which therefore have to be checked for a dangerous scheme rather than merely escaped.

srcset and ping hold several URLs separated by commas or spaces and are checked entry by entry.

URL_LIST_ATTRIBUTES

wire.escape.URL_LIST_ATTRIBUTES = [...]

URL attributes holding a list of URLs rather than a single one.

DEFAULT_URL_SCHEMES

wire.escape.DEFAULT_URL_SCHEMES = [...]

The URL schemes Wire lets through by default.

A URL with no scheme at all is always allowed, which covers every relative link, every absolute path and every protocol-relative URL. A scheme outside this list is replaced with about:blank, because javascript: and vbscript: in an href are script execution dressed as a link, and a data: document inherits the origin of the page that opened it.

Wire.set_url_schemes() replaces this list when an application genuinely needs another one, app: or mailto+-style custom handlers being the usual reason.

BLOCKED_URL

wire.escape.BLOCKED_URL = 'about:blank'

What a blocked URL is replaced with. A blocked link stays a link so the page does not fall apart, but it goes nowhere.

STYLE_SAFE

wire.escape.STYLE_SAFE = '/[^-a-zA-Z0-9 \t\n\r#%.,()_\/:;+*!@]/'

The characters a value may contain inside a <style> body.

Everything else is dropped rather than escaped, because a stylesheet has no escape syntax that would make a < harmless: the tokenizer that finds </style> runs before CSS is ever parsed.

Functions

escape()

wire.escape.escape(value, context: string, schemes: ?list) -> string

Escapes value for the given context.

A Safe value is written out unchanged in every context. That is the whole point of it, and it is why raw must only ever be applied to markup you built yourself.

schemes is the URL scheme allowlist, and is only consulted for CONTEXT_URL. Passing nil uses DEFAULT_URL_SCHEMES.

Parameters

  • value (any)
  • context (string) — One of the CONTEXT_* constants.
  • schemes (?list)

Returns string

text()

wire.escape.text(value) -> string

Escapes value for text between tags, turning &, < and > into character references.

Parameters

  • value (any)

Returns string

attribute()

wire.escape.attribute(value) -> string

Escapes value for a double quoted attribute value, turning & and " into character references.

A < needs no escaping inside an attribute and is left alone, which is what the HTML serialization algorithm does too.

Parameters

  • value (any)

Returns string

script()

wire.escape.script(value) -> string

Escapes value for a place the browser reads as JavaScript, by encoding it as JSON and then hiding the characters that would end the surrounding element or start a comment.

The result carries its own quotes when the value is a string, so an interpolation in a script must not be quoted by hand:

<script>
  var user = {{ name }};    // right: renders as "Ada"
  var user = '{{ name }}';  // wrong: renders as '"Ada"'
</script>

U+2028 and U+2029 are escaped as well. They are legal inside a JSON string but end a line in JavaScript, and an unescaped one turns a valid script into a syntax error.

Parameters

  • value (any)

Returns string

style()

wire.escape.style(value) -> string

Escapes value for the body of a <style> element by dropping every character outside a conservative CSS-safe set.

Dropping rather than escaping is deliberate. There is no escape sequence that stops a < inside a stylesheet from being found by the HTML tokenizer looking for </style>, so the only safe answer is for the character not to be there.

Parameters

  • value (any)

Returns string

url()

wire.escape.url(value, schemes: ?list) -> string

Escapes value for an attribute the browser resolves as a URL, replacing it with about:blank when its scheme is not in schemes.

The scheme is read the way a browser reads it: leading and embedded whitespace and control characters are ignored, and the comparison folds case, so Java\tscript:alert(1) is caught along with the plain spelling.

Parameters

  • value (any)
  • schemes (?list) — The allowlist. nil means DEFAULT_URL_SCHEMES.

Returns string

url_list()

wire.escape.url_list(value, schemes: ?list, descriptors: bool) -> string

Escapes value for an attribute holding several URLs, checking each entry’s scheme on its own.

The two attributes that hold a list spell it differently. ping is whitespace separated and every token in it is a URL. srcset is comma separated and only the first token of each entry is a URL; what follows is a descriptor such as 2x or 640w and is left alone. descriptors picks between them.

Every separator is preserved exactly as it was written, since a srcset whose spaces went missing is no longer a srcset.

Parameters

  • value (any)
  • schemes (?list)
  • descriptors (bool) — True for srcset, false for ping.

Returns string

is_url_attribute()

wire.escape.is_url_attribute(name: string) -> bool

Whether name is an attribute the browser resolves as a single URL.

Parameters

  • name (string)

Returns bool

is_url_list_attribute()

wire.escape.is_url_list_attribute(name: string) -> bool

Whether name is an attribute holding a list of URLs.

Parameters

  • name (string)

Returns bool

uses_descriptors()

wire.escape.uses_descriptors(name: string) -> bool

Whether a URL list attribute allows a descriptor after each URL, which srcset does and ping does not.

Parameters

  • name (string)

Returns bool

is_script_attribute()

wire.escape.is_script_attribute(name: string) -> bool

Whether name is an event handler attribute, whose value a browser reads as JavaScript.

Every one of them starts with on, and treating the whole prefix as script rather than keeping a list means a handler HTML gains next year is covered the day it ships.

Parameters

  • name (string)

Returns bool

attribute_context()

wire.escape.attribute_context(name: string) -> string

The escaping context an attribute named name calls for.

Parameters

  • name (string)

Returns string

text_context()

wire.escape.text_context(tag: string) -> string

The escaping context text inside an element named tag calls for.

Parameters

  • tag (string)

Returns string


2026, Richard Ore and The Zuri Contributors