FEATURE: Nokogumbo (#9577)

* FEATURE: Nokogumbo

Use Nokogumbo HTML parser.
This commit is contained in:
Krzysztof Kotlarek 2020-05-05 13:46:57 +10:00 committed by GitHub
parent b8b1cbbfb9
commit 9bff0882c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 165 additions and 179 deletions

View File

@ -13,7 +13,7 @@ module UserNotificationsHelper
end end
def correct_top_margin(html, desired) def correct_top_margin(html, desired)
fragment = Nokogiri::HTML.fragment(html) fragment = Nokogiri::HTML5.fragment(html)
if para = fragment.css("p:first").first if para = fragment.css("p:first").first
para["style"] = "margin-top: #{desired};" para["style"] = "margin-top: #{desired};"
end end
@ -32,7 +32,7 @@ module UserNotificationsHelper
end end
def first_paragraphs_from(html) def first_paragraphs_from(html)
doc = Nokogiri::HTML(html) doc = Nokogiri::HTML5(html)
result = +"" result = +""
length = 0 length = 0

View File

@ -14,7 +14,7 @@ module Jobs
.where("cooked LIKE '%emoji%'") .where("cooked LIKE '%emoji%'")
.find_in_batches do |group| .find_in_batches do |group|
group.each do |p| group.each do |p|
doc = Nokogiri::HTML::fragment(p.cooked) doc = Nokogiri::HTML5::fragment(p.cooked)
if (doc.css("img.emoji") - doc.css(".quote img")).size > 0 if (doc.css("img.emoji") - doc.css(".quote img")).size > 0
to_award[p.user_id] ||= { post_id: p.id, created_at: p.created_at } to_award[p.user_id] ||= { post_id: p.id, created_at: p.created_at }
end end

View File

@ -19,7 +19,7 @@ module Jobs
begin begin
# Note we can't use `p.cooked` here because oneboxes have been cooked out # Note we can't use `p.cooked` here because oneboxes have been cooked out
cooked = PrettyText.cook(p.raw) cooked = PrettyText.cook(p.raw)
doc = Nokogiri::HTML::fragment(cooked) doc = Nokogiri::HTML5::fragment(cooked)
if doc.search('a.onebox').size > 0 if doc.search('a.onebox').size > 0
to_award[p.user_id] ||= { post_id: p.id, created_at: p.created_at } to_award[p.user_id] ||= { post_id: p.id, created_at: p.created_at }
end end

View File

@ -157,7 +157,7 @@ module Jobs
end end
def extract_images_from(html) def extract_images_from(html)
doc = Nokogiri::HTML::fragment(html) doc = Nokogiri::HTML5::fragment(html)
doc.css("img[src], a.lightbox[href]") - doc.css("img[src], a.lightbox[href]") -
doc.css("img.avatar") - doc.css("img.avatar") -

View File

@ -154,11 +154,11 @@ module Jobs
# and there is no reason to invalidate oneboxes, run the post analyzer etc. # and there is no reason to invalidate oneboxes, run the post analyzer etc.
# when only the username changes. # when only the username changes.
def update_cooked(cooked) def update_cooked(cooked)
doc = Nokogiri::HTML.fragment(cooked) doc = Nokogiri::HTML5.fragment(cooked)
doc.css("a.mention").each do |a| doc.css("a.mention").each do |a|
a.content = a.content.gsub(@cooked_mention_username_regex, "@#{@new_username}") a.content = a.content.gsub(@cooked_mention_username_regex, "@#{@new_username}")
a["href"] = a["href"].gsub(@cooked_mention_user_path_regex, "/u/#{@new_username}") if a["href"] a["href"] = a["href"].gsub(@cooked_mention_user_path_regex, "/u/#{URI.escape(@new_username)}") if a["href"]
end end
doc.css("aside.quote").each do |aside| doc.css("aside.quote").each do |aside|

View File

@ -306,7 +306,7 @@ class Category < ActiveRecord::Base
@@cache_text ||= LruRedux::ThreadSafeCache.new(1000) @@cache_text ||= LruRedux::ThreadSafeCache.new(1000)
@@cache_text.getset(self.description) do @@cache_text.getset(self.description) do
text = Nokogiri::HTML.fragment(self.description).text.strip text = Nokogiri::HTML5.fragment(self.description).text.strip
Rack::Utils.escape_html(text).html_safe Rack::Utils.escape_html(text).html_safe
end end
end end

View File

@ -953,7 +953,7 @@ class Post < ActiveRecord::Base
/\/uploads\/short-url\/[a-zA-Z0-9]+(\.[a-z0-9]+)?/ /\/uploads\/short-url\/[a-zA-Z0-9]+(\.[a-z0-9]+)?/
] ]
fragments ||= Nokogiri::HTML::fragment(self.cooked) fragments ||= Nokogiri::HTML5::fragment(self.cooked)
selectors = fragments.css("a/@href", "img/@src", "source/@src", "track/@src", "video/@poster") selectors = fragments.css("a/@href", "img/@src", "source/@src", "track/@src", "video/@poster")
links = selectors.map do |media| links = selectors.map do |media|

View File

@ -131,7 +131,7 @@ class PostAnalyzer
def cooked_stripped def cooked_stripped
@cooked_stripped ||= begin @cooked_stripped ||= begin
doc = Nokogiri::HTML.fragment(cook(@raw, topic_id: @topic_id)) doc = Nokogiri::HTML5.fragment(cook(@raw, topic_id: @topic_id))
doc.css("pre .mention, aside.quote > .title, aside.quote .mention, aside.quote .mention-group, .onebox, .elided").remove doc.css("pre .mention, aside.quote > .title, aside.quote .mention, aside.quote .mention-group, .onebox, .elided").remove
doc doc
end end

View File

@ -9,7 +9,7 @@ class QuotedPost < ActiveRecord::Base
# we are double parsing this fragment, this may be worth optimising later # we are double parsing this fragment, this may be worth optimising later
def self.extract_from(post) def self.extract_from(post)
doc = Nokogiri::HTML.fragment(post.cooked) doc = Nokogiri::HTML5.fragment(post.cooked)
uniq = {} uniq = {}

View File

@ -78,7 +78,7 @@ class ThemeField < ActiveRecord::Base
js_compiler = ThemeJavascriptCompiler.new(theme_id, self.theme.name) js_compiler = ThemeJavascriptCompiler.new(theme_id, self.theme.name)
doc = Nokogiri::HTML.fragment(html) doc = Nokogiri::HTML5.fragment(html)
doc.css('script[type="text/x-handlebars"]').each do |node| doc.css('script[type="text/x-handlebars"]').each do |node|
name = node["name"] || node["data-template-name"] || "broken" name = node["name"] || node["data-template-name"] || "broken"

View File

@ -126,7 +126,7 @@ class TopicEmbed < ActiveRecord::Base
return return
end end
raw_doc = Nokogiri::HTML(html) raw_doc = Nokogiri::HTML5(html)
auth_element = raw_doc.at('meta[@name="author"]') auth_element = raw_doc.at('meta[@name="author"]')
if auth_element.present? if auth_element.present?
response.author = User.where(username_lower: auth_element[:content].strip).first response.author = User.where(username_lower: auth_element[:content].strip).first
@ -142,7 +142,7 @@ class TopicEmbed < ActiveRecord::Base
title.strip! title.strip!
end end
response.title = title response.title = title
doc = Nokogiri::HTML(read_doc.content) doc = Nokogiri::HTML5(read_doc.content)
tags = { 'img' => 'src', 'script' => 'src', 'a' => 'href' } tags = { 'img' => 'src', 'script' => 'src', 'a' => 'href' }
doc.search(tags.keys.join(',')).each do |node| doc.search(tags.keys.join(',')).each do |node|
@ -198,7 +198,7 @@ class TopicEmbed < ActiveRecord::Base
prefix = "#{uri.scheme}://#{uri.host}" prefix = "#{uri.scheme}://#{uri.host}"
prefix += ":#{uri.port}" if uri.port != 80 && uri.port != 443 prefix += ":#{uri.port}" if uri.port != 80 && uri.port != 443
fragment = Nokogiri::HTML.fragment("<div>#{contents}</div>") fragment = Nokogiri::HTML5.fragment("<div>#{contents}</div>")
fragment.css('a').each do |a| fragment.css('a').each do |a|
href = a['href'] href = a['href']
if href.present? && href.start_with?('/') if href.present? && href.start_with?('/')
@ -220,7 +220,7 @@ class TopicEmbed < ActiveRecord::Base
end end
def self.first_paragraph_from(html) def self.first_paragraph_from(html)
doc = Nokogiri::HTML(html) doc = Nokogiri::HTML5(html)
result = +"" result = +""
doc.css('p').each do |p| doc.css('p').each do |p|

View File

@ -16,7 +16,7 @@ class InlineUploads
end end
end end
cooked_fragment = Nokogiri::HTML::fragment(PrettyText.cook(markdown, disable_emojis: true)) cooked_fragment = Nokogiri::HTML5::fragment(PrettyText.cook(markdown, disable_emojis: true))
link_occurences = [] link_occurences = []
cooked_fragment.traverse do |node| cooked_fragment.traverse do |node|
@ -183,7 +183,7 @@ class InlineUploads
def self.match_anchor(markdown, external_href: false) def self.match_anchor(markdown, external_href: false)
markdown.scan(/((<a[^<]+>)([^<\a>]*?)<\/a>)/i) do |match| markdown.scan(/((<a[^<]+>)([^<\a>]*?)<\/a>)/i) do |match|
node = Nokogiri::HTML::fragment(match[0]).children[0] node = Nokogiri::HTML5::fragment(match[0]).children[0]
href = node.attributes["href"]&.value href = node.attributes["href"]&.value
if href && (matched_uploads(href).present? || external_href) if href && (matched_uploads(href).present? || external_href)
@ -199,7 +199,7 @@ class InlineUploads
def self.match_img(markdown, external_src: false) def self.match_img(markdown, external_src: false)
markdown.scan(/(<(?!img)[^<>]+\/?>)?(\s*)(<img [^>\n]+>)/i) do |match| markdown.scan(/(<(?!img)[^<>]+\/?>)?(\s*)(<img [^>\n]+>)/i) do |match|
node = Nokogiri::HTML::fragment(match[2].strip).children[0] node = Nokogiri::HTML5::fragment(match[2].strip).children[0]
src = node.attributes["src"]&.value src = node.attributes["src"]&.value
if src && (matched_uploads(src).present? || external_src) if src && (matched_uploads(src).present? || external_src)

View File

@ -191,7 +191,7 @@ class SearchIndexer
def self.scrub(html, strip_diacritics: false) def self.scrub(html, strip_diacritics: false)
return +"" if html.blank? return +"" if html.blank?
document = Nokogiri::HTML("<div>#{html}</div>", nil, Encoding::UTF_8.to_s) document = Nokogiri::HTML5("<div>#{html}</div>", nil, Encoding::UTF_8.to_s)
nodes = document.css( nodes = document.css(
"div.#{CookedPostProcessor::LIGHTBOX_WRAPPER_CSS_CLASS}" "div.#{CookedPostProcessor::LIGHTBOX_WRAPPER_CSS_CLASS}"

View File

@ -8,7 +8,7 @@ class BackfillPostUploadReverseIndex < ActiveRecord::Migration[4.2]
# fill the reverse index up # fill the reverse index up
Post.select([:id, :cooked]).find_each do |post| Post.select([:id, :cooked]).find_each do |post|
doc = Nokogiri::HTML::fragment(post.cooked) doc = Nokogiri::HTML5::fragment(post.cooked)
# images # images
doc.search("img").each { |img| add_to_reverse_index(img['src'], post.id) } doc.search("img").each { |img| add_to_reverse_index(img['src'], post.id) }
# thumbnails and/or attachments # thumbnails and/or attachments

View File

@ -30,7 +30,7 @@ SQL
results.each do |row| results.each do |row|
post_id, max_id = row["id"].to_i post_id, max_id = row["id"].to_i
doc = Nokogiri::HTML.fragment(row["cooked"]) doc = Nokogiri::HTML5.fragment(row["cooked"])
uniq = {} uniq = {}

View File

@ -61,7 +61,8 @@ class ContentSecurityPolicy
auto_script_src_extension = { script_src: [] } auto_script_src_extension = { script_src: [] }
html_fields.each(&:ensure_baked!) html_fields.each(&:ensure_baked!)
doc = html_fields.map(&:value_baked).join("\n") doc = html_fields.map(&:value_baked).join("\n")
Nokogiri::HTML.fragment(doc).css('script[src]').each do |node|
Nokogiri::HTML5.fragment(doc).css('script[src]').each do |node|
src = node['src'] src = node['src']
uri = URI(src) uri = URI(src)

View File

@ -24,7 +24,7 @@ class CookedPostProcessor
@cooking_options = @cooking_options.symbolize_keys @cooking_options = @cooking_options.symbolize_keys
cooked = post.cook(post.raw, @cooking_options) cooked = post.cook(post.raw, @cooking_options)
@doc = Nokogiri::HTML::fragment(cooked) @doc = Nokogiri::HTML5::fragment(cooked)
@has_oneboxes = post.post_analyzer.found_oneboxes? @has_oneboxes = post.post_analyzer.found_oneboxes?
@size_cache = {} @size_cache = {}
@ -95,7 +95,7 @@ class CookedPostProcessor
return if previous.blank? return if previous.blank?
previous_text = Nokogiri::HTML::fragment(previous).text.strip previous_text = Nokogiri::HTML5::fragment(previous).text.strip
quoted_text = @doc.css("aside.quote:first-child blockquote").first&.text&.strip || "" quoted_text = @doc.css("aside.quote:first-child blockquote").first&.text&.strip || ""
return if previous_text.gsub(/(\s){2,}/, '\1') != quoted_text.gsub(/(\s){2,}/, '\1') return if previous_text.gsub(/(\s){2,}/, '\1') != quoted_text.gsub(/(\s){2,}/, '\1')

View File

@ -168,7 +168,7 @@ class DiscourseDiff
end end
def tokenize_html_blocks(html) def tokenize_html_blocks(html)
Nokogiri::HTML.fragment(html).search("./*").map(&:to_html) Nokogiri::HTML5.fragment(html).search("./*").map(&:to_html)
end end
def tokenize_html(html) def tokenize_html(html)

View File

@ -338,7 +338,7 @@ module Email
markdown, elided_markdown = if html.present? markdown, elided_markdown = if html.present?
# use the first html extracter that matches # use the first html extracter that matches
if html_extracter = HTML_EXTRACTERS.select { |_, r| html[r] }.min_by { |_, r| html =~ r } if html_extracter = HTML_EXTRACTERS.select { |_, r| html[r] }.min_by { |_, r| html =~ r }
doc = Nokogiri::HTML.fragment(html) doc = Nokogiri::HTML5.fragment(html)
self.public_send(:"extract_from_#{html_extracter[0]}", doc) self.public_send(:"extract_from_#{html_extracter[0]}", doc)
else else
markdown = HtmlToMarkdown.new(html, keep_img_tags: true, keep_cid_imgs: true).to_markdown markdown = HtmlToMarkdown.new(html, keep_img_tags: true, keep_cid_imgs: true).to_markdown

View File

@ -15,7 +15,7 @@ module Email
def initialize(html, opts = nil) def initialize(html, opts = nil)
@html = html @html = html
@opts = opts || {} @opts = opts || {}
@fragment = Nokogiri::HTML.fragment(@html) @fragment = Nokogiri::HTML5.parse(@html)
@custom_styles = nil @custom_styles = nil
end end
@ -161,7 +161,7 @@ module Email
src_uri = i["data-original-href"].present? ? URI(i["data-original-href"]) : URI(i['src']) src_uri = i["data-original-href"].present? ? URI(i["data-original-href"]) : URI(i['src'])
# If an iframe is protocol relative, use SSL when displaying it # If an iframe is protocol relative, use SSL when displaying it
display_src = "#{src_uri.scheme || 'https'}://#{src_uri.host}#{src_uri.path}#{src_uri.query.nil? ? '' : '?' + src_uri.query}#{src_uri.fragment.nil? ? '' : '#' + src_uri.fragment}" display_src = "#{src_uri.scheme || 'https'}://#{src_uri.host}#{src_uri.path}#{src_uri.query.nil? ? '' : '?' + src_uri.query}#{src_uri.fragment.nil? ? '' : '#' + src_uri.fragment}"
i.replace "<p><a href='#{src_uri.to_s}'>#{CGI.escapeHTML(display_src)}</a><p>" i.replace(Nokogiri::HTML5.fragment("<p><a href='#{src_uri.to_s}'>#{CGI.escapeHTML(display_src)}</a><p>"))
rescue URI::Error rescue URI::Error
# If the URL is weird, remove the iframe # If the URL is weird, remove the iframe
i.remove i.remove
@ -242,7 +242,11 @@ module Email
strip_classes_and_ids strip_classes_and_ids
replace_relative_urls replace_relative_urls
replace_secure_media_urls replace_secure_media_urls
@fragment.to_html include_body? ? @fragment.at("body").to_html : @fragment.at("body").children.to_html
end
def include_body?
@html =~ /<body>/i
end end
def strip_avatars_and_emojis def strip_avatars_and_emojis

View File

@ -24,7 +24,7 @@ module Onebox
return true if WhitelistedGenericOnebox.html_providers.include?(data[:provider_name]) return true if WhitelistedGenericOnebox.html_providers.include?(data[:provider_name])
if data[:html]["iframe"] if data[:html]["iframe"]
fragment = Nokogiri::HTML::fragment(data[:html]) fragment = Nokogiri::HTML5::fragment(data[:html])
if iframe = fragment.at_css("iframe") if iframe = fragment.at_css("iframe")
src = iframe["src"] src = iframe["src"]
return src.present? && SiteSetting.allowed_iframes.split("|").any? { |url| src.start_with?(url) } return src.present? && SiteSetting.allowed_iframes.split("|").any? { |url| src.start_with?(url) }

View File

@ -78,7 +78,7 @@ module Oneboxer
# Parse URLs out of HTML, returning the document when finished. # Parse URLs out of HTML, returning the document when finished.
def self.each_onebox_link(string_or_doc, extra_paths: []) def self.each_onebox_link(string_or_doc, extra_paths: [])
doc = string_or_doc doc = string_or_doc
doc = Nokogiri::HTML::fragment(doc) if doc.is_a?(String) doc = Nokogiri::HTML5::fragment(doc) if doc.is_a?(String)
onebox_links = doc.css("a.#{ONEBOX_CSS_CLASS}", *extra_paths) onebox_links = doc.css("a.#{ONEBOX_CSS_CLASS}", *extra_paths)
if onebox_links.present? if onebox_links.present?
@ -94,14 +94,14 @@ module Oneboxer
def self.apply(string_or_doc, extra_paths: nil) def self.apply(string_or_doc, extra_paths: nil)
doc = string_or_doc doc = string_or_doc
doc = Nokogiri::HTML::fragment(doc) if doc.is_a?(String) doc = Nokogiri::HTML5::fragment(doc) if doc.is_a?(String)
changed = false changed = false
each_onebox_link(doc, extra_paths: extra_paths) do |url, element| each_onebox_link(doc, extra_paths: extra_paths) do |url, element|
onebox, _ = yield(url, element) onebox, _ = yield(url, element)
if onebox if onebox
parsed_onebox = Nokogiri::HTML::fragment(onebox) parsed_onebox = Nokogiri::HTML5::fragment(onebox)
next unless parsed_onebox.children.count > 0 next unless parsed_onebox.children.count > 0
if element&.parent&.node_name&.downcase == "p" && if element&.parent&.node_name&.downcase == "p" &&

View File

@ -579,7 +579,7 @@ class PostRevisor
def update_category_description def update_category_description
return unless category = Category.find_by(topic_id: @topic.id) return unless category = Category.find_by(topic_id: @topic.id)
doc = Nokogiri::HTML.fragment(@post.cooked) doc = Nokogiri::HTML5.fragment(@post.cooked)
doc.css("img").remove doc.css("img").remove
if html = doc.css("p").first&.inner_html&.strip if html = doc.css("p").first&.inner_html&.strip

View File

@ -259,7 +259,7 @@ module PrettyText
sanitized = markdown(working_text, options) sanitized = markdown(working_text, options)
doc = Nokogiri::HTML.fragment(sanitized) doc = Nokogiri::HTML5.fragment(sanitized)
if !options[:omit_nofollow] && SiteSetting.add_rel_nofollow_to_user_content if !options[:omit_nofollow] && SiteSetting.add_rel_nofollow_to_user_content
add_rel_nofollow_to_user_content(doc) add_rel_nofollow_to_user_content(doc)
@ -269,7 +269,11 @@ module PrettyText
add_mentions(doc, user_id: opts[:user_id]) add_mentions(doc, user_id: opts[:user_id])
end end
doc.to_html scrubber = Loofah::Scrubber.new do |node|
node.remove if node.name == 'script'
end
loofah_fragment = Loofah.fragment(doc.to_html)
loofah_fragment.scrub!(scrubber).to_html
end end
def self.add_rel_nofollow_to_user_content(doc) def self.add_rel_nofollow_to_user_content(doc)
@ -282,7 +286,7 @@ module PrettyText
doc.css("a").each do |l| doc.css("a").each do |l|
href = l["href"].to_s href = l["href"].to_s
begin begin
uri = URI(href) uri = URI(URI.escape(href))
site_uri ||= URI(Discourse.base_url) site_uri ||= URI(Discourse.base_url)
if !uri.host.present? || if !uri.host.present? ||
@ -305,7 +309,7 @@ module PrettyText
def self.extract_links(html) def self.extract_links(html)
links = [] links = []
doc = Nokogiri::HTML.fragment(html) doc = Nokogiri::HTML5.fragment(html)
# remove href inside quotes & elided part # remove href inside quotes & elided part
doc.css("aside.quote a, .elided a").each { |a| a["href"] = "" } doc.css("aside.quote a, .elided a").each { |a| a["href"] = "" }
@ -338,7 +342,7 @@ module PrettyText
def self.excerpt(html, max_length, options = {}) def self.excerpt(html, max_length, options = {})
# TODO: properly fix this HACK in ExcerptParser without introducing XSS # TODO: properly fix this HACK in ExcerptParser without introducing XSS
doc = Nokogiri::HTML.fragment(html) doc = Nokogiri::HTML5.fragment(html)
DiscourseEvent.trigger(:reduce_excerpt, doc, options) DiscourseEvent.trigger(:reduce_excerpt, doc, options)
strip_image_wrapping(doc) strip_image_wrapping(doc)
strip_oneboxed_media(doc) strip_oneboxed_media(doc)
@ -350,7 +354,7 @@ module PrettyText
return string if string.blank? return string if string.blank?
# If the user is not basic, strip links from their bio # If the user is not basic, strip links from their bio
fragment = Nokogiri::HTML.fragment(string) fragment = Nokogiri::HTML5.fragment(string)
fragment.css('a').each { |a| a.replace(a.inner_html) } fragment.css('a').each { |a| a.replace(a.inner_html) }
fragment.to_html fragment.to_html
end end
@ -395,14 +399,14 @@ module PrettyText
def self.strip_secure_media(doc) def self.strip_secure_media(doc)
doc.css("a[href]").each do |a| doc.css("a[href]").each do |a|
if Upload.secure_media_url?(a["href"]) if Upload.secure_media_url?(a["href"])
target = %w(video audio).include?(a&.parent&.parent&.name) ? a.parent.parent : a target = %w(video audio).include?(a&.parent&.name) ? a.parent : a
target.replace "<p class='secure-media-notice'>#{I18n.t("emails.secure_media_placeholder")}</p>" target.replace "<p class='secure-media-notice'>#{I18n.t("emails.secure_media_placeholder")}</p>"
end end
end end
end end
def self.format_for_email(html, post = nil) def self.format_for_email(html, post = nil)
doc = Nokogiri::HTML.fragment(html) doc = Nokogiri::HTML5.fragment(html)
DiscourseEvent.trigger(:reduce_cooked, doc, post) DiscourseEvent.trigger(:reduce_cooked, doc, post)
strip_secure_media(doc) if post&.with_secure_media? strip_secure_media(doc) if post&.with_secure_media?
strip_image_wrapping(doc) strip_image_wrapping(doc)
@ -462,13 +466,13 @@ module PrettyText
case type case type
when USER_TYPE when USER_TYPE
element['href'] = "#{Discourse::base_uri}/u/#{name}" element['href'] = "#{Discourse::base_uri}/u/#{URI.escape(name)}"
when GROUP_MENTIONABLE_TYPE when GROUP_MENTIONABLE_TYPE
element['class'] = 'mention-group notify' element['class'] = 'mention-group notify'
element['href'] = "#{Discourse::base_uri}/groups/#{name}" element['href'] = "#{Discourse::base_uri}/groups/#{URI.escape(name)}"
when GROUP_TYPE when GROUP_TYPE
element['class'] = 'mention-group' element['class'] = 'mention-group'
element['href'] = "#{Discourse::base_uri}/groups/#{name}" element['href'] = "#{Discourse::base_uri}/groups/#{URI.escape(name)}"
end end
end end
end end

View File

@ -18,7 +18,7 @@ class QuoteComparer
def modified? def modified?
return true if @text.blank? || @parent_post.blank? return true if @text.blank? || @parent_post.blank?
parent_text = Nokogiri::HTML::fragment(@parent_post.cooked).text.delete(QuoteComparer.whitespace) parent_text = Nokogiri::HTML5::fragment(@parent_post.cooked).text.delete(QuoteComparer.whitespace)
text = @text.delete(QuoteComparer.whitespace) text = @text.delete(QuoteComparer.whitespace)
!parent_text.include?(text) !parent_text.include?(text)

View File

@ -11,7 +11,7 @@ module RetrieveTitle
def self.extract_title(html) def self.extract_title(html)
title = nil title = nil
if doc = Nokogiri::HTML(html) if doc = Nokogiri::HTML5(html)
title = doc.at('title')&.inner_text title = doc.at('title')&.inner_text

View File

@ -17,7 +17,7 @@ class Reviewable < ActiveRecord::Base
def self.excerpt(cooked) def self.excerpt(cooked)
excerpt = ::Post.excerpt(cooked, 250, keep_emoji_images: true) excerpt = ::Post.excerpt(cooked, 250, keep_emoji_images: true)
# remove the first link if it's the first node # remove the first link if it's the first node
fragment = Nokogiri::HTML.fragment(excerpt) fragment = Nokogiri::HTML5.fragment(excerpt)
if fragment.children.first == fragment.css("a:first").first && fragment.children.first if fragment.children.first == fragment.css("a:first").first && fragment.children.first
fragment.children.first.remove fragment.children.first.remove
end end

View File

@ -353,7 +353,7 @@ def generate_emoji_groups(keywords, sections)
puts "Generating groups..." puts "Generating groups..."
list = open(EMOJI_ORDERING_URL).read list = open(EMOJI_ORDERING_URL).read
doc = Nokogiri::HTML(list) doc = Nokogiri::HTML5(list)
table = doc.css("table")[0] table = doc.css("table")[0]
EMOJI_GROUPS.map do |group| EMOJI_GROUPS.map do |group|

View File

@ -8,7 +8,7 @@ describe PrettyText do
let(:post) { Fabricate(:post) } let(:post) { Fabricate(:post) }
it "supports details tag" do it "supports details tag" do
cooked_html = <<~HTML cooked_html = <<~HTML.gsub("\n", "")
<details> <details>
<summary> <summary>
foo</summary> foo</summary>
@ -17,7 +17,7 @@ describe PrettyText do
HTML HTML
expect(cooked_html).to match_html(cooked_html) expect(cooked_html).to match_html(cooked_html)
expect(PrettyText.cook("[details=foo]\nbar\n[/details]")).to match_html(cooked_html) expect(PrettyText.cook("[details=foo]\nbar\n[/details]").gsub("\n", "")).to match_html(cooked_html)
end end
it "deletes elided content" do it "deletes elided content" do

View File

@ -68,7 +68,7 @@ module DiscourseNarrativeBot
end end
def bot_mentioned?(post) def bot_mentioned?(post)
doc = Nokogiri::HTML.fragment(post.cooked) doc = Nokogiri::HTML5.fragment(post.cooked)
valid = false valid = false

View File

@ -280,7 +280,7 @@ module DiscourseNarrativeBot
topic_id = @post.topic_id topic_id = @post.topic_id
return unless valid_topic?(topic_id) return unless valid_topic?(topic_id)
if Nokogiri::HTML.fragment(@post.cooked).css('.hashtag').size > 0 if Nokogiri::HTML5.fragment(@post.cooked).css('.hashtag').size > 0
raw = <<~RAW raw = <<~RAW
#{I18n.t("#{I18N_KEY}.category_hashtag.reply", i18n_post_args)} #{I18n.t("#{I18N_KEY}.category_hashtag.reply", i18n_post_args)}
@ -331,7 +331,7 @@ module DiscourseNarrativeBot
topic_id = @post.topic_id topic_id = @post.topic_id
return unless valid_topic?(topic_id) return unless valid_topic?(topic_id)
if Nokogiri::HTML.fragment(@post.cooked).css(".poll").size > 0 if Nokogiri::HTML5.fragment(@post.cooked).css(".poll").size > 0
raw = <<~RAW raw = <<~RAW
#{I18n.t("#{I18N_KEY}.poll.reply", i18n_post_args)} #{I18n.t("#{I18N_KEY}.poll.reply", i18n_post_args)}
@ -354,7 +354,7 @@ module DiscourseNarrativeBot
fake_delay fake_delay
if Nokogiri::HTML.fragment(@post.cooked).css("details").size > 0 if Nokogiri::HTML5.fragment(@post.cooked).css("details").size > 0
reply_to(@post, I18n.t("#{I18N_KEY}.details.reply", i18n_post_args)) reply_to(@post, I18n.t("#{I18N_KEY}.details.reply", i18n_post_args))
else else
reply_to(@post, I18n.t("#{I18N_KEY}.details.not_found", i18n_post_args)) unless @data[:attempted] reply_to(@post, I18n.t("#{I18N_KEY}.details.not_found", i18n_post_args)) unless @data[:attempted]

View File

@ -326,7 +326,7 @@ module DiscourseNarrativeBot
cooked = @post.post_analyzer.cook(@post.raw, {}) cooked = @post.post_analyzer.cook(@post.raw, {})
if Nokogiri::HTML.fragment(cooked).css("img").size > 0 if Nokogiri::HTML5.fragment(cooked).css("img").size > 0
set_state_data(:post_id, @post.id) set_state_data(:post_id, @post.id)
if get_state_data(:liked) if get_state_data(:liked)
@ -366,7 +366,7 @@ module DiscourseNarrativeBot
post_topic_id = @post.topic_id post_topic_id = @post.topic_id
return unless valid_topic?(post_topic_id) return unless valid_topic?(post_topic_id)
if Nokogiri::HTML.fragment(@post.cooked).css("b", "strong", "em", "i", ".bbcode-i", ".bbcode-b").size > 0 if Nokogiri::HTML5.fragment(@post.cooked).css("b", "strong", "em", "i", ".bbcode-i", ".bbcode-b").size > 0
raw = <<~RAW raw = <<~RAW
#{I18n.t("#{I18N_KEY}.formatting.reply", i18n_post_args)} #{I18n.t("#{I18N_KEY}.formatting.reply", i18n_post_args)}
@ -390,7 +390,7 @@ module DiscourseNarrativeBot
post_topic_id = @post.topic_id post_topic_id = @post.topic_id
return unless valid_topic?(post_topic_id) return unless valid_topic?(post_topic_id)
doc = Nokogiri::HTML.fragment(@post.cooked) doc = Nokogiri::HTML5.fragment(@post.cooked)
if doc.css(".quote").size > 0 if doc.css(".quote").size > 0
raw = <<~RAW raw = <<~RAW
@ -416,7 +416,7 @@ module DiscourseNarrativeBot
post_topic_id = @post.topic_id post_topic_id = @post.topic_id
return unless valid_topic?(post_topic_id) return unless valid_topic?(post_topic_id)
doc = Nokogiri::HTML.fragment(@post.cooked) doc = Nokogiri::HTML5.fragment(@post.cooked)
if doc.css(".emoji").size > 0 if doc.css(".emoji").size > 0
raw = <<~RAW raw = <<~RAW

View File

@ -350,7 +350,7 @@ after_initialize do
# in the validators instead of cooking twice # in the validators instead of cooking twice
cooked = PrettyText.cook(raw, topic_id: topic_id, user_id: user_id) cooked = PrettyText.cook(raw, topic_id: topic_id, user_id: user_id)
Nokogiri::HTML(cooked).css("div.poll").map do |p| Nokogiri::HTML5(cooked).css("div.poll").map do |p|
poll = { "options" => [], "name" => DiscoursePoll::DEFAULT_POLL_NAME } poll = { "options" => [], "name" => DiscoursePoll::DEFAULT_POLL_NAME }
# attributes # attributes

View File

@ -131,7 +131,7 @@ describe PrettyText do
MD MD
onebox = Oneboxer.onebox_raw(post.full_url, user_id: Fabricate(:user).id) onebox = Oneboxer.onebox_raw(post.full_url, user_id: Fabricate(:user).id)
doc = Nokogiri::HTML(onebox[:preview]) doc = Nokogiri::HTML5(onebox[:preview])
expect(onebox[:preview]).to include("A post with a poll") expect(onebox[:preview]).to include("A post with a poll")
expect(onebox[:preview]).to include("<a href=\"#{post.url}\">poll</a>") expect(onebox[:preview]).to include("<a href=\"#{post.url}\">poll</a>")

View File

@ -376,7 +376,7 @@ class ImportScripts::IPBoard3 < ImportScripts::Base
raw.gsub!(/<(.+)>&nbsp;<\/\1>/, "\n\n") raw.gsub!(/<(.+)>&nbsp;<\/\1>/, "\n\n")
doc = Nokogiri::HTML.fragment(raw) doc = Nokogiri::HTML5.fragment(raw)
doc.css("blockquote.ipsBlockquote").each do |bq| doc.css("blockquote.ipsBlockquote").each do |bq|
post_id = post_id_from_imported_post_id(bq["data-cid"]) post_id = post_id_from_imported_post_id(bq["data-cid"])

View File

@ -218,7 +218,7 @@ class ImportScripts::Jive < ImportScripts::Base
raw = raw.dup raw = raw.dup
raw = raw[5..-6] raw = raw[5..-6]
doc = Nokogiri::HTML.fragment(raw) doc = Nokogiri::HTML5.fragment(raw)
doc.css('img').each do |img| doc.css('img').each do |img|
img.remove if img['class'] == "jive-image" img.remove if img['class'] == "jive-image"
end end

View File

@ -297,7 +297,7 @@ class ImportScripts::JiveApi < ImportScripts::Base
end end
def process_raw(raw) def process_raw(raw)
doc = Nokogiri::HTML.fragment(raw) doc = Nokogiri::HTML5.fragment(raw)
# convert emoticon # convert emoticon
doc.css("span.emoticon-inline").each do |span| doc.css("span.emoticon-inline").each do |span|

View File

@ -913,7 +913,7 @@ SQL
raw.sub!(match, content) raw.sub!(match, content)
end end
doc = Nokogiri::HTML.fragment(raw) doc = Nokogiri::HTML5.fragment(raw)
doc.css("a,img,li-image").each do |l| doc.css("a,img,li-image").each do |l|
upload_name, image, linked_upload = [nil] * 3 upload_name, image, linked_upload = [nil] * 3

View File

@ -453,10 +453,8 @@ describe CookedPostProcessor do
it "generates overlay information" do it "generates overlay information" do
cpp.post_process cpp.post_process
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"> <p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg>
</div></a></div></p>
HTML HTML
expect(cpp).to be_dirty expect(cpp).to be_dirty
@ -475,7 +473,7 @@ describe CookedPostProcessor do
cpp.post_process cpp.post_process
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p><img class="onebox" src="//test.localhost/#{upload_path}/original/1X/1234567890123456.jpg" width="690" height="788"></p> <p><img class="onebox" src="//test.localhost/#{upload_path}/original/1X/1234567890123456.jpg" width="690" height="788"></p>
HTML HTML
end end
@ -491,7 +489,7 @@ describe CookedPostProcessor do
cpp.post_process cpp.post_process
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p><img src="//test.localhost/#{upload_path}/original/1X/1234567890123456.svg" width="690" height="788"></p> <p><img src="//test.localhost/#{upload_path}/original/1X/1234567890123456.svg" width="690" height="788"></p>
HTML HTML
end end
@ -619,10 +617,8 @@ describe CookedPostProcessor do
it "crops the image" do it "crops the image" do
cpp.post_process cpp.post_process
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_230x500.png" width="230" height="500"><div class="meta"> <p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_230x500.png" width="230" height="500"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1125×2436 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1125×2436 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg>
</div></a></div></p>
HTML HTML
expect(cpp).to be_dirty expect(cpp).to be_dirty
@ -652,10 +648,8 @@ describe CookedPostProcessor do
it "generates overlay information" do it "generates overlay information" do
cpp.post_process cpp.post_process
expect(cpp.html). to match_html <<~HTML expect(cpp.html). to match_html <<~HTML.rstrip
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost/subfolder#{upload.url}" data-download-href="//test.localhost/subfolder/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/subfolder/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"> <p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost/subfolder#{upload.url}" data-download-href="//test.localhost/subfolder/#{upload_path}/#{upload.sha1}" title="logo.png"><img src="//test.localhost/subfolder/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">logo.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg>
</div></a></div></p>
HTML HTML
expect(cpp).to be_dirty expect(cpp).to be_dirty
@ -665,10 +659,8 @@ describe CookedPostProcessor do
upload.update!(original_filename: "><img src=x onerror=alert('haha')>.png") upload.update!(original_filename: "><img src=x onerror=alert('haha')>.png")
cpp.post_process cpp.post_process
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost/subfolder#{upload.url}" data-download-href="//test.localhost/subfolder/#{upload_path}/#{upload.sha1}" title="&amp;gt;&amp;lt;img src=x onerror=alert(&amp;#39;haha&amp;#39;)&amp;gt;.png"><img src="//test.localhost/subfolder/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"> <p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost/subfolder#{upload.url}" data-download-href="//test.localhost/subfolder/#{upload_path}/#{upload.sha1}" title="&amp;gt;&amp;lt;img src=x onerror=alert(&amp;#39;haha&amp;#39;)&amp;gt;.png"><img src="//test.localhost/subfolder/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">&amp;gt;&amp;lt;img src=x onerror=alert(&amp;#39;haha&amp;#39;)&amp;gt;.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">&amp;gt;&amp;lt;img src=x onerror=alert(&amp;#39;haha&amp;#39;)&amp;gt;.png</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg>
</div></a></div></p>
HTML HTML
end end
@ -693,10 +685,8 @@ describe CookedPostProcessor do
it "generates overlay information using image title and ignores alt" do it "generates overlay information using image title and ignores alt" do
cpp.post_process cpp.post_process
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="WAT"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" title="WAT" alt="RED" width="690" height="788"><div class="meta"> <p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="WAT"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" title="WAT" alt="RED" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">WAT</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">WAT</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg>
</div></a></div></p>
HTML HTML
expect(cpp).to be_dirty expect(cpp).to be_dirty
@ -723,10 +713,8 @@ describe CookedPostProcessor do
it "generates overlay information using image title" do it "generates overlay information using image title" do
cpp.post_process cpp.post_process
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="WAT"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" title="WAT" width="690" height="788"><div class="meta"> <p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="WAT"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" title="WAT" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">WAT</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">WAT</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg>
</div></a></div></p>
HTML HTML
expect(cpp).to be_dirty expect(cpp).to be_dirty
@ -753,10 +741,8 @@ describe CookedPostProcessor do
it "generates overlay information using image alt" do it "generates overlay information using image alt" do
cpp.post_process cpp.post_process
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="RED"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" alt="RED" width="690" height="788"><div class="meta"> <p><div class="lightbox-wrapper"><a class="lightbox" href="//test.localhost#{upload.url}" data-download-href="//test.localhost/#{upload_path}/#{upload.sha1}" title="RED"><img src="//test.localhost/#{upload_path}/optimized/1X/#{upload.sha1}_#{OptimizedImage::VERSION}_690x788.png" alt="RED" width="690" height="788"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">RED</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg></div></a></div></p>
<svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use xlink:href="#far-image"></use></svg><span class="filename">RED</span><span class="informations">1750×2000 1.21 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use xlink:href="#discourse-expand"></use></svg>
</div></a></div></p>
HTML HTML
expect(cpp).to be_dirty expect(cpp).to be_dirty
@ -993,7 +979,7 @@ describe CookedPostProcessor do
cpp = CookedPostProcessor.new(post, disable_loading_image: true) cpp = CookedPostProcessor.new(post, disable_loading_image: true)
cpp.post_process cpp.post_process
doc = Nokogiri::HTML::fragment(cpp.html) doc = Nokogiri::HTML5::fragment(cpp.html)
expect(doc.css('.lightbox-wrapper').size).to eq(1) expect(doc.css('.lightbox-wrapper').size).to eq(1)
expect(doc.css('img').first['srcset']).to_not eq(nil) expect(doc.css('img').first['srcset']).to_not eq(nil)
end end
@ -1008,7 +994,7 @@ describe CookedPostProcessor do
cpp = CookedPostProcessor.new(post, disable_loading_image: true) cpp = CookedPostProcessor.new(post, disable_loading_image: true)
cpp.post_process cpp.post_process
doc = Nokogiri::HTML::fragment(cpp.html) doc = Nokogiri::HTML5::fragment(cpp.html)
expect(doc.css('.lightbox-wrapper').size).to eq(0) expect(doc.css('.lightbox-wrapper').size).to eq(0)
expect(doc.css('img').first['srcset']).to_not eq(nil) expect(doc.css('img').first['srcset']).to_not eq(nil)
end end
@ -1023,7 +1009,7 @@ describe CookedPostProcessor do
cpp = CookedPostProcessor.new(post, disable_loading_image: true) cpp = CookedPostProcessor.new(post, disable_loading_image: true)
cpp.post_process cpp.post_process
doc = Nokogiri::HTML::fragment(cpp.html) doc = Nokogiri::HTML5::fragment(cpp.html)
expect(doc.css('.lightbox-wrapper').size).to eq(0) expect(doc.css('.lightbox-wrapper').size).to eq(0)
expect(doc.css('img').first['srcset']).to_not eq(nil) expect(doc.css('img').first['srcset']).to_not eq(nil)
end end
@ -1227,7 +1213,7 @@ describe CookedPostProcessor do
it "uses schemaless url for uploads" do it "uses schemaless url for uploads" do
cpp.optimize_urls cpp.optimize_urls
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p><a href="//test.localhost/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br> <p><a href="//test.localhost/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
<img src="//test.localhost/#{upload_path}/original/1X/1234567890123456.jpg"><br> <img src="//test.localhost/#{upload_path}/original/1X/1234567890123456.jpg"><br>
<a href="http://www.google.com" rel="nofollow noopener">Google</a><br> <a href="http://www.google.com" rel="nofollow noopener">Google</a><br>
@ -1242,7 +1228,7 @@ describe CookedPostProcessor do
it "uses schemaless CDN url for http uploads" do it "uses schemaless CDN url for http uploads" do
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com") Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
cpp.optimize_urls cpp.optimize_urls
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br> <p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
<img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br> <img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
<a href="http://www.google.com" rel="nofollow noopener">Google</a><br> <a href="http://www.google.com" rel="nofollow noopener">Google</a><br>
@ -1255,7 +1241,7 @@ describe CookedPostProcessor do
it "doesn't use schemaless CDN url for https uploads" do it "doesn't use schemaless CDN url for https uploads" do
Rails.configuration.action_controller.stubs(:asset_host).returns("https://my.cdn.com") Rails.configuration.action_controller.stubs(:asset_host).returns("https://my.cdn.com")
cpp.optimize_urls cpp.optimize_urls
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p><a href="https://my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br> <p><a href="https://my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
<img src="https://my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br> <img src="https://my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
<a href="http://www.google.com" rel="nofollow noopener">Google</a><br> <a href="http://www.google.com" rel="nofollow noopener">Google</a><br>
@ -1269,7 +1255,7 @@ describe CookedPostProcessor do
SiteSetting.login_required = true SiteSetting.login_required = true
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com") Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
cpp.optimize_urls cpp.optimize_urls
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br> <p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
<img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br> <img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
<a href="http://www.google.com" rel="nofollow noopener">Google</a><br> <a href="http://www.google.com" rel="nofollow noopener">Google</a><br>
@ -1283,7 +1269,7 @@ describe CookedPostProcessor do
SiteSetting.prevent_anons_from_downloading_files = true SiteSetting.prevent_anons_from_downloading_files = true
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com") Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
cpp.optimize_urls cpp.optimize_urls
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br> <p><a href="//my.cdn.com/#{upload_path}/original/2X/2345678901234567.jpg">Link</a><br>
<img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br> <img src="//my.cdn.com/#{upload_path}/original/1X/1234567890123456.jpg"><br>
<a href="http://www.google.com" rel="nofollow noopener">Google</a><br> <a href="http://www.google.com" rel="nofollow noopener">Google</a><br>
@ -1318,7 +1304,7 @@ describe CookedPostProcessor do
cpp = CookedPostProcessor.new(the_post) cpp = CookedPostProcessor.new(the_post)
cpp.optimize_urls cpp.optimize_urls
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p>This post has a local emoji <img src="https://local.cdn.com/images/emoji/twitter/+1.png?v=#{Emoji::EMOJI_VERSION}" title=":+1:" class="emoji" alt=":+1:"> and an external upload</p> <p>This post has a local emoji <img src="https://local.cdn.com/images/emoji/twitter/+1.png?v=#{Emoji::EMOJI_VERSION}" title=":+1:" class="emoji" alt=":+1:"> and an external upload</p>
<p><img src="https://s3.cdn.com/#{stored_path}" alt="smallest.png" data-base62-sha1="#{upload.base62_sha1}" width="10" height="20"></p> <p><img src="https://s3.cdn.com/#{stored_path}" alt="smallest.png" data-base62-sha1="#{upload.base62_sha1}" width="10" height="20"></p>
HTML HTML
@ -1336,7 +1322,7 @@ describe CookedPostProcessor do
cpp = CookedPostProcessor.new(the_post) cpp = CookedPostProcessor.new(the_post)
cpp.optimize_urls cpp.optimize_urls
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p>This post has a local emoji <img src="https://local.cdn.com/images/emoji/twitter/+1.png?v=#{Emoji::EMOJI_VERSION}" title=":+1:" class="emoji" alt=":+1:"> and an external upload</p> <p>This post has a local emoji <img src="https://local.cdn.com/images/emoji/twitter/+1.png?v=#{Emoji::EMOJI_VERSION}" title=":+1:" class="emoji" alt=":+1:"> and an external upload</p>
<p><img src="/secure-media-uploads/#{stored_path}" alt="smallest.png" data-base62-sha1="#{upload.base62_sha1}" width="10" height="20"></p> <p><img src="/secure-media-uploads/#{stored_path}" alt="smallest.png" data-base62-sha1="#{upload.base62_sha1}" width="10" height="20"></p>
HTML HTML
@ -1357,18 +1343,20 @@ describe CookedPostProcessor do
the_post = Fabricate(:post, raw: "This post has an S3 video onebox:\n#{video_upload.url}") the_post = Fabricate(:post, raw: "This post has an S3 video onebox:\n#{video_upload.url}")
cpp = CookedPostProcessor.new(the_post) cpp = CookedPostProcessor.new(the_post.reload)
cpp.post_process_oneboxes
cpp = CookedPostProcessor.new(the_post.reload)
cpp.post_process_oneboxes cpp.post_process_oneboxes
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML
<p>This post has an S3 video onebox:<br></p> <p>This post has an S3 video onebox:<br>
<div class="onebox video-onebox"> </p><div class="onebox video-onebox">
<video width="100%" height="100%" controls=""> <video width="100%" height="100%" controls="">
<source src="#{video_upload.url}"> <source src="#{video_upload.url}">
<a href="#{video_upload.url}" rel="nofollow ugc noopener">#{video_upload.url}</a> <a href="#{video_upload.url}" rel="nofollow ugc noopener">#{video_upload.url}</a>
</source> </video>
</video> </div>
</div>
HTML HTML
end end
@ -1384,13 +1372,12 @@ describe CookedPostProcessor do
secure_url = video_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-media-uploads") secure_url = video_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-media-uploads")
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p>This post has an S3 video onebox:<br> <p>This post has an S3 video onebox:<br>
<div class="onebox video-onebox"> <div class="onebox video-onebox">
<video width="100%" height="100%" controls=""> <video width="100%" height="100%" controls="">
<source src="#{secure_url}"> <source src="#{secure_url}">
<a href="#{secure_url}">#{secure_url}</a> <a href="#{secure_url}">#{secure_url}</a>
</source>
</video> </video>
</div> </div>
</p> </p>
@ -1416,7 +1403,7 @@ describe CookedPostProcessor do
stub_request(:head, audio_upload.url) stub_request(:head, audio_upload.url)
stub_request(:get, image_upload.url) stub_request(:get, image_upload.url)
raw = <<~RAW raw = <<~RAW.rstrip
This post has a video upload. This post has a video upload.
#{video_upload.url} #{video_upload.url}
@ -1435,19 +1422,17 @@ describe CookedPostProcessor do
secure_video_url = video_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-media-uploads") secure_video_url = video_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-media-uploads")
secure_audio_url = audio_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-media-uploads") secure_audio_url = audio_upload.url.sub(SiteSetting.s3_cdn_url, "#{Discourse.base_url}/secure-media-uploads")
expect(cpp.html).to match_html <<~HTML expect(cpp.html).to match_html <<~HTML.rstrip
<p>This post has a video upload.<br></p> <p>This post has a video upload.<br>
<div class="onebox video-onebox"> <div class="onebox video-onebox">
<video width="100%" height="100%" controls=""> <video width="100%" height="100%" controls="">
<source src="#{secure_video_url}"> <source src="#{secure_video_url}">
<a href="#{secure_video_url}">#{secure_video_url}</a> <a href="#{secure_video_url}">#{secure_video_url}</a>
</source>
</video> </video>
</div> </div>
<p>This post has an audio upload.<br>
<audio controls><source src="#{secure_audio_url}"><a href="#{secure_audio_url}">#{secure_audio_url}</a></source></audio>
</p> </p>
<p>This post has an audio upload.<br>
<audio controls=""><source src="#{secure_audio_url}"><a href="#{secure_audio_url}">#{secure_audio_url}</a></audio></p>
<p>And an image upload.<br> <p>And an image upload.<br>
<img src="#{image_upload.url}" alt="#{image_upload.original_filename}" data-base62-sha1="#{image_upload.base62_sha1}"></p> <img src="#{image_upload.url}" alt="#{image_upload.original_filename}" data-base62-sha1="#{image_upload.base62_sha1}"></p>
@ -1616,7 +1601,7 @@ describe CookedPostProcessor do
let(:post) { build(:post) } let(:post) { build(:post) }
let(:cpp) { CookedPostProcessor.new(post) } let(:cpp) { CookedPostProcessor.new(post) }
let(:doc) { Nokogiri::HTML::fragment('<body><div><a><img id="linked_image"></a><p><img id="standard_image"></p></div></body>') } let(:doc) { Nokogiri::HTML5::fragment('<body><div><a><img id="linked_image"></a><p><img id="standard_image"></p></div></body>') }
it "is true when the image is inside a link" do it "is true when the image is inside a link" do
img = doc.css("img#linked_image").first img = doc.css("img#linked_image").first

View File

@ -8,14 +8,14 @@ describe Email::Styles do
def basic_fragment(html) def basic_fragment(html)
styler = Email::Styles.new(html) styler = Email::Styles.new(html)
styler.format_basic styler.format_basic
Nokogiri::HTML.fragment(styler.to_html) Nokogiri::HTML5.fragment(styler.to_html)
end end
def html_fragment(html) def html_fragment(html)
styler = Email::Styles.new(html) styler = Email::Styles.new(html)
styler.format_basic styler.format_basic
styler.format_html styler.format_html
Nokogiri::HTML.fragment(styler.to_html) Nokogiri::HTML5.fragment(styler.to_html)
end end
context "basic formatter" do context "basic formatter" do

View File

@ -18,7 +18,7 @@ describe ExcerptParser do
</details> </details>
HTML HTML
expect(ExcerptParser.get_excerpt(html, 50, {})).to match_html(<<~HTML) expect(ExcerptParser.get_excerpt(html, 50, {})).to match_html(<<~HTML.rstrip)
<details><summary>FOO</summary>BAR <details><summary>FOO</summary>BAR
Lorem ipsum dolor sit amet, consectetur adi&hellip;</details> Lorem ipsum dolor sit amet, consectetur adi&hellip;</details>
HTML HTML

View File

@ -184,7 +184,7 @@ describe PrettyText do
<aside class="quote no-group" data-username="#{user.username}" data-post="123" data-topic="456" data-full="true"> <aside class="quote no-group" data-username="#{user.username}" data-post="123" data-topic="456" data-full="true">
<div class="title"> <div class="title">
<div class="quote-controls"></div> <div class="quote-controls"></div>
<img alt width="20" height="20" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png" class="avatar"> #{user.username}:</div> <img alt="" width="20" height="20" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png" class="avatar"> #{user.username}:</div>
<blockquote> <blockquote>
<p>ddd</p> <p>ddd</p>
</blockquote> </blockquote>
@ -206,7 +206,7 @@ describe PrettyText do
<aside class="quote no-group" data-username="#{user.username}" data-post="123" data-topic="456" data-full="true"> <aside class="quote no-group" data-username="#{user.username}" data-post="123" data-topic="456" data-full="true">
<div class="title"> <div class="title">
<div class="quote-controls"></div> <div class="quote-controls"></div>
<img alt width="20" height="20" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png" class="avatar"> #{user.username}:</div> <img alt="" width="20" height="20" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png" class="avatar"> #{user.username}:</div>
<blockquote> <blockquote>
<p>ddd</p> <p>ddd</p>
</blockquote> </blockquote>
@ -227,7 +227,7 @@ describe PrettyText do
<aside class="quote no-group" data-username="#{user.username}" data-post="555" data-topic="666"> <aside class="quote no-group" data-username="#{user.username}" data-post="555" data-topic="666">
<div class="title"> <div class="title">
<div class="quote-controls"></div> <div class="quote-controls"></div>
<img alt width="20" height="20" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png" class="avatar"> #{user.username}:</div> <img alt="" width="20" height="20" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png" class="avatar"> #{user.username}:</div>
<blockquote> <blockquote>
<p>ddd</p> <p>ddd</p>
</blockquote> </blockquote>
@ -254,7 +254,7 @@ describe PrettyText do
<aside class="quote group-#{group.name}" data-username="#{user.username}" data-post="2" data-topic="#{topic.id}"> <aside class="quote group-#{group.name}" data-username="#{user.username}" data-post="2" data-topic="#{topic.id}">
<div class="title"> <div class="title">
<div class="quote-controls"></div> <div class="quote-controls"></div>
<img alt width="20" height="20" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png" class="avatar"><a href="http://test.localhost/t/this-is-a-test-topic/#{topic.id}/2">This is a test topic</a> <img alt="" width="20" height="20" src="//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png" class="avatar"><a href="http://test.localhost/t/this-is-a-test-topic/#{topic.id}/2">This is a test topic</a>
</div> </div>
<blockquote> <blockquote>
<p>ddd</p> <p>ddd</p>
@ -828,7 +828,7 @@ describe PrettyText do
describe "strip_image_wrapping" do describe "strip_image_wrapping" do
def strip_image_wrapping(html) def strip_image_wrapping(html)
doc = Nokogiri::HTML.fragment(html) doc = Nokogiri::HTML5.fragment(html)
described_class.strip_image_wrapping(doc) described_class.strip_image_wrapping(doc)
doc.to_html doc.to_html
end end
@ -1122,7 +1122,7 @@ describe PrettyText do
it "can handle mixed lists" do it "can handle mixed lists" do
# known bug in old md engine # known bug in old md engine
cooked = PrettyText.cook("* a\n\n1. b") cooked = PrettyText.cook("* a\n\n1. b")
expect(cooked).to match_html("<ul>\n<li>a</li>\n</ul><ol>\n<li>b</li>\n</ol>") expect(cooked).to match_html("<ul>\n<li>a</li>\n</ul>\n<ol>\n<li>b</li>\n</ol>")
end end
it "can handle traditional vs non traditional newlines" do it "can handle traditional vs non traditional newlines" do
@ -1342,13 +1342,13 @@ HTML
it "supports img bbcode" do it "supports img bbcode" do
cooked = PrettyText.cook "[img]http://www.image/test.png[/img]" cooked = PrettyText.cook "[img]http://www.image/test.png[/img]"
html = "<p><img src=\"http://www.image/test.png\" alt></p>" html = "<p><img src=\"http://www.image/test.png\" alt=\"\"></p>"
expect(cooked).to eq(html) expect(cooked).to eq(html)
end end
it "provides safety for img bbcode" do it "provides safety for img bbcode" do
cooked = PrettyText.cook "[img]http://aaa.com<script>alert(1);</script>[/img]" cooked = PrettyText.cook "[img]http://aaa.com<script>alert(1);</script>[/img]"
html = '<p><img src="http://aaa.com&lt;script&gt;alert(1);&lt;/script&gt;" alt></p>' html = '<p><img src="http://aaa.com&lt;script&gt;alert(1);&lt;/script&gt;" alt=""></p>'
expect(cooked).to eq(html) expect(cooked).to eq(html)
end end
@ -1433,10 +1433,10 @@ HTML
html = <<~HTML html = <<~HTML
<p><img src="http://png.com/my.png" alt="title with | title" width="220" height="100"><br> <p><img src="http://png.com/my.png" alt="title with | title" width="220" height="100"><br>
<img src="http://png.com/my.png" alt><br> <img src="http://png.com/my.png" alt=""><br>
<img src="http://png.com/my.png" alt width="220" height="100"><br> <img src="http://png.com/my.png" alt="" width="220" height="100"><br>
<img src="http://png.com/my.png" alt="stuff"><br> <img src="http://png.com/my.png" alt="stuff"><br>
<img src="http://png.com/my.png" alt title="some title" width="110" height="50"></p> <img src="http://png.com/my.png" alt="" title="some title" width="110" height="50"></p>
HTML HTML
expect(cooked).to eq(html.strip) expect(cooked).to eq(html.strip)
@ -1452,11 +1452,11 @@ HTML
MD MD
html = <<~HTML html = <<~HTML
<p><img src="http://png.com/my.png" alt width="110" height="50"><br> <p><img src="http://png.com/my.png" alt="" width="110" height="50"><br>
<img src="http://png.com/my.png" alt width="110" height="50"><br> <img src="http://png.com/my.png" alt="" width="110" height="50"><br>
<img src="http://png.com/my.png" alt width="110" height="50"><br> <img src="http://png.com/my.png" alt="" width="110" height="50"><br>
<img src="http://png.com/my.png" alt width="150" height="68"><br> <img src="http://png.com/my.png" alt="" width="150" height="68"><br>
<img src="http://png.com/my.png" alt width="110" height="50"></p> <img src="http://png.com/my.png" alt="" width="110" height="50"></p>
HTML HTML
expect(cooked).to eq(html.strip) expect(cooked).to eq(html.strip)

View File

@ -217,9 +217,9 @@ describe ContentSecurityPolicy do
policy # call this first to make sure further actions clear the cache policy # call this first to make sure further actions clear the cache
theme.set_field(target: :common, name: "header", value: <<~SCRIPT) theme.set_field(target: :common, name: "header", value: <<~SCRIPT)
<script src='https://example.com/myscript.js'/> <script src='https://example.com/myscript.js'></script>
<script src='//example2.com/protocol-less-script.js'/> <script src='//example2.com/protocol-less-script.js'></script>
<script src='domain-only.com'/> <script src='domain-only.com'></script>
<script>console.log('inline script')</script> <script>console.log('inline script')</script>
SCRIPT SCRIPT

View File

@ -14,7 +14,7 @@ describe TopicEmbed do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
let(:title) { "How to turn a fish from good to evil in 30 seconds" } let(:title) { "How to turn a fish from good to evil in 30 seconds" }
let(:url) { 'http://eviltrout.com/123' } let(:url) { 'http://eviltrout.com/123' }
let(:contents) { "hello world new post <a href='/hello'>hello</a> <img src='/images/wat.jpg'>" } let(:contents) { "<p>hello world new post <a href='/hello'>hello</a> <img src='/images/wat.jpg'></p>" }
fab!(:embeddable_host) { Fabricate(:embeddable_host) } fab!(:embeddable_host) { Fabricate(:embeddable_host) }
it "returns nil when the URL is malformed" do it "returns nil when the URL is malformed" do
@ -46,7 +46,7 @@ describe TopicEmbed do
it "Supports updating the post content" do it "Supports updating the post content" do
expect do expect do
TopicEmbed.import(user, url, "New title received", "muhahaha new contents!") TopicEmbed.import(user, url, "New title received", "<p>muhahaha new contents!</p>")
end.to change { topic_embed.reload.content_sha1 } end.to change { topic_embed.reload.content_sha1 }
expect(topic_embed.topic.title).to eq("New title received") expect(topic_embed.topic.title).to eq("New title received")

View File

@ -11,7 +11,7 @@ describe CategoriesController do
it 'web crawler view has correct urls for subfolder install' do it 'web crawler view has correct urls for subfolder install' do
set_subfolder "/forum" set_subfolder "/forum"
get '/categories', headers: { 'HTTP_USER_AGENT' => 'Googlebot' } get '/categories', headers: { 'HTTP_USER_AGENT' => 'Googlebot' }
html = Nokogiri::HTML(response.body) html = Nokogiri::HTML5(response.body)
expect(html.css('body.crawler')).to be_present expect(html.css('body.crawler')).to be_present
expect(html.css("a[href=\"/forum/c/#{category.slug}\"]")).to be_present expect(html.css("a[href=\"/forum/c/#{category.slug}\"]")).to be_present
end end

View File

@ -231,7 +231,7 @@ RSpec.describe EmailController do
navigate_to_unsubscribe navigate_to_unsubscribe
source = Nokogiri::HTML::fragment(response.body) source = Nokogiri::HTML5::fragment(response.body)
expect(source.css(".combobox option").map(&:inner_text)).to eq(slow_digest_frequencies) expect(source.css(".combobox option").map(&:inner_text)).to eq(slow_digest_frequencies)
end end
@ -242,7 +242,7 @@ RSpec.describe EmailController do
navigate_to_unsubscribe navigate_to_unsubscribe
source = Nokogiri::HTML::fragment(response.body) source = Nokogiri::HTML5::fragment(response.body)
expect(source.css(".combobox option[selected='selected']")[0]['value']).to eq(six_months_freq.to_s) expect(source.css(".combobox option[selected='selected']")[0]['value']).to eq(six_months_freq.to_s)
end end
@ -253,7 +253,7 @@ RSpec.describe EmailController do
navigate_to_unsubscribe navigate_to_unsubscribe
source = Nokogiri::HTML::fragment(response.body) source = Nokogiri::HTML5::fragment(response.body)
expect(source.css(".combobox option[selected='selected']")[0]['value']).to eq(never_frequency.to_s) expect(source.css(".combobox option[selected='selected']")[0]['value']).to eq(never_frequency.to_s)
end end
end end

View File

@ -146,7 +146,7 @@ describe EmbedController do
get '/embed/comments', params: { embed_url: embed_url }, headers: headers get '/embed/comments', params: { embed_url: embed_url }, headers: headers
html = Nokogiri::HTML.fragment(response.body) html = Nokogiri::HTML5.fragment(response.body)
css_link = html.at("link[data-target=embedded_theme]").attribute("href").value css_link = html.at("link[data-target=embedded_theme]").attribute("href").value
get css_link get css_link

View File

@ -238,7 +238,7 @@ describe UserApiKeysController do
SiteSetting.min_trust_level_for_user_api_key = 0 SiteSetting.min_trust_level_for_user_api_key = 0
post "/user-api-key", params: args post "/user-api-key", params: args
expect(response.status).not_to eq(302) expect(response.status).not_to eq(302)
payload = Nokogiri::HTML(response.body).at('code').content payload = Nokogiri::HTML5(response.body).at('code').content
encrypted = Base64.decode64(payload) encrypted = Base64.decode64(payload)
key = OpenSSL::PKey::RSA.new(private_key) key = OpenSSL::PKey::RSA.new(private_key)
parsed = JSON.parse(key.private_decrypt(encrypted)) parsed = JSON.parse(key.private_decrypt(encrypted))

View File

@ -142,7 +142,7 @@ describe UsernameChanger do
post = create_post_and_change_username(raw: ".@foo -@foo %@foo _@foo ,@foo ;@foo @@foo") post = create_post_and_change_username(raw: ".@foo -@foo %@foo _@foo ,@foo ;@foo @@foo")
expect(post.raw).to eq(".@bar -@bar %@bar _@bar ,@bar ;@bar @@bar") expect(post.raw).to eq(".@bar -@bar %@bar _@bar ,@bar ;@bar @@bar")
expect(post.cooked).to match_html(<<~HTML) expect(post.cooked).to match_html(<<~HTML.rstrip)
<p>.<a class="mention" href="/u/bar">@bar</a> <p>.<a class="mention" href="/u/bar">@bar</a>
-<a class="mention" href="/u/bar">@bar</a> -<a class="mention" href="/u/bar">@bar</a>
%<a class="mention" href="/u/bar">@bar</a> %<a class="mention" href="/u/bar">@bar</a>
@ -164,7 +164,7 @@ describe UsernameChanger do
post = create_post_and_change_username(raw: "**@foo** *@foo* _@foo_ ~~@foo~~") post = create_post_and_change_username(raw: "**@foo** *@foo* _@foo_ ~~@foo~~")
expect(post.raw).to eq("**@bar** *@bar* _@bar_ ~~@bar~~") expect(post.raw).to eq("**@bar** *@bar* _@bar_ ~~@bar~~")
expect(post.cooked).to match_html(<<~HTML) expect(post.cooked).to match_html(<<~HTML.rstrip)
<p><strong><a class="mention" href="/u/bar">@bar</a></strong> <p><strong><a class="mention" href="/u/bar">@bar</a></strong>
<em><a class="mention" href="/u/bar">@bar</a></em> <em><a class="mention" href="/u/bar">@bar</a></em>
<em><a class="mention" href="/u/bar">@bar</a></em> <em><a class="mention" href="/u/bar">@bar</a></em>
@ -176,7 +176,7 @@ describe UsernameChanger do
post = create_post_and_change_username(raw: "@foo. @foo, @foo: @foo; @foo_ @foo-") post = create_post_and_change_username(raw: "@foo. @foo, @foo: @foo; @foo_ @foo-")
expect(post.raw).to eq("@bar. @bar, @bar: @bar; @bar_ @bar-") expect(post.raw).to eq("@bar. @bar, @bar: @bar; @bar_ @bar-")
expect(post.cooked).to match_html(<<~HTML) expect(post.cooked).to match_html(<<~HTML.rstrip)
<p><a class="mention" href="/u/bar">@bar</a>. <p><a class="mention" href="/u/bar">@bar</a>.
<a class="mention" href="/u/bar">@bar</a>, <a class="mention" href="/u/bar">@bar</a>,
<a class="mention" href="/u/bar">@bar</a>: <a class="mention" href="/u/bar">@bar</a>:
@ -220,12 +220,8 @@ describe UsernameChanger do
post = create_post_and_change_username(raw: "@foo @foobar @foo-bar @foo_bar @foo1") post = create_post_and_change_username(raw: "@foo @foobar @foo-bar @foo_bar @foo1")
expect(post.raw).to eq("@bar @foobar @foo-bar @foo_bar @foo1") expect(post.raw).to eq("@bar @foobar @foo-bar @foo_bar @foo1")
expect(post.cooked).to match_html(<<~HTML) expect(post.cooked).to match_html(<<~HTML.rstrip)
<p><a class="mention" href="/u/bar">@bar</a> <p><a class="mention" href="/u/bar">@bar</a> <a class="mention" href="/u/foobar">@foobar</a> <a class="mention" href="/u/foo-bar">@foo-bar</a> <a class="mention" href="/u/foo_bar">@foo_bar</a> <a class="mention" href="/u/foo1">@foo1</a></p>
<a class="mention" href="/u/foobar">@foobar</a>
<a class="mention" href="/u/foo-bar">@foo-bar</a>
<a class="mention" href="/u/foo_bar">@foo_bar</a>
<a class="mention" href="/u/foo1">@foo1</a></p>
HTML HTML
end end
@ -311,12 +307,8 @@ describe UsernameChanger do
post = create_post_and_change_username(raw: "@թռչուն @թռչուն鳥 @թռչուն-鳥 @թռչուն_鳥 @թռչուն٩", target_username: 'птица') post = create_post_and_change_username(raw: "@թռչուն @թռչուն鳥 @թռչուն-鳥 @թռչուն_鳥 @թռչուն٩", target_username: 'птица')
expect(post.raw).to eq("@птица @թռչուն鳥 @թռչուն-鳥 @թռչուն_鳥 @թռչուն٩") expect(post.raw).to eq("@птица @թռչուն鳥 @թռչուն-鳥 @թռչուն_鳥 @թռչուն٩")
expect(post.cooked).to match_html(<<~HTML) expect(post.cooked).to match_html(<<~HTML.rstrip)
<p><a class="mention" href="/u/%D0%BF%D1%82%D0%B8%D1%86%D0%B0">@птица</a> <p><a class="mention" href="/u/%D0%BF%D1%82%D0%B8%D1%86%D0%B0">@птица</a> <a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6%E9%B3%A5">@թռչուն鳥</a> <a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6-%E9%B3%A5">@թռչուն-鳥</a> <a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6_%E9%B3%A5">@թռչուն_鳥</a> <a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6%D9%A9">@թռչուն٩</a></p>
<a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6%E9%B3%A5">@թռչուն</a>
<a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6-%E9%B3%A5">@թռչուն-</a>
<a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6_%E9%B3%A5">@թռչուն_鳥</a>
<a class="mention" href="/u/%D5%A9%D5%BC%D5%B9%D5%B8%D6%82%D5%B6%D9%A9">@թռչուն٩</a></p>
HTML HTML
end end
@ -364,7 +356,7 @@ describe UsernameChanger do
dolor sit amet dolor sit amet
RAW RAW
expect(post.cooked).to match_html(<<~HTML) expect(post.cooked).to match_html(<<~HTML.rstrip)
<p>Lorem ipsum</p> <p>Lorem ipsum</p>
<aside class="quote no-group" data-username="bar" data-post="1" data-topic="#{quoted_post.topic.id}"> <aside class="quote no-group" data-username="bar" data-post="1" data-topic="#{quoted_post.topic.id}">
<div class="title"> <div class="title">
@ -377,7 +369,7 @@ describe UsernameChanger do
<aside class="quote no-group" data-username="bar"> <aside class="quote no-group" data-username="bar">
<div class="title"> <div class="title">
<div class="quote-controls"></div> <div class="quote-controls"></div>
<img alt='' width="20" height="20" src="#{avatar_url}" class="avatar"> bar:</div> <img alt="" width="20" height="20" src="#{avatar_url}" class="avatar"> bar:</div>
<blockquote> <blockquote>
<p>quoted post</p> <p>quoted post</p>
</blockquote> </blockquote>
@ -385,7 +377,7 @@ describe UsernameChanger do
<aside class="quote no-group" data-username="bar" data-post="1" data-topic="#{quoted_post.topic.id}"> <aside class="quote no-group" data-username="bar" data-post="1" data-topic="#{quoted_post.topic.id}">
<div class="title"> <div class="title">
<div class="quote-controls"></div> <div class="quote-controls"></div>
<img alt='' width="20" height="20" src="#{avatar_url}" class="avatar"> bar:</div> <img alt="" width="20" height="20" src="#{avatar_url}" class="avatar"> bar:</div>
<blockquote> <blockquote>
<p>quoted post</p> <p>quoted post</p>
</blockquote> </blockquote>
@ -415,7 +407,7 @@ describe UsernameChanger do
end end
let(:expected_cooked) do let(:expected_cooked) do
<<~HTML <<~HTML.rstrip
<p>Lorem ipsum</p> <p>Lorem ipsum</p>
<aside class="quote no-group" data-username="bar" data-post="1" data-topic="#{quoted_post.topic.id}"> <aside class="quote no-group" data-username="bar" data-post="1" data-topic="#{quoted_post.topic.id}">
<div class="title"> <div class="title">
@ -459,7 +451,7 @@ describe UsernameChanger do
expect(post.raw).to eq(raw) expect(post.raw).to eq(raw)
expect(post.cooked).to match_html(<<~HTML) expect(post.cooked).to match_html(<<~HTML.rstrip)
<p><aside class="quote" data-post="#{quoted_post.post_number}" data-topic="#{quoted_post.topic.id}"> <p><aside class="quote" data-post="#{quoted_post.post_number}" data-topic="#{quoted_post.topic.id}">
<div class="title"> <div class="title">
<div class="quote-controls"></div> <div class="quote-controls"></div>
@ -491,7 +483,7 @@ describe UsernameChanger do
expect(post.raw).to eq(raw) expect(post.raw).to eq(raw)
expect(post.cooked).to match_html(<<~HTML) expect(post.cooked).to match_html(<<~HTML.rstrip)
<p><aside class="quote" data-post="#{quoted_post.post_number}" data-topic="#{quoted_post.topic.id}"> <p><aside class="quote" data-post="#{quoted_post.post_number}" data-topic="#{quoted_post.topic.id}">
<div class="title"> <div class="title">
<div class="quote-controls"></div> <div class="quote-controls"></div>

View File

@ -17,7 +17,7 @@ RSpec::Matchers.define :match_html do |expected|
end end
def make_canonical_html(html) def make_canonical_html(html)
Nokogiri::HTML(html) { |config| config.options = Nokogiri::XML::ParseOptions::NOBLANKS | Nokogiri::XML::ParseOptions::COMPACT } Nokogiri::HTML5(html) { |config| config[:options] = Nokogiri::XML::ParseOptions::NOBLANKS | Nokogiri::XML::ParseOptions::COMPACT }
end end
end end