[FIX] Make route parsing regex more robust
- fixes sinatra/sinatra/#611 - adds support for case-insensitive URL encoding
This commit is contained in:
parent
9984d0d2b3
commit
f079ce18fe
|
@ -1325,7 +1325,10 @@ module Sinatra
|
||||||
ignore = ""
|
ignore = ""
|
||||||
pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c|
|
pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c|
|
||||||
ignore << escaped(c).join if c.match(/[\.@]/)
|
ignore << escaped(c).join if c.match(/[\.@]/)
|
||||||
encoded(c)
|
patt = encoded(c)
|
||||||
|
patt.gsub(/%\h\h/) do |match|
|
||||||
|
match.split(//).map {|char| char =~ /[A-Z]/ ? "[#{char}#{char.tr('A-Z', 'a-z')}]" : char}.join
|
||||||
|
end
|
||||||
end
|
end
|
||||||
pattern.gsub!(/((:\w+)|\*)/) do |match|
|
pattern.gsub!(/((:\w+)|\*)/) do |match|
|
||||||
if match == "*"
|
if match == "*"
|
||||||
|
@ -1333,7 +1336,9 @@ module Sinatra
|
||||||
"(.*?)"
|
"(.*?)"
|
||||||
else
|
else
|
||||||
keys << $2[1..-1]
|
keys << $2[1..-1]
|
||||||
"([^#{ignore}/?#]+)"
|
ignore_pattern = safe_ignore(ignore)
|
||||||
|
|
||||||
|
ignore_pattern
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
[/\A#{pattern}\z/, keys]
|
[/\A#{pattern}\z/, keys]
|
||||||
|
@ -1361,6 +1366,29 @@ module Sinatra
|
||||||
[Regexp.escape(enc), URI.escape(char, /./)]
|
[Regexp.escape(enc), URI.escape(char, /./)]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def safe_ignore(ignore)
|
||||||
|
unsafe_ignore = []
|
||||||
|
ignore = ignore.gsub(/%\h\h/) do |hex|
|
||||||
|
unsafe_ignore << hex[1..2]
|
||||||
|
''
|
||||||
|
end
|
||||||
|
unsafe_patterns = unsafe_ignore.map do |unsafe|
|
||||||
|
chars = unsafe.split(//).map do |char|
|
||||||
|
if char =~ /[A-Z]/
|
||||||
|
char <<= char.tr('A-Z', 'a-z')
|
||||||
|
end
|
||||||
|
char
|
||||||
|
end
|
||||||
|
|
||||||
|
"|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
|
||||||
|
end
|
||||||
|
if unsafe_patterns.length > 0
|
||||||
|
"((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
|
||||||
|
else
|
||||||
|
"([^#{ignore}/?#]+)"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
public
|
public
|
||||||
# Makes the methods defined in the block and in the Modules given
|
# Makes the methods defined in the block and in the Modules given
|
||||||
# in `extensions` available to the handlers and templates
|
# in `extensions` available to the handlers and templates
|
||||||
|
|
|
@ -58,7 +58,7 @@ class CompileTest < Test::Unit::TestCase
|
||||||
fails "/:foo", "/"
|
fails "/:foo", "/"
|
||||||
fails "/:foo", "/foo/"
|
fails "/:foo", "/foo/"
|
||||||
|
|
||||||
converts "/föö", %r{\A/f%C3%B6%C3%B6\z}
|
converts "/föö", %r{\A/f%[Cc]3%[Bb]6%[Cc]3%[Bb]6\z}
|
||||||
parses "/föö", "/f%C3%B6%C3%B6", {}
|
parses "/föö", "/f%C3%B6%C3%B6", {}
|
||||||
|
|
||||||
converts "/:foo/:bar", %r{\A/([^/?#]+)/([^/?#]+)\z}
|
converts "/:foo/:bar", %r{\A/([^/?#]+)/([^/?#]+)\z}
|
||||||
|
@ -87,7 +87,7 @@ class CompileTest < Test::Unit::TestCase
|
||||||
converts "/test$/", %r{\A/test(?:\$|%24)/\z}
|
converts "/test$/", %r{\A/test(?:\$|%24)/\z}
|
||||||
parses "/test$/", "/test$/", {}
|
parses "/test$/", "/test$/", {}
|
||||||
|
|
||||||
converts "/te+st/", %r{\A/te(?:\+|%2B)st/\z}
|
converts "/te+st/", %r{\A/te(?:\+|%2[Bb])st/\z}
|
||||||
parses "/te+st/", "/te+st/", {}
|
parses "/te+st/", "/te+st/", {}
|
||||||
fails "/te+st/", "/test/"
|
fails "/te+st/", "/test/"
|
||||||
fails "/te+st/", "/teeest/"
|
fails "/te+st/", "/teeest/"
|
||||||
|
@ -95,7 +95,7 @@ class CompileTest < Test::Unit::TestCase
|
||||||
converts "/test(bar)/", %r{\A/test(?:\(|%28)bar(?:\)|%29)/\z}
|
converts "/test(bar)/", %r{\A/test(?:\(|%28)bar(?:\)|%29)/\z}
|
||||||
parses "/test(bar)/", "/test(bar)/", {}
|
parses "/test(bar)/", "/test(bar)/", {}
|
||||||
|
|
||||||
converts "/path with spaces", %r{\A/path(?:%20|(?:\+|%2B))with(?:%20|(?:\+|%2B))spaces\z}
|
converts "/path with spaces", %r{\A/path(?:%20|(?:\+|%2[Bb]))with(?:%20|(?:\+|%2[Bb]))spaces\z}
|
||||||
parses "/path with spaces", "/path%20with%20spaces", {}
|
parses "/path with spaces", "/path%20with%20spaces", {}
|
||||||
parses "/path with spaces", "/path%2Bwith%2Bspaces", {}
|
parses "/path with spaces", "/path%2Bwith%2Bspaces", {}
|
||||||
parses "/path with spaces", "/path+with+spaces", {}
|
parses "/path with spaces", "/path+with+spaces", {}
|
||||||
|
@ -110,22 +110,22 @@ class CompileTest < Test::Unit::TestCase
|
||||||
parses "/*/foo/*/*", "/bar/foo/bling/baz/boom", "splat" => ["bar", "bling", "baz/boom"]
|
parses "/*/foo/*/*", "/bar/foo/bling/baz/boom", "splat" => ["bar", "bling", "baz/boom"]
|
||||||
fails "/*/foo/*/*", "/bar/foo/baz"
|
fails "/*/foo/*/*", "/bar/foo/baz"
|
||||||
|
|
||||||
converts "/test.bar", %r{\A/test(?:\.|%2E)bar\z}
|
converts "/test.bar", %r{\A/test(?:\.|%2[Ee])bar\z}
|
||||||
parses "/test.bar", "/test.bar", {}
|
parses "/test.bar", "/test.bar", {}
|
||||||
fails "/test.bar", "/test0bar"
|
fails "/test.bar", "/test0bar"
|
||||||
|
|
||||||
converts "/:file.:ext", %r{\A/([^\.%2E/?#]+)(?:\.|%2E)([^\.%2E/?#]+)\z}
|
converts "/:file.:ext", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)\z}
|
||||||
parses "/:file.:ext", "/pony.jpg", "file" => "pony", "ext" => "jpg"
|
parses "/:file.:ext", "/pony.jpg", "file" => "pony", "ext" => "jpg"
|
||||||
parses "/:file.:ext", "/pony%2Ejpg", "file" => "pony", "ext" => "jpg"
|
parses "/:file.:ext", "/pony%2Ejpg", "file" => "pony", "ext" => "jpg"
|
||||||
fails "/:file.:ext", "/.jpg"
|
fails "/:file.:ext", "/.jpg"
|
||||||
|
|
||||||
converts "/:name.?:format?", %r{\A/([^\.%2E/?#]+)(?:\.|%2E)?([^\.%2E/?#]+)?\z}
|
converts "/:name.?:format?", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])?((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)?\z}
|
||||||
parses "/:name.?:format?", "/foo", "name" => "foo", "format" => nil
|
parses "/:name.?:format?", "/foo", "name" => "foo", "format" => nil
|
||||||
parses "/:name.?:format?", "/foo.bar", "name" => "foo", "format" => "bar"
|
parses "/:name.?:format?", "/foo.bar", "name" => "foo", "format" => "bar"
|
||||||
parses "/:name.?:format?", "/foo%2Ebar", "name" => "foo", "format" => "bar"
|
parses "/:name.?:format?", "/foo%2Ebar", "name" => "foo", "format" => "bar"
|
||||||
fails "/:name.?:format?", "/.bar"
|
fails "/:name.?:format?", "/.bar"
|
||||||
|
|
||||||
converts "/:user@?:host?", %r{\A/([^@%40/?#]+)(?:@|%40)?([^@%40/?#]+)?\z}
|
converts "/:user@?:host?", %r{\A/((?:[^@/?#%]|(?:%[^4].|%[4][^0]))+)(?:@|%40)?((?:[^@/?#%]|(?:%[^4].|%[4][^0]))+)?\z}
|
||||||
parses "/:user@?:host?", "/foo@bar", "user" => "foo", "host" => "bar"
|
parses "/:user@?:host?", "/foo@bar", "user" => "foo", "host" => "bar"
|
||||||
parses "/:user@?:host?", "/foo.foo@bar", "user" => "foo.foo", "host" => "bar"
|
parses "/:user@?:host?", "/foo.foo@bar", "user" => "foo.foo", "host" => "bar"
|
||||||
parses "/:user@?:host?", "/foo@bar.bar", "user" => "foo", "host" => "bar.bar"
|
parses "/:user@?:host?", "/foo@bar.bar", "user" => "foo", "host" => "bar.bar"
|
||||||
|
@ -136,4 +136,18 @@ class CompileTest < Test::Unit::TestCase
|
||||||
# parses "/:name(.:format)?", "/foo", "name" => "foo", "format" => nil
|
# parses "/:name(.:format)?", "/foo", "name" => "foo", "format" => nil
|
||||||
# parses "/:name(.:format)?", "/foo.bar", "name" => "foo", "format" => "bar"
|
# parses "/:name(.:format)?", "/foo.bar", "name" => "foo", "format" => "bar"
|
||||||
fails "/:name(.:format)?", "/foo."
|
fails "/:name(.:format)?", "/foo."
|
||||||
|
|
||||||
|
parses "/:id/test.bar", "/3/test.bar", {"id" => "3"}
|
||||||
|
parses "/:id/test.bar", "/2/test.bar", {"id" => "2"}
|
||||||
|
parses "/:id/test.bar", "/2E/test.bar", {"id" => "2E"}
|
||||||
|
parses "/:id/test.bar", "/2e/test.bar", {"id" => "2e"}
|
||||||
|
fails "/:id/test.bar", "/%2E/test.bar"
|
||||||
|
|
||||||
|
parses "/:file.:ext", "/pony%2ejpg", "file" => "pony", "ext" => "jpg"
|
||||||
|
parses "/:file.:ext", "/pony%E6%AD%A3%2Ejpg", "file" => "pony%E6%AD%A3", "ext" => "jpg"
|
||||||
|
parses "/:file.:ext", "/pony%e6%ad%a3%2ejpg", "file" => "pony%e6%ad%a3", "ext" => "jpg"
|
||||||
|
parses "/:file.:ext", "/pony正%2Ejpg", "file" => "pony正", "ext" => "jpg"
|
||||||
|
parses "/:file.:ext", "/pony正%2ejpg", "file" => "pony正", "ext" => "jpg"
|
||||||
|
fails "/:file.:ext", "/pony正..jpg"
|
||||||
|
fails "/:file.:ext", "/pony正.%2ejpg"
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue