From b772d96f7abcdf1361e72d8690eea4e6e2583d33 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 15 Jul 2015 15:32:35 +1000 Subject: [PATCH] FEATURE: permalink normalization Optionally allow admins to apply regex based normalization to permalinks prior to matching. This allows us to drop query string, or cleanly ignore slugs, etc. --- app/models/permalink.rb | 69 +++++++++++++++++-- config/locales/server.en.yml | 2 + config/site_settings.yml | 4 ++ .../controllers/permalinks_controller_spec.rb | 11 +++ spec/models/permalink_spec.rb | 10 +++ 5 files changed, 92 insertions(+), 4 deletions(-) diff --git a/app/models/permalink.rb b/app/models/permalink.rb index a216814fc..c7c7d567e 100644 --- a/app/models/permalink.rb +++ b/app/models/permalink.rb @@ -5,11 +5,72 @@ class Permalink < ActiveRecord::Base before_validation :normalize_url - def normalize_url - if self.url - self.url = self.url.strip - self.url = self.url[1..-1] if url[0,1] == '/' + class Normalizer + attr_reader :source + + def initialize(source) + @source = source + if source.present? + @rules = source.split("|").map do |rule| + parse_rule(rule) + end.compact + end end + + def parse_rule(rule) + return unless rule =~ /\/.*\// + + escaping = false + regex = "" + sub = "" + c = 0 + + rule.chars.each do |l| + c += 1 if !escaping && l == "/" + escaping = l == "\\" + + if c > 1 + sub << l + else + regex << l + end + end + + if regex.length > 1 + [Regexp.new(regex[1..-1]), sub[1..-1] || ""] rescue nil + end + + end + + def normalize(url) + return url unless @rules + @rules.each do |(regex,sub)| + url = url.sub(regex,sub) + end + + url + end + + end + + def self.normalize_url(url) + if url + url = url.strip + url = url[1..-1] if url[0,1] == '/' + end + + normalizations = SiteSetting.permalink_normalizations + + @normalizer = Normalizer.new(normalizations) unless @normalizer && @normalizer.source == normalizations + @normalizer.normalize(url) + end + + def self.find_by_url(url) + find_by(url: normalize_url(url)) + end + + def normalize_url + self.url = Permalink.normalize_url(url) if url end def target_url diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 60dafee22..177e0e55d 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1139,6 +1139,8 @@ en: suppress_uncategorized_badge: "Don't show the badge for uncategorized topics in topic lists." + permalink_normalizations: "Apply the following regex before matching permalinks, for example: /(\\/topic.*)\\?.*/\\1 will strip query strings from topic routes. Format is regex+string use \\1 etc. to access captures" + global_notice: "Display an URGENT, EMERGENCY global banner notice to all visitors, change to blank to hide it (HTML allowed)." disable_edit_notifications: "Disables edit notifications by the system user when 'download_remote_images_to_local' is active." diff --git a/config/site_settings.yml b/config/site_settings.yml index 826115a36..eb5755b1d 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -806,6 +806,10 @@ uncategorized: default: 'ascii' enum: 'SlugSetting' + permalink_normalizations: + default: '' + type: list + # Search min_search_term_length: client: true diff --git a/spec/controllers/permalinks_controller_spec.rb b/spec/controllers/permalinks_controller_spec.rb index 2cf79a93c..02d175461 100644 --- a/spec/controllers/permalinks_controller_spec.rb +++ b/spec/controllers/permalinks_controller_spec.rb @@ -10,6 +10,17 @@ describe PermalinksController do expect(response.status).to eq(301) end + it "should apply normalizations" do + SiteSetting.permalink_normalizations = "/(.*)\\?.*/\\1" + + permalink = Fabricate(:permalink, url: '/topic/bla', external_url: '/topic/100') + + get :show, url: permalink.url, test: "hello" + + expect(response).to redirect_to('/topic/100') + expect(response.status).to eq(301) + end + it 'return 404 if permalink record does not exist' do get :show, url: '/not/a/valid/url' expect(response.status).to eq(404) diff --git a/spec/models/permalink_spec.rb b/spec/models/permalink_spec.rb index 68844219f..e86e8c469 100644 --- a/spec/models/permalink_spec.rb +++ b/spec/models/permalink_spec.rb @@ -2,6 +2,16 @@ require "spec_helper" describe Permalink do + describe "normalization" do + it "correctly normalizes" do + normalizer = Permalink::Normalizer.new("/(\\/hello.*)\\?.*/\\1|/(\\/bye.*)\\?.*/\\1") + + expect(normalizer.normalize("/hello?a=1")).to eq("/hello") + expect(normalizer.normalize("/bye?test=1")).to eq("/bye") + expect(normalizer.normalize("/bla?a=1")).to eq("/bla?a=1") + end + end + describe "new record" do it "strips blanks" do permalink = described_class.create(url: " my/old/url ")