aboutsummaryrefslogtreecommitdiff
path: root/src/url.lua
diff options
context:
space:
mode:
Diffstat (limited to 'src/url.lua')
-rw-r--r--src/url.lua179
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
9local Public, Private = {}, {} 9-- make sure LuaSocket is loaded
10local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace 10if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end
11socket.url = Public 11-- get LuaSocket namespace
12local socket = _G[LUASOCKET_LIBNAME]
13if not socket then error('module requires LuaSocket') end
14-- create smtp namespace inside LuaSocket namespace
15local url = {}
16socket.url = url
17-- make all module globals fall into smtp namespace
18setmetatable(url, { __index = _G })
19setfenv(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-----------------------------------------------------------------------------
28function escape(s)
29 return string.gsub(s, "(.)", function(c)
30 return string.format("%%%02x", string.byte(c))
31 end)
32end
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-----------------------------------------------------------------------------
42local 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
48end
49
50-- these are allowed withing a path segment, along with alphanum
51-- other characters must be escaped
52local segment_set = make_set {
53 "-", "_", ".", "!", "~", "*", "'", "(",
54 ")", ":", "@", "&", "=", "+", "$", ",",
55}
56
57local 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)
62end
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-----------------------------------------------------------------------------
71function unescape(s)
72 return string.gsub(s, "%%(%x%x)", function(hex)
73 return string.char(tonumber(hex, 16))
74 end)
75end
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-----------------------------------------------------------------------------
85local 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
104end
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-----------------------------------------------------------------------------
31function Public.parse(url, default) 124function 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-----------------------------------------------------------------------------
73function Public.build(parsed) 166function 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-----------------------------------------------------------------------------
105function Public.absolute(base_url, relative_url) 198function 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
129end 222end
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-----------------------------------------------------------------------------
138function Public.parse_path(path) 231function 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-----------------------------------------------------------------------------
159function Public.build_path(parsed, unsafe) 252function 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
183end 276end
184
185function 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
191end
192
193-- these are allowed withing a path segment, along with alphanum
194-- other characters must be escaped
195Private.segment_set = Private.make_set {
196 "-", "_", ".", "!", "~", "*", "'", "(",
197 ")", ":", "@", "&", "=", "+", "$", ",",
198}
199
200function 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)
206end
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-----------------------------------------------------------------------------
216function 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
235end