diff options
Diffstat (limited to 'src/url.lua')
| -rw-r--r-- | src/url.lua | 179 |
1 files changed, 110 insertions, 69 deletions
diff --git a/src/url.lua b/src/url.lua index 27e7928..ab3a922 100644 --- a/src/url.lua +++ b/src/url.lua | |||
| @@ -6,9 +6,102 @@ | |||
| 6 | -- RCS ID: $Id$ | 6 | -- RCS ID: $Id$ |
| 7 | ---------------------------------------------------------------------------- | 7 | ---------------------------------------------------------------------------- |
| 8 | 8 | ||
| 9 | local Public, Private = {}, {} | 9 | -- make sure LuaSocket is loaded |
| 10 | local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace | 10 | if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end |
| 11 | socket.url = Public | 11 | -- get LuaSocket namespace |
| 12 | local socket = _G[LUASOCKET_LIBNAME] | ||
| 13 | if not socket then error('module requires LuaSocket') end | ||
| 14 | -- create smtp namespace inside LuaSocket namespace | ||
| 15 | local url = {} | ||
| 16 | socket.url = url | ||
| 17 | -- make all module globals fall into smtp namespace | ||
| 18 | setmetatable(url, { __index = _G }) | ||
| 19 | setfenv(1, url) | ||
| 20 | |||
| 21 | ----------------------------------------------------------------------------- | ||
| 22 | -- Encodes a string into its escaped hexadecimal representation | ||
| 23 | -- Input | ||
| 24 | -- s: binary string to be encoded | ||
| 25 | -- Returns | ||
| 26 | -- escaped representation of string binary | ||
| 27 | ----------------------------------------------------------------------------- | ||
| 28 | function escape(s) | ||
| 29 | return string.gsub(s, "(.)", function(c) | ||
| 30 | return string.format("%%%02x", string.byte(c)) | ||
| 31 | end) | ||
| 32 | end | ||
| 33 | |||
| 34 | ----------------------------------------------------------------------------- | ||
| 35 | -- Protects a path segment, to prevent it from interfering with the | ||
| 36 | -- url parsing. | ||
| 37 | -- Input | ||
| 38 | -- s: binary string to be encoded | ||
| 39 | -- Returns | ||
| 40 | -- escaped representation of string binary | ||
| 41 | ----------------------------------------------------------------------------- | ||
| 42 | local function make_set(t) | ||
| 43 | local s = {} | ||
| 44 | for i = 1, table.getn(t) do | ||
| 45 | s[t[i]] = 1 | ||
| 46 | end | ||
| 47 | return s | ||
| 48 | end | ||
| 49 | |||
| 50 | -- these are allowed withing a path segment, along with alphanum | ||
| 51 | -- other characters must be escaped | ||
| 52 | local segment_set = make_set { | ||
| 53 | "-", "_", ".", "!", "~", "*", "'", "(", | ||
| 54 | ")", ":", "@", "&", "=", "+", "$", ",", | ||
| 55 | } | ||
| 56 | |||
| 57 | local function protect_segment(s) | ||
| 58 | return string.gsub(s, "(%W)", function (c) | ||
| 59 | if segment_set[c] then return c | ||
| 60 | else return escape(c) end | ||
| 61 | end) | ||
| 62 | end | ||
| 63 | |||
| 64 | ----------------------------------------------------------------------------- | ||
| 65 | -- Encodes a string into its escaped hexadecimal representation | ||
| 66 | -- Input | ||
| 67 | -- s: binary string to be encoded | ||
| 68 | -- Returns | ||
| 69 | -- escaped representation of string binary | ||
| 70 | ----------------------------------------------------------------------------- | ||
| 71 | function unescape(s) | ||
| 72 | return string.gsub(s, "%%(%x%x)", function(hex) | ||
| 73 | return string.char(tonumber(hex, 16)) | ||
| 74 | end) | ||
| 75 | end | ||
| 76 | |||
| 77 | ----------------------------------------------------------------------------- | ||
| 78 | -- Builds a path from a base path and a relative path | ||
| 79 | -- Input | ||
| 80 | -- base_path | ||
| 81 | -- relative_path | ||
| 82 | -- Returns | ||
| 83 | -- corresponding absolute path | ||
| 84 | ----------------------------------------------------------------------------- | ||
| 85 | local function absolute_path(base_path, relative_path) | ||
| 86 | if string.sub(relative_path, 1, 1) == "/" then return relative_path end | ||
| 87 | local path = string.gsub(base_path, "[^/]*$", "") | ||
| 88 | path = path .. relative_path | ||
| 89 | path = string.gsub(path, "([^/]*%./)", function (s) | ||
| 90 | if s ~= "./" then return s else return "" end | ||
| 91 | end) | ||
| 92 | path = string.gsub(path, "/%.$", "/") | ||
| 93 | local reduced | ||
| 94 | while reduced ~= path do | ||
| 95 | reduced = path | ||
| 96 | path = string.gsub(reduced, "([^/]*/%.%./)", function (s) | ||
| 97 | if s ~= "../../" then return "" else return s end | ||
| 98 | end) | ||
| 99 | end | ||
| 100 | path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) | ||
| 101 | if s ~= "../.." then return "" else return s end | ||
| 102 | end) | ||
| 103 | return path | ||
| 104 | end | ||
| 12 | 105 | ||
| 13 | ----------------------------------------------------------------------------- | 106 | ----------------------------------------------------------------------------- |
| 14 | -- Parses a url and returns a table with all its parts according to RFC 2396 | 107 | -- Parses a url and returns a table with all its parts according to RFC 2396 |
| @@ -28,7 +121,7 @@ socket.url = Public | |||
| 28 | -- Obs: | 121 | -- Obs: |
| 29 | -- the leading '/' in {/<path>} is considered part of <path> | 122 | -- the leading '/' in {/<path>} is considered part of <path> |
| 30 | ----------------------------------------------------------------------------- | 123 | ----------------------------------------------------------------------------- |
| 31 | function Public.parse(url, default) | 124 | function parse(url, default) |
| 32 | -- initialize default parameters | 125 | -- initialize default parameters |
| 33 | local parsed = default or {} | 126 | local parsed = default or {} |
| 34 | -- empty url is parsed to nil | 127 | -- empty url is parsed to nil |
| @@ -66,11 +159,11 @@ end | |||
| 66 | -- Rebuilds a parsed URL from its components. | 159 | -- Rebuilds a parsed URL from its components. |
| 67 | -- Components are protected if any reserved or unallowed characters are found | 160 | -- Components are protected if any reserved or unallowed characters are found |
| 68 | -- Input | 161 | -- Input |
| 69 | -- parsed: parsed URL, as returned by Public.parse | 162 | -- parsed: parsed URL, as returned by parse |
| 70 | -- Returns | 163 | -- Returns |
| 71 | -- a stringing with the corresponding URL | 164 | -- a stringing with the corresponding URL |
| 72 | ----------------------------------------------------------------------------- | 165 | ----------------------------------------------------------------------------- |
| 73 | function Public.build(parsed) | 166 | function build(parsed) |
| 74 | local url = parsed.path or "" | 167 | local url = parsed.path or "" |
| 75 | if parsed.params then url = url .. ";" .. parsed.params end | 168 | if parsed.params then url = url .. ";" .. parsed.params end |
| 76 | if parsed.query then url = url .. "?" .. parsed.query end | 169 | if parsed.query then url = url .. "?" .. parsed.query end |
| @@ -102,9 +195,9 @@ end | |||
| 102 | -- Returns | 195 | -- Returns |
| 103 | -- corresponding absolute url | 196 | -- corresponding absolute url |
| 104 | ----------------------------------------------------------------------------- | 197 | ----------------------------------------------------------------------------- |
| 105 | function Public.absolute(base_url, relative_url) | 198 | function absolute(base_url, relative_url) |
| 106 | local base = Public.parse(base_url) | 199 | local base = parse(base_url) |
| 107 | local relative = Public.parse(relative_url) | 200 | local relative = parse(relative_url) |
| 108 | if not base then return relative_url | 201 | if not base then return relative_url |
| 109 | elseif not relative then return base_url | 202 | elseif not relative then return base_url |
| 110 | elseif relative.scheme then return relative_url | 203 | elseif relative.scheme then return relative_url |
| @@ -121,10 +214,10 @@ function Public.absolute(base_url, relative_url) | |||
| 121 | end | 214 | end |
| 122 | end | 215 | end |
| 123 | else | 216 | else |
| 124 | relative.path = Private.absolute_path(base.path,relative.path) | 217 | relative.path = absolute_path(base.path,relative.path) |
| 125 | end | 218 | end |
| 126 | end | 219 | end |
| 127 | return Public.build(relative) | 220 | return build(relative) |
| 128 | end | 221 | end |
| 129 | end | 222 | end |
| 130 | 223 | ||
| @@ -135,13 +228,13 @@ end | |||
| 135 | -- Returns | 228 | -- Returns |
| 136 | -- segment: a table with one entry per segment | 229 | -- segment: a table with one entry per segment |
| 137 | ----------------------------------------------------------------------------- | 230 | ----------------------------------------------------------------------------- |
| 138 | function Public.parse_path(path) | 231 | function parse_path(path) |
| 139 | local parsed = {} | 232 | local parsed = {} |
| 140 | path = path or "" | 233 | path = path or "" |
| 141 | path = string.gsub(path, "%s", "") | 234 | path = string.gsub(path, "%s", "") |
| 142 | string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) | 235 | string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) |
| 143 | for i = 1, table.getn(parsed) do | 236 | for i = 1, table.getn(parsed) do |
| 144 | parsed[i] = socket.code.unescape(parsed[i]) | 237 | parsed[i] = unescape(parsed[i]) |
| 145 | end | 238 | end |
| 146 | if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end | 239 | if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end |
| 147 | if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end | 240 | if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end |
| @@ -154,9 +247,9 @@ end | |||
| 154 | -- parsed: path segments | 247 | -- parsed: path segments |
| 155 | -- unsafe: if true, segments are not protected before path is built | 248 | -- unsafe: if true, segments are not protected before path is built |
| 156 | -- Returns | 249 | -- Returns |
| 157 | -- path: correspondin path stringing | 250 | -- path: corresponding path stringing |
| 158 | ----------------------------------------------------------------------------- | 251 | ----------------------------------------------------------------------------- |
| 159 | function Public.build_path(parsed, unsafe) | 252 | function build_path(parsed, unsafe) |
| 160 | local path = "" | 253 | local path = "" |
| 161 | local n = table.getn(parsed) | 254 | local n = table.getn(parsed) |
| 162 | if unsafe then | 255 | if unsafe then |
| @@ -170,66 +263,14 @@ function Public.build_path(parsed, unsafe) | |||
| 170 | end | 263 | end |
| 171 | else | 264 | else |
| 172 | for i = 1, n-1 do | 265 | for i = 1, n-1 do |
| 173 | path = path .. Private.protect_segment(parsed[i]) | 266 | path = path .. protect_segment(parsed[i]) |
| 174 | path = path .. "/" | 267 | path = path .. "/" |
| 175 | end | 268 | end |
| 176 | if n > 0 then | 269 | if n > 0 then |
| 177 | path = path .. Private.protect_segment(parsed[n]) | 270 | path = path .. protect_segment(parsed[n]) |
| 178 | if parsed.is_directory then path = path .. "/" end | 271 | if parsed.is_directory then path = path .. "/" end |
| 179 | end | 272 | end |
| 180 | end | 273 | end |
| 181 | if parsed.is_absolute then path = "/" .. path end | 274 | if parsed.is_absolute then path = "/" .. path end |
| 182 | return path | 275 | return path |
| 183 | end | 276 | end |
| 184 | |||
| 185 | function Private.make_set(t) | ||
| 186 | local s = {} | ||
| 187 | for i = 1, table.getn(t) do | ||
| 188 | s[t[i]] = 1 | ||
| 189 | end | ||
| 190 | return s | ||
| 191 | end | ||
| 192 | |||
| 193 | -- these are allowed withing a path segment, along with alphanum | ||
| 194 | -- other characters must be escaped | ||
| 195 | Private.segment_set = Private.make_set { | ||
| 196 | "-", "_", ".", "!", "~", "*", "'", "(", | ||
| 197 | ")", ":", "@", "&", "=", "+", "$", ",", | ||
| 198 | } | ||
| 199 | |||
| 200 | function Private.protect_segment(s) | ||
| 201 | local segment_set = Private.segment_set | ||
| 202 | return string.gsub(s, "(%W)", function (c) | ||
| 203 | if segment_set[c] then return c | ||
| 204 | else return socket.code.escape(c) end | ||
| 205 | end) | ||
| 206 | end | ||
| 207 | |||
| 208 | ----------------------------------------------------------------------------- | ||
| 209 | -- Builds a path from a base path and a relative path | ||
| 210 | -- Input | ||
| 211 | -- base_path | ||
| 212 | -- relative_path | ||
| 213 | -- Returns | ||
| 214 | -- corresponding absolute path | ||
| 215 | ----------------------------------------------------------------------------- | ||
| 216 | function Private.absolute_path(base_path, relative_path) | ||
| 217 | if string.sub(relative_path, 1, 1) == "/" then return relative_path end | ||
| 218 | local path = string.gsub(base_path, "[^/]*$", "") | ||
| 219 | path = path .. relative_path | ||
| 220 | path = string.gsub(path, "([^/]*%./)", function (s) | ||
| 221 | if s ~= "./" then return s else return "" end | ||
| 222 | end) | ||
| 223 | path = string.gsub(path, "/%.$", "/") | ||
| 224 | local reduced | ||
| 225 | while reduced ~= path do | ||
| 226 | reduced = path | ||
| 227 | path = string.gsub(reduced, "([^/]*/%.%./)", function (s) | ||
| 228 | if s ~= "../../" then return "" else return s end | ||
| 229 | end) | ||
| 230 | end | ||
| 231 | path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) | ||
| 232 | if s ~= "../.." then return "" else return s end | ||
| 233 | end) | ||
| 234 | return path | ||
| 235 | end | ||
