Skip to content

Commit 344e59e

Browse files
authored
Merge pull request rails#49696 from seanpdoyle/action-text-sanitizer-docs
Document Action Text Sanitization
2 parents 89b0a8c + 591d88a commit 344e59e

File tree

4 files changed

+91
-27
lines changed

4 files changed

+91
-27
lines changed

actiontext/app/models/action_text/rich_text.rb

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,24 @@ module ActionText
1717
# message.content.to_s # => "<h1>Funny times!</h1>"
1818
# message.content.to_plain_text # => "Funny times!"
1919
#
20+
# message = Message.create!(content: "<div onclick='action()'>safe<script>unsafe</script></div>")
21+
# message.content #=> #<ActionText::RichText....
22+
# message.content.to_s # => "<div>safeunsafe</div>"
23+
# message.content.to_plain_text # => "safeunsafe"
2024
class RichText < Record
2125
self.table_name = "action_text_rich_texts"
2226

27+
##
28+
# :method: to_s
29+
#
30+
# Safely transforms RichText into an HTML String.
31+
#
32+
# message = Message.create!(content: "<h1>Funny times!</h1>")
33+
# message.content.to_s # => "<h1>Funny times!</h1>"
34+
#
35+
# message = Message.create!(content: "<div onclick='action()'>safe<script>unsafe</script></div>")
36+
# message.content.to_s # => "<div>safeunsafe</div>"
37+
2338
serialize :body, coder: ActionText::Content
2439
delegate :to_s, :nil?, to: :body
2540

@@ -30,10 +45,16 @@ class RichText < Record
3045
self.embeds = body.attachables.grep(ActiveStorage::Blob).uniq if body.present?
3146
end
3247

33-
# Returns the +body+ attribute as plain text with all HTML tags removed.
48+
# Returns a plain-text version of the markup contained by the +body+ attribute,
49+
# with tags removed but HTML entities encoded.
3450
#
3551
# message = Message.create!(content: "<h1>Funny times!</h1>")
3652
# message.content.to_plain_text # => "Funny times!"
53+
#
54+
# NOTE: that the returned string is not HTML safe and should not be rendered in browsers.
55+
#
56+
# message = Message.create!(content: "&lt;script&gt;alert()&lt;/script&gt;")
57+
# message.content.to_plain_text # => "<script>alert()</script>"
3758
def to_plain_text
3859
body&.to_plain_text.to_s
3960
end

actiontext/lib/action_text/content.rb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,19 @@ def render_attachment_galleries(&block)
106106
self.class.new(content, canonicalize: false)
107107
end
108108

109-
# Returns the content as plain text with all HTML tags removed.
109+
# Returns a plain-text version of the markup contained by the content,
110+
# with tags removed but HTML entities encoded.
110111
#
111112
# content = ActionText::Content.new("<h1>Funny times!</h1>")
112113
# content.to_plain_text # => "Funny times!"
114+
#
115+
# content = ActionText::Content.new("<div onclick='action()'>safe<script>unsafe</script></div>")
116+
# content.to_plain_text # => "safeunsafe"
117+
#
118+
# NOTE: that the returned string is not HTML safe and should not be rendered in browsers.
119+
#
120+
# content = ActionText::Content.new("&lt;script&gt;alert()&lt;/script&gt;")
121+
# content.to_plain_text # => "<script>alert()</script>"
113122
def to_plain_text
114123
render_attachments(with_full_attributes: false, &:to_plain_text).fragment.to_plain_text
115124
end
@@ -130,6 +139,13 @@ def to_partial_path
130139
"action_text/contents/content"
131140
end
132141

142+
# Safely transforms Content into an HTML String.
143+
#
144+
# content = ActionText::Content.new(content: "<h1>Funny times!</h1>")
145+
# content.to_s # => "<h1>Funny times!</h1>"
146+
#
147+
# content = ActionText::Content.new("<div onclick='action()'>safe<script>unsafe</script></div>")
148+
# content.to_s # => "<div>safeunsafe</div>"
133149
def to_s
134150
to_rendered_html_with_layout
135151
end

guides/source/action_text_overview.md

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ Action Text brings rich text content and editing to Rails. It includes
2121
the [Trix editor](https://trix-editor.org) that handles everything from formatting
2222
to links to quotes to lists to embedded images and galleries.
2323
The rich text content generated by the Trix editor is saved in its own
24-
RichText model that's associated with any existing Active Record model in the application.
24+
RichText model that can be associated with any existing Active Record model in the application.
2525
Any embedded images (or other attachments) are automatically stored using
2626
Active Storage and associated with the included RichText model.
2727

28+
When it's time to render the content, Action Text processes it by sanitizing its
29+
content so that it's safe to embed directly into the page's HTML.
30+
2831
## Trix Compared to Other Rich Text Editors
2932

3033
Most WYSIWYG editors are wrappers around HTML’s `contenteditable` and `execCommand` APIs,
@@ -80,7 +83,7 @@ $ bin/rails generate model Message content:rich_text
8083

8184
NOTE: you don't need to add a `content` field to your `messages` table.
8285
83-
Then use [`rich_text_area`] to refer to this field in the form for the model:
86+
Then use [`rich_text_area`][] to refer to this field in the form for the model:
8487
8588
```erb
8689
<%# app/views/messages/_form.html.erb %>
@@ -92,16 +95,6 @@ Then use [`rich_text_area`] to refer to this field in the form for the model:
9295
<% end %>
9396
```
9497
95-
And finally, display the sanitized rich text on a page:
96-
97-
```erb
98-
<%= @message.content %>
99-
```
100-
101-
NOTE: If there's an attached resource within `content` field, it might not show properly unless you
102-
have *libvips/libvips42* package installed locally on your machine.
103-
Check their [install docs](https://www.libvips.org/install.html) on how to get it.
104-
10598
To accept the rich text content, all you have to do is permit the referenced attribute:
10699
107100
```ruby
@@ -117,28 +110,60 @@ end
117110
118111
## Rendering Rich Text Content
119112
120-
By default, Action Text will render rich text content inside an element with the
121-
`.trix-content` class:
113+
Instances of `ActionText::RichText` are capable of safely rendering themselves, so embed them directly into the page:
114+
115+
```erb
116+
<%= @message.content %>
117+
```
118+
119+
Learn more about Action Text's sanitization process in the documentation for the [`ActionText::RichText`](https://api.rubyonrails.org/classes/ActionText/RichText.html) class.
120+
121+
NOTE: If there's an attached resource within `content` field, it might not show properly unless you
122+
have *libvips/libvips42* package installed locally on your machine.
123+
Check their [install docs](https://www.libvips.org/install.html) on how to get it.
124+
125+
By default, Action Text will render rich text content inside an element with the `.trix-content` class. Elements with this class, as well as the Action Text editor, are styled by the [`trix` stylesheet](https://unpkg.com/trix/dist/trix.css). To provide your own styles instead, remove `trix` from the pre-processor directives in the `app/assets/stylesheets/actiontext.css` file created by the installer:
126+
127+
```diff
128+
/*
129+
* Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
130+
* the trix-editor content (whether displayed or under editing). Feel free to incorporate this
131+
* inclusion directly in any other asset bundle and remove this file.
132+
*
133+
- *= require trix
134+
*/
135+
```
136+
137+
To customize the HTML container element that's rendered around rich text content, edit the `app/views/layouts/action_text/contents/_content.html.erb` layout file created by the installer:
122138

123139
```html+erb
124140
<%# app/views/layouts/action_text/contents/_content.html.erb %>
141+
125142
<div class="trix-content">
126143
<%= yield %>
127144
</div>
128145
```
129146

130-
Elements with this class, as well as the Action Text editor, are styled by the
131-
[`trix` stylesheet](https://unpkg.com/trix/dist/trix.css).
132-
To provide your own styles instead, remove the `= require trix` line from the
133-
`app/assets/stylesheets/actiontext.css` stylesheet created by the installer.
147+
To customize the HTML rendered for embedded images and other attachments (known as blobs), edit the `app/views/active_storage/blobs/_blob.html.erb` template created by the installer:
134148

135-
To customize the HTML rendered around rich text content, edit the
136-
`app/views/layouts/action_text/contents/_content.html.erb` layout created by the
137-
installer.
138-
139-
To customize the HTML rendered for embedded images and other attachments (known
140-
as blobs), edit the `app/views/active_storage/blobs/_blob.html.erb` template
141-
created by the installer.
149+
```html+erb
150+
<%# app/views/active_storage/blobs/_blob.html.erb %>
151+
152+
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
153+
<% if blob.representable? %>
154+
<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
155+
<% end %>
156+
157+
<figcaption class="attachment__caption">
158+
<% if caption = blob.try(:caption) %>
159+
<%= caption %>
160+
<% else %>
161+
<span class="attachment__name"><%= blob.filename %></span>
162+
<span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
163+
<% end %>
164+
</figcaption>
165+
</figure>
166+
```
142167

143168
### Rendering attachments
144169

guides/source/security.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,8 @@ s = sanitize(user_input, tags: tags, attributes: %w(href title))
788788

789789
This allows only the given tags and does a good job, even against all kinds of tricks and malformed tags.
790790

791+
Both Action View and Action Text build their [sanitization helpers](https://api.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html) on top of the [rails-html-sanitizer](https://github.com/rails/rails-html-sanitizer) gem.
792+
791793
As a second step, _it is good practice to escape all output of the application_, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). _Use `html_escape()` (or its alias `h()`) method_ to replace the HTML input characters `&`, `"`, `<`, and `>` by their uninterpreted representations in HTML (`&amp;`, `&quot;`, `&lt;`, and `&gt;`).
792794

793795
##### Obfuscation and Encoding Injection

0 commit comments

Comments
 (0)