diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb index 65c3b93b7..5dcf86d25 100644 --- a/app/models/site_setting.rb +++ b/app/models/site_setting.rb @@ -91,6 +91,7 @@ class SiteSetting < ActiveRecord::Base setting(:allow_duplicate_topic_titles, false) + setting(:add_rel_nofollow_to_user_content, true) setting(:post_excerpt_maxlength, 300) setting(:post_onebox_maxlength, 500) setting(:best_of_score_threshold, 15) diff --git a/config/locales/en.yml b/config/locales/en.yml index 0a3331ef4..8c4ab9945 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -219,6 +219,7 @@ en: max_image_width: "maximum width for an image in a post" category_featured_topics: "number of topics displayed in the category list" popup_delay: "Length of time in ms before popups appear on the screen" + add_rel_nofollow_to_user_content: "Add rel nofollow to all submitted user content, except for internal links (including parent domains) changing this requires you update all your baked markdown" post_excerpt_maxlength: "Maximum length in chars of a post's excerpt." post_onebox_maxlength: "Maximum length of a oneboxed discourse post." category_post_template: "The post template that appears once you create a category" diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb index 4842cd817..cb28ebac6 100644 --- a/lib/pretty_text.rb +++ b/lib/pretty_text.rb @@ -172,7 +172,33 @@ module PrettyText cloned = opts.dup # we have a minor inconsistency cloned[:topicId] = opts[:topic_id] - Sanitize.clean(markdown(text.dup, cloned), PrettyText.whitelist) + sanitized = Sanitize.clean(markdown(text.dup, cloned), PrettyText.whitelist) + if SiteSetting.add_rel_nofollow_to_user_content + sanitized = add_rel_nofollow_to_user_content(sanitized) + end + sanitized + end + + def self.add_rel_nofollow_to_user_content(html) + site_uri = nil + doc = Nokogiri::HTML.fragment(html) + doc.css("a").each do |l| + href = l["href"].to_s + begin + uri = URI(href) + site_uri ||= URI(Discourse.base_url) + + if !uri.host.present? || uri.host.ends_with?(site_uri.host) + # we are good no need for nofollow + else + l["rel"] = "nofollow" + end + rescue URI::InvalidURIError + # add a nofollow anyway + l["rel"] = "nofollow" + end + end + doc.to_html end def self.extract_links(html) diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index cb8536fd6..430b8ac62 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -75,6 +75,24 @@ test .should == "
```\nhello\n```\n
" end end + + describe "rel nofollow" do + before do + SiteSetting.stubs(:add_rel_nofollow_to_user_content).returns(true) + end + + it "should inject nofollow in all user provided links" do + PrettyText.cook('cnn').should =~ /nofollow/ + end + + it "should not inject nofollow in all local links" do + (PrettyText.cook("cnn") !~ /nofollow/).should be_true + end + + it "should not inject nofollow in all subdomain links" do + (PrettyText.cook("cnn") !~ /nofollow/).should be_true + end + end describe "Excerpt" do it "should preserve links" do @@ -130,6 +148,7 @@ test end end + describe "apply cdn" do it "should detect bare links to images and apply a CDN" do PrettyText.apply_cdn("hello","http://a.com").should ==