Permalink
Browse files

Handle the case of /:name.:format parsing /file.tar.gz

  • Loading branch information...
1 parent 6c41a20 commit 2791c293c45a609f9399c11f867472dda45db006 @floere floere committed Mar 21, 2013
Showing with 54 additions and 14 deletions.
  1. +32 −4 lib/sinatra/base.rb
  2. +22 −10 test/compile_test.rb
View
36 lib/sinatra/base.rb
@@ -1416,28 +1416,56 @@ def compile!(verb, path, block, options = {})
def compile(path)
if path.respond_to? :to_str
keys = []
+
+ # We append a / at the end if there was one.
+ # Reason: Splitting does not split off an empty
+ # string at the end if the split separator
+ # is at the end.
+ #
postfix = '/' if path =~ /\/\z/
+
+ # Split the path into pieces in between forward slashes.
+ #
segments = path.split('/').map! do |segment|
- ignore = ""
+ ignore = []
+
+ # Special character handling.
+ #
pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c|
ignore << escaped(c).join if c.match(/[\.@]/)
patt = encoded(c)
patt.gsub(/%[\da-fA-F]{2}/) do |match|
match.split(//).map {|char| char =~ /[A-Z]/ ? "[#{char}#{char.tr('A-Z', 'a-z')}]" : char}.join
end
end
- pattern.gsub!(/((:\w+)|\*)/) do |match|
+
+ ignore = ignore.uniq.join
+
+ # Key handling.
+ #
+ pattern.gsub(/((:\w+)|\*)/) do |match|
if match == "*"
keys << 'splat'
"(.*?)"
else
keys << $2[1..-1]
ignore_pattern = safe_ignore(ignore)
-
+
ignore_pattern
end
end
- pattern
+ end
+
+ # Special case handling.
+ #
+ if segment = segments.pop
+ if segment.match(/\[\^\\\./)
+ parts = segment.rpartition /\[\^\\\./
+ parts[1] = '[^'
+ segments << parts.join
+ else
+ segments << segment
+ end
end
[/\A#{segments.join('/')}#{postfix}\z/, keys]
elsif path.respond_to?(:keys) && path.respond_to?(:match)
View
32 test/compile_test.rb
@@ -13,7 +13,7 @@ def self.parses pattern, example, expected_params
it "parses #{example} with #{pattern} into params #{expected_params}" do
compiled, keys = compiled pattern
match = compiled.match(example)
- fail %Q{"#{example}" does not parse on pattern "#{pattern}".} unless match
+ fail %Q{"#{example}" does not parse on pattern "#{pattern}" (compiled pattern is #{compiled.source}).} unless match
# Aggregate e.g. multiple splat values into one array.
#
@@ -115,15 +115,19 @@ def compiled pattern
parses "/test.bar", "/test.bar", {}
fails "/test.bar", "/test0bar"
- converts "/:file.:ext", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)\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%2Ejpg", "file" => "pony", "ext" => "jpg"
fails "/:file.:ext", "/.jpg"
- converts "/:name.?:format?", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])?((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)?\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.bar", "name" => "foo", "format" => "bar"
parses "/:name.?:format?", "/foo%2Ebar", "name" => "foo", "format" => "bar"
+ parses "/:name?.?:format", "/.bar", "name" => nil, "format" => "bar"
+ parses "/:name?.?:format?", "/.bar", "name" => nil, "format" => "bar"
+ parses "/:name?.:format?", "/.bar", "name" => nil, "format" => "bar"
+ fails "/:name.:format", "/.bar"
fails "/:name.?:format?", "/.bar"
converts "/:user@?:host?", %r{\A/((?:[^@/?#%]|(?:%[^4].|%[4][^0]))+)(?:@|%40)?((?:[^@/?#%]|(?:%[^4].|%[4][^0]))+)?\z}
@@ -142,28 +146,36 @@ def compiled pattern
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"}
- parses "/:id/test.bar", "/%2E/test.bar", {"id" => "%2E"}
+ parses "/:id/test.bar", "/%2E/test.bar", {"id" => "%2E"}
parses '/10/:id', '/10/test', "id" => "test"
parses '/10/:id', '/10/te.st', "id" => "te.st"
parses '/10.1/:id', '/10.1/test', "id" => "test"
parses '/10.1/:id', '/10.1/te.st', "id" => "te.st"
+ parses '/:foo/:id', '/10.1/te.st', "foo" => "10.1", "id" => "te.st"
+ parses '/:foo/:id', '/10.1.2/te.st', "foo" => "10.1.2", "id" => "te.st"
+ parses '/:foo.:bar/:id', '/10.1/te.st', "foo" => "10", "bar" => "1", "id" => "te.st"
+ fails '/:foo.:bar/:id', '/10.1.2/te.st' # We don't do crazy.
- parses '/:a/:b.?:c?', '/a/b', "a" => "a", "b" => "b", "c" => nil
- parses '/:a/:b.?:c?', '/a/b.c', "a" => "a", "b" => "b", "c" => "c"
- parses '/:a/:b.?:c?', '/a.b/c', "a" => "a.b", "b" => "c", "c" => nil
- parses '/:a/:b.?:c?', '/a.b/c.d', "a" => "a.b", "b" => "c", "c" => "d"
+ parses '/:a/:b.?:c?', '/a/b', "a" => "a", "b" => "b", "c" => nil
+ parses '/:a/:b.?:c?', '/a/b.c', "a" => "a", "b" => "b", "c" => "c"
+ parses '/:a/:b.?:c?', '/a.b/c', "a" => "a.b", "b" => "c", "c" => nil
+ parses '/:a/:b.?:c?', '/a.b/c.d', "a" => "a.b", "b" => "c", "c" => "d"
+ fails '/:a/:b.?:c?', '/a.b/c.d/e'
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"
+ parses "/:file.:ext", "/pony正..jpg", "file" => "pony正", "ext" => ".jpg"
fails "/:file.:ext", "/pony正.%2ejpg"
- # parses "/:foo.:bar", "/file.tar.gz", "foo" => "file", "bar" => "tar.gz"
+ converts "/:name.:format", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)\z}
+ parses "/:name.:format", "/file.tar.gz", "name" => "file", "format" => "tar.gz"
+ parses "/:name.:format1.:format2", "/file.tar.gz", "name" => "file", "format1" => "tar", "format2" => "gz"
+ parses "/:name.:format1.:format2", "/file.temp.tar.gz", "name" => "file", "format1" => "temp", "format2" => "tar.gz"
# From issue #688.
#

0 comments on commit 2791c29

Please sign in to comment.