aboutsummaryrefslogtreecommitdiff
path: root/src/url.lua
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/url.lua98
1 files changed, 61 insertions, 37 deletions
diff --git a/src/url.lua b/src/url.lua
index 7809535..8e0dc5c 100644
--- a/src/url.lua
+++ b/src/url.lua
@@ -49,7 +49,7 @@ local function make_set(t)
49 return s 49 return s
50end 50end
51 51
52-- these are allowed withing a path segment, along with alphanum 52-- these are allowed within a path segment, along with alphanum
53-- other characters must be escaped 53-- other characters must be escaped
54local segment_set = make_set { 54local segment_set = make_set {
55 "-", "_", ".", "!", "~", "*", "'", "(", 55 "-", "_", ".", "!", "~", "*", "'", "(",
@@ -59,16 +59,16 @@ local segment_set = make_set {
59local function protect_segment(s) 59local function protect_segment(s)
60 return string.gsub(s, "([^A-Za-z0-9_])", function (c) 60 return string.gsub(s, "([^A-Za-z0-9_])", function (c)
61 if segment_set[c] then return c 61 if segment_set[c] then return c
62 else return string.format("%%%02x", string.byte(c)) end 62 else return string.format("%%%02X", string.byte(c)) end
63 end) 63 end)
64end 64end
65 65
66----------------------------------------------------------------------------- 66-----------------------------------------------------------------------------
67-- Encodes a string into its escaped hexadecimal representation 67-- Unencodes a escaped hexadecimal string into its binary representation
68-- Input 68-- Input
69-- s: binary string to be encoded 69-- s: escaped hexadecimal string to be unencoded
70-- Returns 70-- Returns
71-- escaped representation of string binary 71-- unescaped binary representation of escaped hexadecimal binary
72----------------------------------------------------------------------------- 72-----------------------------------------------------------------------------
73function _M.unescape(s) 73function _M.unescape(s)
74 return (string.gsub(s, "%%(%x%x)", function(hex) 74 return (string.gsub(s, "%%(%x%x)", function(hex)
@@ -77,6 +77,34 @@ function _M.unescape(s)
77end 77end
78 78
79----------------------------------------------------------------------------- 79-----------------------------------------------------------------------------
80-- Removes '..' and '.' components appropriately from a path.
81-- Input
82-- path
83-- Returns
84-- dot-normalized path
85local function remove_dot_components(path)
86 local marker = string.char(1)
87 repeat
88 local was = path
89 path = path:gsub('//', '/'..marker..'/', 1)
90 until path == was
91 repeat
92 local was = path
93 path = path:gsub('/%./', '/', 1)
94 until path == was
95 repeat
96 local was = path
97 path = path:gsub('[^/]+/%.%./([^/]+)', '%1', 1)
98 until path == was
99 path = path:gsub('[^/]+/%.%./*$', '')
100 path = path:gsub('/%.%.$', '/')
101 path = path:gsub('/%.$', '/')
102 path = path:gsub('^/%.%./', '/')
103 path = path:gsub(marker, '')
104 return path
105end
106
107-----------------------------------------------------------------------------
80-- Builds a path from a base path and a relative path 108-- Builds a path from a base path and a relative path
81-- Input 109-- Input
82-- base_path 110-- base_path
@@ -85,23 +113,12 @@ end
85-- corresponding absolute path 113-- corresponding absolute path
86----------------------------------------------------------------------------- 114-----------------------------------------------------------------------------
87local function absolute_path(base_path, relative_path) 115local function absolute_path(base_path, relative_path)
88 if string.sub(relative_path, 1, 1) == "/" then return relative_path end 116 if string.sub(relative_path, 1, 1) == "/" then
89 local path = string.gsub(base_path, "[^/]*$", "") 117 return remove_dot_components(relative_path) end
90 path = path .. relative_path 118 base_path = base_path:gsub("[^/]*$", "")
91 path = string.gsub(path, "([^/]*%./)", function (s) 119 if not base_path:find'/$' then base_path = base_path .. '/' end
92 if s ~= "./" then return s else return "" end 120 local path = base_path .. relative_path
93 end) 121 path = remove_dot_components(path)
94 path = string.gsub(path, "/%.$", "/")
95 local reduced
96 while reduced ~= path do
97 reduced = path
98 path = string.gsub(reduced, "([^/]*/%.%./)", function (s)
99 if s ~= "../../" then return "" else return s end
100 end)
101 end
102 path = string.gsub(reduced, "([^/]*/%.%.)$", function (s)
103 if s ~= "../.." then return "" else return s end
104 end)
105 return path 122 return path
106end 123end
107 124
@@ -131,11 +148,6 @@ function _M.parse(url, default)
131 if not url or url == "" then return nil, "invalid url" end 148 if not url or url == "" then return nil, "invalid url" end
132 -- remove whitespace 149 -- remove whitespace
133 -- url = string.gsub(url, "%s", "") 150 -- url = string.gsub(url, "%s", "")
134 -- get fragment
135 url = string.gsub(url, "#(.*)$", function(f)
136 parsed.fragment = f
137 return ""
138 end)
139 -- get scheme 151 -- get scheme
140 url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", 152 url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
141 function(s) parsed.scheme = s; return "" end) 153 function(s) parsed.scheme = s; return "" end)
@@ -144,6 +156,11 @@ function _M.parse(url, default)
144 parsed.authority = n 156 parsed.authority = n
145 return "" 157 return ""
146 end) 158 end)
159 -- get fragment
160 url = string.gsub(url, "#(.*)$", function(f)
161 parsed.fragment = f
162 return ""
163 end)
147 -- get query string 164 -- get query string
148 url = string.gsub(url, "%?(.*)", function(q) 165 url = string.gsub(url, "%?(.*)", function(q)
149 parsed.query = q 166 parsed.query = q
@@ -162,9 +179,9 @@ function _M.parse(url, default)
162 function(u) parsed.userinfo = u; return "" end) 179 function(u) parsed.userinfo = u; return "" end)
163 authority = string.gsub(authority, ":([^:%]]*)$", 180 authority = string.gsub(authority, ":([^:%]]*)$",
164 function(p) parsed.port = p; return "" end) 181 function(p) parsed.port = p; return "" end)
165 if authority ~= "" then 182 if authority ~= "" then
166 -- IPv6? 183 -- IPv6?
167 parsed.host = string.match(authority, "^%[(.+)%]$") or authority 184 parsed.host = string.match(authority, "^%[(.+)%]$") or authority
168 end 185 end
169 local userinfo = parsed.userinfo 186 local userinfo = parsed.userinfo
170 if not userinfo then return parsed end 187 if not userinfo then return parsed end
@@ -183,8 +200,9 @@ end
183-- a stringing with the corresponding URL 200-- a stringing with the corresponding URL
184----------------------------------------------------------------------------- 201-----------------------------------------------------------------------------
185function _M.build(parsed) 202function _M.build(parsed)
186 local ppath = _M.parse_path(parsed.path or "") 203 --local ppath = _M.parse_path(parsed.path or "")
187 local url = _M.build_path(ppath) 204 --local url = _M.build_path(ppath)
205 local url = parsed.path or ""
188 if parsed.params then url = url .. ";" .. parsed.params end 206 if parsed.params then url = url .. ";" .. parsed.params end
189 if parsed.query then url = url .. "?" .. parsed.query end 207 if parsed.query then url = url .. "?" .. parsed.query end
190 local authority = parsed.authority 208 local authority = parsed.authority
@@ -193,7 +211,7 @@ function _M.build(parsed)
193 if string.find(authority, ":") then -- IPv6? 211 if string.find(authority, ":") then -- IPv6?
194 authority = "[" .. authority .. "]" 212 authority = "[" .. authority .. "]"
195 end 213 end
196 if parsed.port then authority = authority .. ":" .. parsed.port end 214 if parsed.port then authority = authority .. ":" .. base.tostring(parsed.port) end
197 local userinfo = parsed.userinfo 215 local userinfo = parsed.userinfo
198 if parsed.user then 216 if parsed.user then
199 userinfo = parsed.user 217 userinfo = parsed.user
@@ -219,16 +237,21 @@ end
219-- corresponding absolute url 237-- corresponding absolute url
220----------------------------------------------------------------------------- 238-----------------------------------------------------------------------------
221function _M.absolute(base_url, relative_url) 239function _M.absolute(base_url, relative_url)
240 local base_parsed
222 if base.type(base_url) == "table" then 241 if base.type(base_url) == "table" then
223 base_parsed = base_url 242 base_parsed = base_url
224 base_url = _M.build(base_parsed) 243 base_url = _M.build(base_parsed)
225 else 244 else
226 base_parsed = _M.parse(base_url) 245 base_parsed = _M.parse(base_url)
227 end 246 end
247 local result
228 local relative_parsed = _M.parse(relative_url) 248 local relative_parsed = _M.parse(relative_url)
229 if not base_parsed then return relative_url 249 if not base_parsed then
230 elseif not relative_parsed then return base_url 250 result = relative_url
231 elseif relative_parsed.scheme then return relative_url 251 elseif not relative_parsed then
252 result = base_url
253 elseif relative_parsed.scheme then
254 result = relative_url
232 else 255 else
233 relative_parsed.scheme = base_parsed.scheme 256 relative_parsed.scheme = base_parsed.scheme
234 if not relative_parsed.authority then 257 if not relative_parsed.authority then
@@ -241,13 +264,14 @@ function _M.absolute(base_url, relative_url)
241 relative_parsed.query = base_parsed.query 264 relative_parsed.query = base_parsed.query
242 end 265 end
243 end 266 end
244 else 267 else
245 relative_parsed.path = absolute_path(base_parsed.path or "", 268 relative_parsed.path = absolute_path(base_parsed.path or "",
246 relative_parsed.path) 269 relative_parsed.path)
247 end 270 end
248 end 271 end
249 return _M.build(relative_parsed) 272 result = _M.build(relative_parsed)
250 end 273 end
274 return remove_dot_components(result)
251end 275end
252 276
253----------------------------------------------------------------------------- 277-----------------------------------------------------------------------------