1
2
3
4local tonumber, tostring, type = tonumber, tostring, type
5
6local gsub, sub, match, find, format, byte, char = string.gsub, string.sub, string.match, string.find, string.format, string.byte, string.char
7local insert = table.insert
8
9local socket = socket or require("socket")
10
11local url = {
12 _VERSION = "URL 1.0.3",
13}
14
15socket.url = url
16
17function url.escape(s)
18 return (gsub(s, "([^A-Za-z0-9_])", function(c)
19 return format("%%%02x", byte(c))
20 end))
21end
22
23local function make_set(t)
24 local s = { }
25 for i=1,#t do
26 s[t[i]] = true
27 end
28 return s
29end
30
31local segment_set = make_set {
32 "-", "_", ".", "!", "~", "*", "'", "(",
33 ")", ":", "@", "&", "=", "+", "$", ",",
34}
35
36local function protect_segment(s)
37 return gsub(s, "([^A-Za-z0-9_])", function(c)
38 if segment_set[c] then
39 return c
40 else
41 return format("%%%02X", byte(c))
42 end
43 end)
44end
45
46function url.unescape(s)
47 return (gsub(s, "%%(%x%x)", function(hex)
48 return char(tonumber(hex,16))
49 end))
50end
51
52local function absolute_path(base_path, relative_path)
53 if find(relative_path,"^/") then
54 return relative_path
55 end
56 local path = gsub(base_path, "[^/]*$", "")
57 path = path .. relative_path
58 path = gsub(path, "([^/]*%./)", function (s)
59 if s ~= "./" then
60 return s
61 else
62 return ""
63 end
64 end)
65 path = gsub(path, "/%.$", "/")
66 local reduced
67 while reduced ~= path do
68 reduced = path
69 path = gsub(reduced, "([^/]*/%.%./)", function (s)
70 if s ~= "../../" then
71 return ""
72 else
73 return s
74 end
75 end)
76 end
77 path = gsub(reduced, "([^/]*/%.%.)$", function (s)
78 if s ~= "../.." then
79 return ""
80 else
81 return s
82 end
83 end)
84 return path
85end
86
87function url.parse(url, default)
88 local parsed = { }
89 for k, v in next, default or parsed do
90 parsed[k] = v
91 end
92 if not url or url == "" then
93 return nil, "invalid url"
94 end
95 url = gsub(url, "#(.*)$", function(f)
96 parsed.fragment = f
97 return ""
98 end)
99 url = gsub(url, "^([%w][%w%+%-%.]*)%:", function(s)
100 parsed.scheme = s
101 return ""
102 end)
103 url = gsub(url, "^//([^/]*)", function(n)
104 parsed.authority = n
105 return ""
106 end)
107 url = gsub(url, "%?(.*)", function(q)
108 parsed.query = q
109 return ""
110 end)
111 url = gsub(url, "%;(.*)", function(p)
112 parsed.params = p
113 return ""
114 end)
115 if url ~= "" then
116 parsed.path = url
117 end
118 local authority = parsed.authority
119 if not authority then
120 return parsed
121 end
122 authority = gsub(authority,"^([^@]*)@", function(u)
123 parsed.userinfo = u
124 return ""
125 end)
126 authority = gsub(authority, ":([^:%]]*)$", function(p)
127 parsed.port = p
128 return ""
129 end)
130 if authority ~= "" then
131 parsed.host = match(authority, "^%[(.+)%]$") or authority
132 end
133 local userinfo = parsed.userinfo
134 if not userinfo then
135 return parsed
136 end
137 userinfo = gsub(userinfo, ":([^:]*)$", function(p)
138 parsed.password = p
139 return ""
140 end)
141 parsed.user = userinfo
142 return parsed
143end
144
145function url.build(parsed)
146 local url = parsed.path or ""
147 if parsed.params then
148 url = url .. ";" .. parsed.params
149 end
150 if parsed.query then
151 url = url .. "?" .. parsed.query
152 end
153 local authority = parsed.authority
154 if parsed.host then
155 authority = parsed.host
156 if find(authority, ":") then
157 authority = "[" .. authority .. "]"
158 end
159 if parsed.port then
160 authority = authority .. ":" .. tostring(parsed.port)
161 end
162 local userinfo = parsed.userinfo
163 if parsed.user then
164 userinfo = parsed.user
165 if parsed.password then
166 userinfo = userinfo .. ":" .. parsed.password
167 end
168 end
169 if userinfo then authority = userinfo .. "@" .. authority end
170 end
171 if authority then
172 url = "//" .. authority .. url
173 end
174 if parsed.scheme then
175 url = parsed.scheme .. ":" .. url
176 end
177 if parsed.fragment then
178 url = url .. "#" .. parsed.fragment
179 end
180 return url
181end
182
183function url.absolute(base_url, relative_url)
184 local base_parsed
185 if type(base_url) == "table" then
186 base_parsed = base_url
187 base_url = url.build(base_parsed)
188 else
189 base_parsed = url.parse(base_url)
190 end
191 local relative_parsed = url.parse(relative_url)
192 if not base_parsed then
193 return relative_url
194 elseif not relative_parsed then
195 return base_url
196 elseif relative_parsed.scheme then
197 return relative_url
198 else
199 relative_parsed.scheme = base_parsed.scheme
200 if not relative_parsed.authority then
201 relative_parsed.authority = base_parsed.authority
202 if not relative_parsed.path then
203 relative_parsed.path = base_parsed.path
204 if not relative_parsed.params then
205 relative_parsed.params = base_parsed.params
206 if not relative_parsed.query then
207 relative_parsed.query = base_parsed.query
208 end
209 end
210 else
211 relative_parsed.path = absolute_path(base_parsed.path or "", relative_parsed.path)
212 end
213 end
214 return url.build(relative_parsed)
215 end
216end
217
218function url.parse_path(path)
219 local parsed = { }
220 path = path or ""
221 gsub(path, "([^/]+)", function (s)
222 insert(parsed, s)
223 end)
224 for i=1,#parsed do
225 parsed[i] = url.unescape(parsed[i])
226 end
227 if sub(path, 1, 1) == "/" then
228 parsed.is_absolute = 1
229 end
230 if sub(path, -1, -1) == "/" then
231 parsed.is_directory = 1
232 end
233 return parsed
234end
235
236function url.build_path(parsed, unsafe)
237 local path = ""
238 local n = #parsed
239 if unsafe then
240 for i = 1, n-1 do
241 path = path .. parsed[i] .. "/"
242 end
243 if n > 0 then
244 path = path .. parsed[n]
245 if parsed.is_directory then
246 path = path .. "/"
247 end
248 end
249 else
250 for i = 1, n-1 do
251 path = path .. protect_segment(parsed[i]) .. "/"
252 end
253 if n > 0 then
254 path = path .. protect_segment(parsed[n])
255 if parsed.is_directory then
256 path = path .. "/"
257 end
258 end
259 end
260 if parsed.is_absolute then
261 path = "/" .. path
262 end
263 return path
264end
265
266package.loaded["socket.url"] = url
267
268return url
269 |