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