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.
This commit is contained in:
Sam 2015-07-15 15:32:35 +10:00
parent d20324ece8
commit b772d96f7a
5 changed files with 92 additions and 4 deletions

View file

@ -5,13 +5,74 @@ 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
return external_url if external_url
return post.url if post

View file

@ -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."

View file

@ -806,6 +806,10 @@ uncategorized:
default: 'ascii'
enum: 'SlugSetting'
permalink_normalizations:
default: ''
type: list
# Search
min_search_term_length:
client: true

View file

@ -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)

View file

@ -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 ")