@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Document all the SafeHTML stuff

Summary: Write documentation about how to use phutil_tag(), etc., safely.

Test Plan:
Read carefully.

{F31223}

Reviewers: vrana, btrahan, chad

Reviewed By: vrana

CC: aran

Maniphest Tasks: T2432

Differential Revision: https://secure.phabricator.com/D4769

+178
+178
src/docs/developer/rendering_html.diviner
··· 1 + @title Rendering HTML 2 + @group developer 3 + 4 + Rendering HTML in the Phabricator environment. 5 + 6 + = Overview = 7 + 8 + Phabricator attempts to prevent XSS by treating strings as default-unsafe when 9 + rendering. This means that if you try to build HTML through string 10 + concatenation, it won't work: the string will be escaped by the rendering 11 + pipeline, and the browser will treat it as plain text, not HTML. 12 + 13 + This document describes the right way to build HTML components so they are safe 14 + from XSS and render correctly. Broadly: 15 + 16 + - Use @{function@libphutil:phutil_tag} (and @{function:javelin_tag}) to build 17 + tags. 18 + - Use @{function@libphutil:hsprintf} where @{function@libphutil:phutil_tag} 19 + is awkward. 20 + - Combine elements with arrays, not string concatenation. 21 + - @{class:AphrontView} subclasses should return a 22 + @{class@libphutil:PhutilSafeHTML} object from their `render()` method. 23 + - @{function:pht} has some special rules. 24 + - There are some other things that you should be aware of. 25 + - Do not use @{function:phutil_render_tag} or @{function:javelin_render_tag}, 26 + they are deprecated and unsafe. 27 + 28 + See below for discussion. 29 + 30 + = Building Tags: phutil_tag() = 31 + 32 + Build HTML tags with @{function@libphutil:phutil_tag}. For example: 33 + 34 + phutil_tag( 35 + 'div', 36 + array( 37 + 'class' => 'some-class', 38 + ), 39 + $content); 40 + 41 + @{function@libphutil:phutil_tag} will properly escape the content and all the 42 + attributes, and return a @{class@libphutil:PhutilSafeHTML} object. The rendering 43 + pipeline knows that this object represents a properly escaped HTML tag. This 44 + allows @{function@libphutil:phutil_tag} to render tags with other tags as 45 + content correctly (without double-escaping): 46 + 47 + phutil_tag( 48 + 'div', 49 + array(), 50 + phutil_tag( 51 + 'strong', 52 + array(), 53 + $content)); 54 + 55 + In Phabricator, the @{function:javelin_tag} function is similar to 56 + @{function@libphutil:phutil_tag}, but provides special handling for the 57 + `sigil` and `meta` attributes. 58 + 59 + = Building Blocks: hsprintf() = 60 + 61 + Sometimes, @{function@libphutil:phutil_tag} can be particularly awkward to 62 + use. You can use @{function@libphutil:hsprintf} to build larger and more 63 + complex blocks of HTML, when @{function@libphutil:phutil_tag} is a poor fit. 64 + @{function:hsprintf} has `sprintf()` semantics, but `%s` escapes HTML: 65 + 66 + // Safely build fragments or unwieldy blocks. 67 + hsprintf( 68 + '<div id="%s">', 69 + $div_id); 70 + 71 + @{function:hsprintf} can be especially useful when: 72 + 73 + - You need to build a block with a lot of tags, like a table with rows and 74 + cells. 75 + - You need to build part of a tag (usually you should avoid this, but if you 76 + do need to, @{function@libphutil:phutil_tag} can not do it). 77 + 78 + Note that it is unsafe to provide any user-controlled data to the first 79 + parameter of @{function@libphutil:hsprintf} (the `sprintf()`-style pattern). 80 + 81 + Like @{function@libphutil:phutil_tag}, this function returns a 82 + @{class@libphutil:PhutilSafeHTML} object. 83 + 84 + = Composing Tags = 85 + 86 + When you are building a view which combines smaller components, like a section 87 + with a header and a body: 88 + 89 + $header = phutil_tag('h1', ...); 90 + $body = phutil_tag('p', ...); 91 + 92 + ...you should NOT use string concatenation: 93 + 94 + COUNTEREXAMPLE 95 + // Not dangerous, but does the wrong thing. 96 + phutil_tag('div', array(), $header.$body); 97 + 98 + Instead, use an array: 99 + 100 + // Render a tag containing other tags safely. 101 + phutil_tag('div', array(), array($header, $body)); 102 + 103 + If you concatenate @{class@libphutil:PhutilSafeHTML} objects, they revert to 104 + normal strings and are no longer marked as properly escaped tags. 105 + 106 + (In the future, these objects may stop converting to strings, but for now they 107 + must to maintain backward compatibility.) 108 + 109 + If you need to build a list of items with some element in between each of them 110 + (like a middot, comma, or vertical bar) you can use 111 + @{function:array_interleave}: 112 + 113 + // Render links with commas between them. 114 + phutil_tag( 115 + 'div', 116 + array(), 117 + array_interleave(', ', $list_of_links)); 118 + 119 + = AphrontView Classes = 120 + 121 + Subclasses of @{class:AphrontView} in Phabricator should return a 122 + @{class@libphutil:PhutilSafeHTML} object. The easiest way to do this is to 123 + return `phutil_tag()` or `javelin_tag()`: 124 + 125 + return phutil_tag('div', ...); 126 + 127 + @{class:AphrontView} subclasses can use `renderHTMLChildren()` and 128 + `renderHTMLView()` to build @{class@libphutil:PhutilSafeHTML} objects from 129 + children or arbitrary lists of components. 130 + 131 + @{class:AphrontView} subclasses should avoid `renderChildren()` and 132 + `renderSingleView()` and transition callers to the `HTML` varieties. These older 133 + methods do not return @{class@libphutil:PhutilSafeHTML} objects. 134 + 135 + = Internationalization: pht() = 136 + 137 + The @{function:pht} function has some special rules. If any input to 138 + @{function:pht} is a @{class@libphutil:PhutilSafeHTML} object, @{function:pht} 139 + returns a @{class@libphutil:PhutilSafeHTML} object itself. Otherwise, it returns 140 + normal text. 141 + 142 + This is generally safe because translations are not permitted to have more tags 143 + than the original text did (so if the original text had no tags, translations 144 + can not add any). 145 + 146 + Normally, this just means that @{function:pht} does the right thing and behaves 147 + like you would expect, but it is worth being aware of. 148 + 149 + = Special Cases = 150 + 151 + NOTE: This section describes dangerous methods which can bypass XSS protections. 152 + If possible, do not use them. 153 + 154 + You can build @{class@libphutil:PhutilSafeHTML} out of a string explicitly by 155 + calling @{function:phutil_safe_html} on it. This is **dangerous**, because if 156 + you are wrong and the string is not actually safe, you have introduced an XSS 157 + vulnerability. Consequently, you should avoid calling this if possible. 158 + 159 + You can use @{function@libphutil:phutil_escape_html} to explicitly escape an 160 + HTML string. You should not normally need to use it. 161 + 162 + You can use @{function@libphutil:phutil_escape_html_newlines} to escape HTML 163 + while converting newlines to `<br />`. 164 + 165 + If you need to extract the content of a @{class@libphutil:PhutilSafeHTML} 166 + object, you should call `getHTMLContent()`, not cast it to a string. Eventually, 167 + we would like to remove the string cast entirely. 168 + 169 + = Deprecated Functions = 170 + 171 + The functions @{function@libphutil:phutil_render_tag} and 172 + @{function:javelin_render_tag} are unsafe versions of 173 + @{function@libphutil:phutil_tag} and @{function:javelin_tag}, are deprecated, 174 + and will be removed in the future. You should avoid their use and convert code 175 + to use @{function@libphutil:phutil_tag} and @{function:javelin_tag}. 176 + 177 + The function @{function:phabricator_render_form} is also deprecated, in favor of 178 + @{function:phabricator_form}.