Skip to content

Commit 9028907

Browse files
authored
Refactor markdown sanitize (#824)
* 重构 Markdown 的 HTML 结果处理方式,去掉 body_html,改为读取的时候转换 + cache. 用 sanitize gem 来处理 HTML 安全的问题,同时修复 YouTube iframe 的支持。
1 parent 330393b commit 9028907

31 files changed

+154
-113
lines changed

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ gem 'turbolinks', '~> 5.0.0'
1515
gem 'dropzonejs-rails'
1616
gem 'rails_autolink'
1717

18+
gem 'sanitize'
19+
1820
gem 'pg'
1921
gem 'pghero'
2022

Gemfile.lock

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
GIT
22
remote: https://github.com/elasticsearch/elasticsearch-rails
3-
revision: 15761247f3e99654bda946a178e50b5365414b59
3+
revision: b6d485748c71a07d064ea2a46a6da82d64a04cd7
44
specs:
55
elasticsearch-model (0.1.9)
66
activesupport (> 3)
@@ -111,6 +111,7 @@ GEM
111111
coffee-script-source (1.11.1)
112112
concurrent-ruby (1.0.2)
113113
connection_pool (2.2.1)
114+
crass (1.0.2)
114115
dalli (2.7.6)
115116
database_cleaner (1.5.3)
116117
debug_inspector (0.0.2)
@@ -160,7 +161,7 @@ GEM
160161
faraday (0.9.2)
161162
multipart-post (>= 1.2, < 3)
162163
ffi (1.9.14)
163-
font-awesome-rails (4.7.0.0)
164+
font-awesome-rails (4.7.0.1)
164165
railties (>= 3.2, < 5.1)
165166
get_process_mem (0.2.1)
166167
globalid (0.3.7)
@@ -195,7 +196,7 @@ GEM
195196
rails-dom-testing (>= 1, < 3)
196197
railties (>= 4.2.0)
197198
thor (>= 0.14, < 2.0)
198-
json (2.0.2)
199+
json (1.8.3)
199200
jsonapi (0.1.1.beta2)
200201
json (~> 1.8)
201202
jwt (1.5.6)
@@ -229,6 +230,8 @@ GEM
229230
nio4r (1.2.1)
230231
nokogiri (1.6.8.1)
231232
mini_portile2 (~> 2.1.0)
233+
nokogumbo (1.4.10)
234+
nokogiri
232235
notifications (0.2.0)
233236
rails (>= 4.2.0, < 5.1)
234237
will_paginate (>= 3.0.0)
@@ -363,6 +366,10 @@ GEM
363366
ruby_dep (1.5.0)
364367
rucaptcha (1.2.0)
365368
railties (>= 3.2)
369+
sanitize (4.4.0)
370+
crass (~> 1.0.2)
371+
nokogiri (>= 1.4.4)
372+
nokogumbo (~> 1.4.1)
366373
sass (3.4.22)
367374
sass-rails (5.0.6)
368375
railties (>= 4.0.0, < 6)
@@ -414,7 +421,7 @@ GEM
414421
turbolinks (5.0.1)
415422
turbolinks-source (~> 5)
416423
turbolinks-source (5.0.0)
417-
twemoji (3.0.2)
424+
twemoji (3.1.0)
418425
nokogiri (~> 1.6.2)
419426
tzinfo (1.2.2)
420427
thread_safe (~> 0.1)
@@ -501,6 +508,7 @@ DEPENDENCIES
501508
rubocop (~> 0.39.0)
502509
ruby-push-notifications
503510
rucaptcha
511+
sanitize
504512
sass-rails
505513
second_level_cache
506514
sidekiq

app/controllers/replies_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def index
2424
return
2525
end
2626

27-
@replies = Reply.unscoped.where('topic_id = ? and id > ?', @topic.id, last_id).without_body.order(:id).all
27+
@replies = Reply.unscoped.where('topic_id = ? and id > ?', @topic.id, last_id).order(:id).all
2828
if current_user
2929
current_user.read_topic(@topic, replies_ids: @replies.collect(&:id))
3030
end

app/controllers/topics_controller.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def index
3232
end
3333

3434
def feed
35-
@topics = Topic.without_hide_nodes.recent.without_body.limit(20).includes(:node, :user, :last_reply_user)
35+
@topics = Topic.without_hide_nodes.recent.limit(20).includes(:node, :user, :last_reply_user)
3636
render layout: false if stale?(@topics)
3737
end
3838

@@ -49,7 +49,7 @@ def node
4949

5050
def node_feed
5151
@node = Node.find(params[:id])
52-
@topics = @node.topics.recent.without_body.limit(20)
52+
@topics = @node.topics.recent.limit(20)
5353
render layout: false if stale?([@node, @topics])
5454
end
5555

@@ -93,7 +93,7 @@ def show
9393
@show_raw = params[:raw] == '1'
9494
@can_reply = can? :create, Reply
9595

96-
@replies = Reply.unscoped.where(topic_id: @topic.id).without_body.order(:id).all
96+
@replies = Reply.unscoped.where(topic_id: @topic.id).order(:id).all
9797

9898
check_current_user_liked_replies
9999
check_current_user_status_for_topic

app/helpers/application_helper.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
module ApplicationHelper
2-
ALLOW_TAGS = %w(p br img h1 h2 h3 h4 h5 h6 blockquote pre code b i
3-
strong em table tr td tbody th strike del u a ul ol li span hr)
4-
ALLOW_ATTRIBUTES = %w(href src class width height id title alt target rel data-floor frameborder allowfullscreen)
52
EMPTY_STRING = ''.freeze
63

74
def markdown(text)
8-
sanitize_markdown(Homeland::Markdown.call(text))
5+
Rails.cache.fetch(['markdown', Digest::MD5.hexdigest(text)]) do
6+
raw sanitize_markdown(Homeland::Markdown.call(text))
7+
end
98
end
109

11-
def sanitize_markdown(body)
12-
# TODO: This method slow, 3.5ms per call in topic body
13-
sanitize(body, tags: ALLOW_TAGS, attributes: ALLOW_ATTRIBUTES)
10+
def sanitize_markdown(html)
11+
Sanitize.fragment(html, Homeland::Sanitize::DEFAULT)
1412
end
1513

1614
def notice_message

app/models/comment.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
class Comment < ApplicationRecord
22
include MarkdownBody
3-
43
belongs_to :commentable, polymorphic: true
54
belongs_to :user
65

app/models/concerns/markdown_body.rb

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
11
module MarkdownBody
22
extend ActiveSupport::Concern
3-
include ActionView::Helpers::SanitizeHelper
3+
include ActionView::Helpers::OutputSafetyHelper
44
include ApplicationHelper
55

6-
included do
7-
before_save :markdown_body
8-
scope :without_body, -> { without(:body) }
9-
end
10-
11-
private
12-
13-
def markdown_body
14-
if self.body_changed?
15-
self.body_html = sanitize_markdown(Homeland::Markdown.call(body))
16-
end
6+
def body_html
7+
markdown(body)
178
end
189
end

app/models/page_version.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
class PageVersion < ApplicationRecord
2+
include MarkdownBody
3+
24
belongs_to :user
35
belongs_to :page
46
end

app/models/reply.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
require 'digest/md5'
22
class Reply < ApplicationRecord
3-
include SoftDelete
43
include MarkdownBody
4+
include SoftDelete
55
include Likeable
66
include Mentionable
77
include MentionTopic
@@ -16,8 +16,7 @@ class Reply < ApplicationRecord
1616
delegate :login, to: :user, prefix: true, allow_nil: true
1717

1818
scope :without_system, -> { where(action: nil) }
19-
scope :fields_for_list, -> { select(:topic_id, :id, :body_html, :updated_at, :created_at) }
20-
scope :without_body, -> { select(column_names - ['body']) }
19+
scope :fields_for_list, -> { select(:topic_id, :id, :body, :updated_at, :created_at) }
2120

2221
validates :body, presence: true, unless: -> { system_event? }
2322
validates :body, uniqueness: { scope: [:topic_id, :user_id], message: '不能重复提交。' }, unless: -> { system_event? }

app/models/topic.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
]
1111

1212
class Topic < ApplicationRecord
13-
include Likeable
1413
include MarkdownBody
14+
include Likeable
1515
include SoftDelete
1616
include Mentionable
1717
include Closeable
@@ -45,7 +45,6 @@ class Topic < ApplicationRecord
4545
scope :popular, -> { where('likes_count > 5') }
4646
scope :excellent, -> { where('excellent >= 1') }
4747
scope :without_hide_nodes, -> { exclude_column_ids('node_id', Topic.topic_index_hide_node_ids) }
48-
scope :without_body, -> { select(column_names - ['body']) }
4948
scope :without_node_ids, -> (ids) { exclude_column_ids('node_id', ids) }
5049
scope :exclude_column_ids, lambda { |column, ids|
5150
if ids.empty?
@@ -99,7 +98,7 @@ def related_topics(size = 5)
9998
end
10099

101100
def self.fields_for_list
102-
columns = %w(body body_html who_deleted follower_ids)
101+
columns = %w(body who_deleted follower_ids)
103102
select(column_names - columns.map(&:to_s))
104103
end
105104

app/serializers/reply_serializer.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
#
33
# == attributes
44
# - *id* [Integer] 编号
5-
# - *body_html* [String] 以转换成 HTML 的正文
65
# - *topic_id* [Integer] 话题编号
76
# - *deleted* [Boolean] 是否已删除
87
# - *likes_count* [Integer] 赞数量

app/views/admin/page_versions/show.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@
1717
Title: <%= @page_version.title %>
1818
</div>
1919

20-
<%= markdown @page_version.body %>
20+
<%= @page_version.body_html %>

app/views/comments/_comment.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<%= user_name_tag(item.user) %> <%= t("common.published_at") %> <%= timeago(item.created_at) %>
66
</div>
77
<div class="body markdown">
8-
<%= sanitize item.body_html %>
8+
<%= item.body_html %>
99
</div>
1010
</div>
1111
</div>

app/views/notifications/_mention.html.erb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</div>
1212
<% if reply.present? %>
1313
<div class="media-content summary markdown">
14-
<%= raw reply.body_html %>
14+
<%= reply.body_html %>
1515
</div>
1616
<% end %>
1717
<% when Topic %>
@@ -26,7 +26,7 @@
2626
<%= topic_title_tag(topic) %> 提及你:
2727
</div>
2828
<div class="media-content summary markdown">
29-
<%= raw topic.body_html %>
29+
<%= topic.body_html %>
3030
</div>
3131
<% end %>
3232
<% end %>

app/views/notifications/_topic_reply.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</div>
1313
<%- if reply.present? -%>
1414
<div class="media-content summary markdown">
15-
<%= raw reply.body_html %>
15+
<%= reply.body_html %>
1616
</div>
1717
<%- end -%>
1818
<% end %>

app/views/pages/show.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
<div class="panel-body markdown">
2626
<article>
27-
<%= raw @page.body_html %>
27+
<%= @page.body_html %>
2828
</article>
2929
</div>
3030

app/views/replies/_reply.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
</span>
3636
</div>
3737
<div class="markdown<%= ' deleted' if reply.deleted? %>">
38-
<%= raw reply.body_html %>
38+
<%= reply.body_html %>
3939
</div>
4040
</div>
4141
<% end %>

app/views/shared/_comment.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<span class="name"><%= user_name_tag(comment.user) %></span> 发表于 <%= timeago(comment.created_at) %>
88
</div>
99
<div class="body markdown">
10-
<%= raw comment.body_html %>
10+
<%= comment.body_html %>
1111
</div>
1212
</div>
1313
</div>

app/views/topic_mailer/new_reply.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<%= user_name_tag(@reply.user) %> 回复了 《<%= topic_title_tag(@topic)%>》:</label> <br />
44
</p>
55
<blockquote>
6-
<p><%= raw @reply.body_html %><br/></p>
6+
<p><%= @reply.body_html %><br/></p>
77
</blockquote>
88
<p>
99
<label> 帖子地址:</label> <%= topic_title_tag(@topic) %>.

app/views/topics/feed.builder

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ xml.rss(version: "2.0"){
88
for topic in @topics
99
xml.item do
1010
xml.title topic.title
11-
xml.description raw(topic.body_html)
11+
xml.description markdown(topic.body)
1212
xml.author topic.user.login
1313
xml.pubDate(topic.created_at.strftime("%a, %d %b %Y %H:%M:%S %z"))
1414
xml.link topic_url topic

app/views/topics/node_feed.builder

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ xml.rss version: "2.0" do
77
for topic in @topics
88
xml.item do
99
xml.title topic.title
10-
xml.description raw(topic.body_html)
10+
xml.description markdown(topic.body)
1111
xml.author topic.user.login
1212
xml.pubDate topic.created_at.strftime("%a, %d %b %Y %H:%M:%S %z")
1313
xml.link topic_url(topic)

app/views/topics/show.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
<%= raw Setting.before_topic_html %>
3737

3838
<article>
39-
<%= raw @topic.body_html %>
39+
<%= @topic.body_html %>
4040
</article>
4141

4242

app/views/users/_replies.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<span class="info">at <%= timeago(reply.created_at) %></span>
99
</div>
1010
<div class="body markdown">
11-
<%= raw reply.body_html %>
11+
<%= reply.body_html %>
1212
</div>
1313
</li>
1414
<% end %>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class RemoveBodyHtmlField < ActiveRecord::Migration[5.0]
2+
def change
3+
remove_column :topics, :body_html
4+
remove_column :replies, :body_html
5+
remove_column :pages, :body_html
6+
remove_column :comments, :body_html
7+
end
8+
end

db/schema.rb

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema.define(version: 20160912124102) do
13+
ActiveRecord::Schema.define(version: 20161215014636) do
1414

1515
# These are extensions that must be enabled in order to support this database
1616
enable_extension "plpgsql"
@@ -26,7 +26,6 @@
2626

2727
create_table "comments", force: :cascade do |t|
2828
t.text "body", null: false
29-
t.text "body_html"
3029
t.integer "user_id", null: false
3130
t.string "commentable_type"
3231
t.integer "commentable_id"
@@ -164,7 +163,6 @@
164163
t.string "slug", null: false
165164
t.string "title", null: false
166165
t.text "body", null: false
167-
t.text "body_html"
168166
t.boolean "locked", default: false
169167
t.integer "version", default: 0, null: false
170168
t.integer "editor_ids", default: [], null: false, array: true
@@ -189,7 +187,6 @@
189187
t.integer "user_id", null: false
190188
t.integer "topic_id", null: false
191189
t.text "body", null: false
192-
t.text "body_html"
193190
t.integer "state", default: 1, null: false
194191
t.integer "liked_user_ids", default: [], array: true
195192
t.integer "likes_count", default: 0
@@ -264,7 +261,6 @@
264261
t.integer "node_id", null: false
265262
t.string "title", null: false
266263
t.text "body", null: false
267-
t.text "body_html"
268264
t.integer "last_reply_id"
269265
t.integer "last_reply_user_id"
270266
t.string "last_reply_user_login"
@@ -348,5 +344,4 @@
348344
t.index ["login"], name: "index_users_on_login", using: :btree
349345
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true, using: :btree
350346
end
351-
352347
end

0 commit comments

Comments
 (0)