Skip to content

Commit

Permalink
Document Action Text Sanitization
Browse files Browse the repository at this point in the history
Add documentation for `ActionText::RichText#to_s` and
`ActionText::Content#to_s` that demonstrates Action Text's ability to
sanitize and scrub its content.

Co-authored-by: Mike Dalessio <mike.dalessio@gmail.com>
Co-authored-by: Petrik de Heus <petrik@deheus.net>
  • Loading branch information
3 people committed Oct 30, 2023
1 parent 89b0a8c commit 591d88a
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 27 deletions.
23 changes: 22 additions & 1 deletion actiontext/app/models/action_text/rich_text.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,24 @@ module ActionText
# message.content.to_s # => "<h1>Funny times!</h1>"
# message.content.to_plain_text # => "Funny times!"
#
# message = Message.create!(content: "<div onclick='action()'>safe<script>unsafe</script></div>")
# message.content #=> #<ActionText::RichText....
# message.content.to_s # => "<div>safeunsafe</div>"
# message.content.to_plain_text # => "safeunsafe"
class RichText < Record
self.table_name = "action_text_rich_texts"

##
# :method: to_s
#
# Safely transforms RichText into an HTML String.
#
# message = Message.create!(content: "<h1>Funny times!</h1>")
# message.content.to_s # => "<h1>Funny times!</h1>"
#
# message = Message.create!(content: "<div onclick='action()'>safe<script>unsafe</script></div>")
# message.content.to_s # => "<div>safeunsafe</div>"

serialize :body, coder: ActionText::Content
delegate :to_s, :nil?, to: :body

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

# Returns the +body+ attribute as plain text with all HTML tags removed.
# Returns a plain-text version of the markup contained by the +body+ attribute,
# with tags removed but HTML entities encoded.
#
# message = Message.create!(content: "<h1>Funny times!</h1>")
# message.content.to_plain_text # => "Funny times!"
#
# NOTE: that the returned string is not HTML safe and should not be rendered in browsers.
#
# message = Message.create!(content: "&lt;script&gt;alert()&lt;/script&gt;")
# message.content.to_plain_text # => "<script>alert()</script>"
def to_plain_text
body&.to_plain_text.to_s
end
Expand Down
18 changes: 17 additions & 1 deletion actiontext/lib/action_text/content.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,19 @@ def render_attachment_galleries(&block)
self.class.new(content, canonicalize: false)
end

# Returns the content as plain text with all HTML tags removed.
# Returns a plain-text version of the markup contained by the content,
# with tags removed but HTML entities encoded.
#
# content = ActionText::Content.new("<h1>Funny times!</h1>")
# content.to_plain_text # => "Funny times!"
#
# content = ActionText::Content.new("<div onclick='action()'>safe<script>unsafe</script></div>")
# content.to_plain_text # => "safeunsafe"
#
# NOTE: that the returned string is not HTML safe and should not be rendered in browsers.
#
# content = ActionText::Content.new("&lt;script&gt;alert()&lt;/script&gt;")
# content.to_plain_text # => "<script>alert()</script>"
def to_plain_text
render_attachments(with_full_attributes: false, &:to_plain_text).fragment.to_plain_text
end
Expand All @@ -130,6 +139,13 @@ def to_partial_path
"action_text/contents/content"
end

# Safely transforms Content into an HTML String.
#
# content = ActionText::Content.new(content: "<h1>Funny times!</h1>")
# content.to_s # => "<h1>Funny times!</h1>"
#
# content = ActionText::Content.new("<div onclick='action()'>safe<script>unsafe</script></div>")
# content.to_s # => "<div>safeunsafe</div>"
def to_s
to_rendered_html_with_layout
end
Expand Down
75 changes: 50 additions & 25 deletions guides/source/action_text_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ Action Text brings rich text content and editing to Rails. It includes
the [Trix editor](https://trix-editor.org) that handles everything from formatting
to links to quotes to lists to embedded images and galleries.
The rich text content generated by the Trix editor is saved in its own
RichText model that's associated with any existing Active Record model in the application.
RichText model that can be associated with any existing Active Record model in the application.
Any embedded images (or other attachments) are automatically stored using
Active Storage and associated with the included RichText model.

When it's time to render the content, Action Text processes it by sanitizing its
content so that it's safe to embed directly into the page's HTML.

## Trix Compared to Other Rich Text Editors

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

NOTE: you don't need to add a `content` field to your `messages` table.
Then use [`rich_text_area`] to refer to this field in the form for the model:
Then use [`rich_text_area`][] to refer to this field in the form for the model:
```erb
<%# app/views/messages/_form.html.erb %>
Expand All @@ -92,16 +95,6 @@ Then use [`rich_text_area`] to refer to this field in the form for the model:
<% end %>
```
And finally, display the sanitized rich text on a page:
```erb
<%= @message.content %>
```
NOTE: If there's an attached resource within `content` field, it might not show properly unless you
have *libvips/libvips42* package installed locally on your machine.
Check their [install docs](https://www.libvips.org/install.html) on how to get it.

To accept the rich text content, all you have to do is permit the referenced attribute:
```ruby
Expand All @@ -117,28 +110,60 @@ end
## Rendering Rich Text Content
By default, Action Text will render rich text content inside an element with the
`.trix-content` class:
Instances of `ActionText::RichText` are capable of safely rendering themselves, so embed them directly into the page:
```erb
<%= @message.content %>
```
Learn more about Action Text's sanitization process in the documentation for the [`ActionText::RichText`](https://api.rubyonrails.org/classes/ActionText/RichText.html) class.

NOTE: If there's an attached resource within `content` field, it might not show properly unless you
have *libvips/libvips42* package installed locally on your machine.
Check their [install docs](https://www.libvips.org/install.html) on how to get it.
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:
```diff
/*
* Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
* the trix-editor content (whether displayed or under editing). Feel free to incorporate this
* inclusion directly in any other asset bundle and remove this file.
*
- *= require trix
*/
```
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:

```html+erb
<%# app/views/layouts/action_text/contents/_content.html.erb %>
<div class="trix-content">
<%= yield %>
</div>
```

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 the `= require trix` line from the
`app/assets/stylesheets/actiontext.css` stylesheet created by the installer.
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:

To customize the HTML rendered around rich text content, edit the
`app/views/layouts/action_text/contents/_content.html.erb` layout created by the
installer.

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.
```html+erb
<%# app/views/active_storage/blobs/_blob.html.erb %>
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
<% if blob.representable? %>
<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
<% end %>
<figcaption class="attachment__caption">
<% if caption = blob.try(:caption) %>
<%= caption %>
<% else %>
<span class="attachment__name"><%= blob.filename %></span>
<span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
<% end %>
</figcaption>
</figure>
```

### Rendering attachments

Expand Down
2 changes: 2 additions & 0 deletions guides/source/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,8 @@ s = sanitize(user_input, tags: tags, attributes: %w(href title))

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

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.

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;`).

##### Obfuscation and Encoding Injection
Expand Down

0 comments on commit 591d88a

Please sign in to comment.