diff --git a/CHANGELOG.md b/CHANGELOG.md index d5aaf6d..b56d995 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +- Add `trailing_slash` http route option. + +======= ### Fixed - Fixed request crash with empty body and unexpected header Content-Type (#189) diff --git a/http/server.lua b/http/server.lua index 286be38..4430ffb 100644 --- a/http/server.lua +++ b/http/server.lua @@ -993,10 +993,6 @@ local function httpd_stop(self) end local function match_route(self, method, route) - -- route must have '/' at the begin and end - if string.match(route, '.$') ~= '/' then - route = route .. '/' - end if string.match(route, '^.') ~= '/' then route = '/' .. route end @@ -1007,8 +1003,12 @@ local function match_route(self, method, route) local stash = {} for _, r in pairs(self.routes) do + local sroute = route if r.method == method or r.method == 'ANY' then - local m = { string.match(route, r.match) } + if r.trailing_slash and string.match(route, '.$') ~= '/' then + sroute = route .. '/' + end + local m = { string.match(sroute, r.match) } local nfit if #m > 0 then if #r.stash > 0 then @@ -1157,6 +1157,7 @@ local function add_route(self, opts, sub) end opts = extend({method = 'ANY'}, opts, false) + opts = extend({trailing_slash = true}, opts, false) local ctx local action @@ -1218,7 +1219,7 @@ local function add_route(self, opts, sub) table.insert(stash, name) end - if string.match(opts.match, '.$') ~= '/' then + if string.match(opts.match, '.$') ~= '/' and opts.trailing_slash then opts.match = opts.match .. '/' end if string.match(opts.match, '^.') ~= '/' then diff --git a/test/helpers.lua b/test/helpers.lua index cf8d243..6a86218 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -36,6 +36,10 @@ helpers.cfgserv = function(opts) :route({path = '/test', file = 'test.html.el' }, function(cx) return cx:render({ title = 'title: 123' }) end) + :route({path = '/trailing_slash_f/a/b/*c', trailing_slash = false}, + function(req) return req:render({text = req:stash("c")}) end) + :route({path = '/trailing_slash_t/a/b/*c', trailing_slash = true}, + function(req) return req:render({text = req:stash("c")}) end) return httpd end diff --git a/test/integration/http_server_requests_test.lua b/test/integration/http_server_requests_test.lua index 06fd185..625c4b1 100644 --- a/test/integration/http_server_requests_test.lua +++ b/test/integration/http_server_requests_test.lua @@ -418,6 +418,71 @@ g.test_content_type_header_without_render = function() t.assert_equals(r.headers['content-type'], 'text/plain; charset=utf-8', 'content-type header') end +g.test_trailing_slash_f_get = function() + local r = http_client.get(helpers.base_uri .. '/trailing_slash_f/a/b/c') + t.assert_equals(r.status, 200) + t.assert_equals(r.body, 'c') +end + +g.test_trailing_slash_f_get_with_slash_at_begging = function() + local r = http_client.get(helpers.base_uri .. '/trailing_slash_f/a/b//c') + t.assert_equals(r.status, 200) + t.assert_equals(r.body, '/c') +end + +g.test_trailing_slash_f_get_with_slash_at_begging_and_end = function() + local r = http_client.get(helpers.base_uri .. '/trailing_slash_f/a/b//c/') + t.assert_equals(r.status, 200) + t.assert_equals(r.body, '/c/') +end + +g.test_trailing_slash_f_get_with_slash = function() + local r = http_client.get(helpers.base_uri .. '/trailing_slash_f/a/b/c/') + t.assert_equals(r.status, 200) + t.assert_equals(r.body, 'c/') +end + +g.test_trailing_slash_f_get_with_encoded_slash_begging = function() + local r = http_client.get(helpers.base_uri .. '/trailing_slash_f/a/b/%2Fc') + t.assert_equals(r.status, 200) + t.assert_equals(r.body, '/c') +end + +g.test_trailing_slash_f_get_with_encoded_slash_begging_and_end = function() + local r = http_client.get(helpers.base_uri .. '/trailing_slash_f/a/b/%2Fc%2F') + t.assert_equals(r.status, 200) + t.assert_equals(r.body, '/c/') +end + +g.test_trailing_slash_f_get_html = function() + local r = http_client.get(helpers.base_uri .. '/trailing_slash_f/a/b/c.htm') + t.assert_equals(r.status, 200) + t.assert_equals(r.body, 'c.htm') +end + +g.test_trailing_slash_f_get_long = function() + local r = http_client.get(helpers.base_uri .. '/trailing_slash_f/a/b/c/d/e') + t.assert_equals(r.status, 200) + t.assert_equals(r.body, 'c/d/e') +end + +g.test_trailing_slash_f_get_long_with_slash_end = function() + local r = http_client.get(helpers.base_uri .. '/trailing_slash_f/a/b/c/d/e/') + t.assert_equals(r.status, 200) + t.assert_equals(r.body, 'c/d/e/') +end + +g.test_trailing_slash_t_get_with_slash_at_begging_and_end = function() + local r = http_client.get(helpers.base_uri .. '/trailing_slash_t/a/b//c/') + t.assert_equals(r.status, 200) + t.assert_equals(r.body, '/c') +end + +g.test_trailing_slash_t_get_with_encoded_slash_begging_and_end = function() + local r = http_client.get(helpers.base_uri .. '/trailing_slash_t/a/b/%2Fc%2F') + t.assert_equals(r.status, 200) + t.assert_equals(r.body, '/c') + g.test_get_dot_slash = function() local httpd = g.httpd httpd:route({ @@ -445,4 +510,5 @@ g.test_unwanted_content_type = function() local r = http_client.get(helpers.base_uri .. '/unwanted-content-type', opt) t.assert_equals(r.status, 200) t.assert_equals(r.body, '[]') + end diff --git a/test/integration/http_server_url_match_test.lua b/test/integration/http_server_url_match_test.lua index 284e201..6c302d6 100644 --- a/test/integration/http_server_url_match_test.lua +++ b/test/integration/http_server_url_match_test.lua @@ -46,4 +46,18 @@ g.test_server_url_match = function() '-123-dea/1/2/3', '/abb-123-dea/1/2/3/cde') t.assert_equals(httpd:match('GET', '/banners/1wulc.z8kiy.6p5e3').stash.token, '1wulc.z8kiy.6p5e3', 'stash with dots') + + t.assert_equals(httpd:match('GET', '/trailing_slash_t/a/b/c').endpoint.path, '/trailing_slash_t/a/b/*c') + t.assert_equals(httpd:match('GET', '/trailing_slash_t/a/b/c').stash.c, 'c') + t.assert_equals(httpd:match('GET', '/trailing_slash_t/a/b//c/').endpoint.path, '/trailing_slash_t/a/b/*c') + t.assert_equals(httpd:match('GET', '/trailing_slash_t/a/b//c/').stash.c, '/c') + + t.assert_equals(httpd:match('GET', '/trailing_slash_f/a/b/c').endpoint.path, '/trailing_slash_f/a/b/*c') + t.assert_equals(httpd:match('GET', '/trailing_slash_f/a/b/c').stash.c, 'c') + t.assert_equals(httpd:match('GET', '/trailing_slash_f/a/b//c').endpoint.path, '/trailing_slash_f/a/b/*c') + t.assert_equals(httpd:match('GET', '/trailing_slash_f/a/b//c').stash.c, '/c') + t.assert_equals(httpd:match('GET', '/trailing_slash_f/a/b//c/').endpoint.path, '/trailing_slash_f/a/b/*c') + t.assert_equals(httpd:match('GET', '/trailing_slash_f/a/b//c/').stash.c, '/c/') + t.assert_equals(httpd:match('GET', '/trailing_slash_f/a/b/c.htm').endpoint.path, '/trailing_slash_f/a/b/*c') + t.assert_equals(httpd:match('GET', '/trailing_slash_f/a/b/c.htm').stash.c, 'c.htm') end