-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathurl.lua
175 lines (137 loc) · 3.55 KB
/
url.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
local tostring = tostring
local re_match = ngx.re.match
local concat = table.concat
local tonumber = tonumber
local setmetatable = setmetatable
local re_gsub = ngx.re.gsub
local select = select
local find = string.find
local sub = string.sub
local assert = assert
local _M = {
_VERSION = '0.3.4',
ports = {
https = 443,
http = 80,
}
}
function _M.default_port(scheme)
return _M.ports[scheme]
end
function _M.scheme(url)
local start = find(url, ':', 1, true)
if start then
return sub(url, 1, start - 1), sub(url, start + 1)
end
end
local core_base = require "resty.core.base"
local core_regex = require "resty.core.regex"
local new_tab = core_base.new_tab
local C = require('ffi').C
local function compile_regex(pattern)
local compiled, err, flags = core_regex.re_match_compile(pattern, 'joxi')
assert(compiled, err)
return compiled, flags
end
local collect_captures = core_regex.collect_captures
local abs_regex, abs_regex_flags = compile_regex([=[
^
(?:(\w+):)? # scheme (1)
//
(?:
([^:@]+)? # user (2)
(?:
:
([^@]+)? # password (3)
)?
@)?
( # host (4)
[a-z\.\-\d\_]+ # domain
|
[\d\.]+ # ipv4
|
\[[a-f0-9\:]+\] # ipv6
)
(?:
:(\d+) # port (5)
)?
(.*) # path (6)
$
]=])
local http_regex, http_regex_flags = compile_regex('^https?$')
local function match(str, regex, flags)
local res = new_tab(regex.ncaptures, regex.name_count)
if not str then return false, res end
local rc = C.ngx_http_lua_ffi_exec_regex(regex, flags, str, #str, 0)
return rc > 0, collect_captures(regex, rc, str, flags, res)
end
local function _match_opaque(scheme, opaque)
if match(scheme, http_regex, http_regex_flags) then
return nil, 'invalid endpoint'
end
return { scheme, opaque = opaque }
end
local function _transform_match(m)
m[0] = nil
if m[6] == '' or m[6] == '/' then m[6] = nil end
return m
end
function _M.split(url, protocol)
if not url then
return nil, 'missing endpoint'
end
local scheme, opaque = _M.scheme(url)
if not scheme then return nil, 'missing scheme' end
if protocol and not re_match(scheme, protocol, 'oj') then
return nil, 'invalid protocol'
end
local ok, m = match(url, abs_regex, abs_regex_flags)
if ok then
return _transform_match(m)
else
return _match_opaque(scheme, opaque)
end
end
function _M.parse(url, protocol)
local parts, err = _M.split(url, protocol)
if err then
return parts, err
end
-- https://tools.ietf.org/html/rfc3986#section-3
return setmetatable({
scheme = parts[1] or nil,
user = parts[2] or nil,
password = parts[3] or nil,
host = parts[4] or nil,
port = tonumber(parts[5]),
path = parts[6] or nil,
opaque = parts.opaque,
}, { __tostring = function() return url end })
end
function _M.normalize(uri)
local regex = [=[
( # Capture group
(?<!/)/ # Look for / that does not follow another /
# Look for file:///
(?(?<=\bfile:/) # if...
// # then look for // right after it
| # else
# Look for http:// or ftp://, etc.
(?(?<=:/) # if [stuff]:/
/ # then look for /
| # else
)
)
)
/+ # everything else with / after it
]=]
return re_gsub(uri, regex, '/', 'jox')
end
function _M.join(...)
local components = {}
for i=1, select('#', ...) do
components[i] = tostring(select(i, ...))
end
return _M.normalize(concat(components, '/'))
end
return _M