diff --git a/src/docs/developer/rendering_html.diviner b/src/docs/developer/rendering_html.diviner new file mode 100644 index 0000000000..185c43caf9 --- /dev/null +++ b/src/docs/developer/rendering_html.diviner @@ -0,0 +1,178 @@ +@title Rendering HTML +@group developer + +Rendering HTML in the Phabricator environment. + += Overview = + +Phabricator attempts to prevent XSS by treating strings as default-unsafe when +rendering. This means that if you try to build HTML through string +concatenation, it won't work: the string will be escaped by the rendering +pipeline, and the browser will treat it as plain text, not HTML. + +This document describes the right way to build HTML components so they are safe +from XSS and render correctly. Broadly: + + - Use @{function@libphutil:phutil_tag} (and @{function:javelin_tag}) to build + tags. + - Use @{function@libphutil:hsprintf} where @{function@libphutil:phutil_tag} + is awkward. + - Combine elements with arrays, not string concatenation. + - @{class:AphrontView} subclasses should return a + @{class@libphutil:PhutilSafeHTML} object from their `render()` method. + - @{function:pht} has some special rules. + - There are some other things that you should be aware of. + - Do not use @{function:phutil_render_tag} or @{function:javelin_render_tag}, + they are deprecated and unsafe. + +See below for discussion. + += Building Tags: phutil_tag() = + +Build HTML tags with @{function@libphutil:phutil_tag}. For example: + + phutil_tag( + 'div', + array( + 'class' => 'some-class', + ), + $content); + +@{function@libphutil:phutil_tag} will properly escape the content and all the +attributes, and return a @{class@libphutil:PhutilSafeHTML} object. The rendering +pipeline knows that this object represents a properly escaped HTML tag. This +allows @{function@libphutil:phutil_tag} to render tags with other tags as +content correctly (without double-escaping): + + phutil_tag( + 'div', + array(), + phutil_tag( + 'strong', + array(), + $content)); + +In Phabricator, the @{function:javelin_tag} function is similar to +@{function@libphutil:phutil_tag}, but provides special handling for the +`sigil` and `meta` attributes. + += Building Blocks: hsprintf() = + +Sometimes, @{function@libphutil:phutil_tag} can be particularly awkward to +use. You can use @{function@libphutil:hsprintf} to build larger and more +complex blocks of HTML, when @{function@libphutil:phutil_tag} is a poor fit. +@{function:hsprintf} has `sprintf()` semantics, but `%s` escapes HTML: + + // Safely build fragments or unwieldy blocks. + hsprintf( + '
', + $div_id); + +@{function:hsprintf} can be especially useful when: + + - You need to build a block with a lot of tags, like a table with rows and + cells. + - You need to build part of a tag (usually you should avoid this, but if you + do need to, @{function@libphutil:phutil_tag} can not do it). + +Note that it is unsafe to provide any user-controlled data to the first +parameter of @{function@libphutil:hsprintf} (the `sprintf()`-style pattern). + +Like @{function@libphutil:phutil_tag}, this function returns a +@{class@libphutil:PhutilSafeHTML} object. + += Composing Tags = + +When you are building a view which combines smaller components, like a section +with a header and a body: + + $header = phutil_tag('h1', ...); + $body = phutil_tag('p', ...); + +...you should NOT use string concatenation: + + COUNTEREXAMPLE + // Not dangerous, but does the wrong thing. + phutil_tag('div', array(), $header.$body); + +Instead, use an array: + + // Render a tag containing other tags safely. + phutil_tag('div', array(), array($header, $body)); + +If you concatenate @{class@libphutil:PhutilSafeHTML} objects, they revert to +normal strings and are no longer marked as properly escaped tags. + +(In the future, these objects may stop converting to strings, but for now they +must to maintain backward compatibility.) + +If you need to build a list of items with some element in between each of them +(like a middot, comma, or vertical bar) you can use +@{function:array_interleave}: + + // Render links with commas between them. + phutil_tag( + 'div', + array(), + array_interleave(', ', $list_of_links)); + += AphrontView Classes = + +Subclasses of @{class:AphrontView} in Phabricator should return a +@{class@libphutil:PhutilSafeHTML} object. The easiest way to do this is to +return `phutil_tag()` or `javelin_tag()`: + + return phutil_tag('div', ...); + +@{class:AphrontView} subclasses can use `renderHTMLChildren()` and +`renderHTMLView()` to build @{class@libphutil:PhutilSafeHTML} objects from +children or arbitrary lists of components. + +@{class:AphrontView} subclasses should avoid `renderChildren()` and +`renderSingleView()` and transition callers to the `HTML` varieties. These older +methods do not return @{class@libphutil:PhutilSafeHTML} objects. + += Internationalization: pht() = + +The @{function:pht} function has some special rules. If any input to +@{function:pht} is a @{class@libphutil:PhutilSafeHTML} object, @{function:pht} +returns a @{class@libphutil:PhutilSafeHTML} object itself. Otherwise, it returns +normal text. + +This is generally safe because translations are not permitted to have more tags +than the original text did (so if the original text had no tags, translations +can not add any). + +Normally, this just means that @{function:pht} does the right thing and behaves +like you would expect, but it is worth being aware of. + += Special Cases = + +NOTE: This section describes dangerous methods which can bypass XSS protections. +If possible, do not use them. + +You can build @{class@libphutil:PhutilSafeHTML} out of a string explicitly by +calling @{function:phutil_safe_html} on it. This is **dangerous**, because if +you are wrong and the string is not actually safe, you have introduced an XSS +vulnerability. Consequently, you should avoid calling this if possible. + +You can use @{function@libphutil:phutil_escape_html} to explicitly escape an +HTML string. You should not normally need to use it. + +You can use @{function@libphutil:phutil_escape_html_newlines} to escape HTML +while converting newlines to `
`. + +If you need to extract the content of a @{class@libphutil:PhutilSafeHTML} +object, you should call `getHTMLContent()`, not cast it to a string. Eventually, +we would like to remove the string cast entirely. + += Deprecated Functions = + +The functions @{function@libphutil:phutil_render_tag} and +@{function:javelin_render_tag} are unsafe versions of +@{function@libphutil:phutil_tag} and @{function:javelin_tag}, are deprecated, +and will be removed in the future. You should avoid their use and convert code +to use @{function@libphutil:phutil_tag} and @{function:javelin_tag}. + +The function @{function:phabricator_render_form} is also deprecated, in favor of +@{function:phabricator_form}.