forked from daurnimator/lua-http
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutil.lua
More file actions
230 lines (211 loc) · 6.21 KB
/
util.lua
File metadata and controls
230 lines (211 loc) · 6.21 KB
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
local lpeg = require "lpeg"
local http_patts = require "lpeg_patterns.http"
-- Encodes a character as a percent encoded string
local function char_to_pchar(c)
return string.format("%%%02X", c:byte(1,1))
end
-- encodeURI replaces all characters except the following with the appropriate UTF-8 escape sequences:
-- ; , / ? : @ & = + $
-- alphabetic, decimal digits, - _ . ! ~ * ' ( )
-- #
local function encodeURI(str)
return (str:gsub("[^%;%,%/%?%:%@%&%=%+%$%w%-%_%.%!%~%*%'%(%)%#]", char_to_pchar))
end
-- encodeURIComponent escapes all characters except the following: alphabetic, decimal digits, - _ . ! ~ * ' ( )
local function encodeURIComponent(str)
return (str:gsub("[^%w%-_%.%!%~%*%'%(%)]", char_to_pchar))
end
-- decodeURI unescapes url encoded characters
-- excluding for characters that are special in urls
local decodeURI do
-- Keep the blacklist in numeric form.
-- This means we can skip case normalisation of the hex characters
local decodeURI_blacklist = {}
for char in ("#$&+,/:;=?@"):gmatch(".") do
decodeURI_blacklist[string.byte(char)] = true
end
local function decodeURI_helper(str)
local x = tonumber(str, 16)
if not decodeURI_blacklist[x] then
return string.char(x)
end
-- return nothing; gsub will not perform the replacement
end
function decodeURI(str)
return (str:gsub("%%(%x%x)", decodeURI_helper))
end
end
-- Converts a hex string to a character
local function pchar_to_char(str)
return string.char(tonumber(str, 16))
end
-- decodeURIComponent unescapes *all* url encoded characters
local function decodeURIComponent(str)
return (str:gsub("%%(%x%x)", pchar_to_char))
end
-- An iterator over query segments (delimited by "&") as key/value pairs
-- if a query segment has no '=', the value will be `nil`
local function query_args(str)
local iter, state, first = str:gmatch("([^=&]+)(=?)([^&]*)&?")
return function(state, last) -- luacheck: ignore 431
local name, equals, value = iter(state, last)
if name == nil then return nil end
name = decodeURIComponent(name)
if equals == "" then
value = nil
else
value = decodeURIComponent(value)
end
return name, value
end, state, first
end
-- Converts a dictionary (string keys, string values) to an encoded query string
local function dict_to_query(form)
local r, i = {}, 0
for name, value in pairs(form) do
i = i + 1
r[i] = encodeURIComponent(name).."="..encodeURIComponent(value)
end
return table.concat(r, "&", 1, i)
end
-- Resolves a relative path
local function resolve_relative_path(orig_path, relative_path)
local t, i = {}, 0
local is_abs
if relative_path:sub(1,1) == "/" then
-- "relative" argument is actually absolute. ignore orig_path argument
is_abs = true
else
is_abs = orig_path:sub(1,1) == "/"
-- this will skip empty path components due to +
-- the / on the end ignores trailing component
for segment in orig_path:gmatch("([^/]+)/") do
i = i + 1
t[i] = segment
end
end
for segment in relative_path:gmatch("([^/]+)") do
if segment == ".." then
-- if we're at the root, do nothing
if i > 0 then
-- discard a component
i = i - 1
end
elseif segment ~= "." then
i = i + 1
t[i] = segment
end
end
-- Make sure leading slash is kept
local s
if is_abs then
if i == 0 then return "/" end
t[0] = ""
s = 0
else
s = 1
end
-- Make sure trailing slash is kept
if relative_path:sub(-1, -1) == "/" then
i = i + 1
t[i] = ""
end
return table.concat(t, "/", s, i)
end
local scheme_to_port = {
http = 80;
ws = 80;
https = 443;
wss = 443;
}
-- Splits a :authority header (same as Host) into host and port
local function split_authority(authority, scheme)
local host, port
local h, p = authority:match("^[ \t]*(.-):(%d+)[ \t]*$")
if p then
authority = h
port = tonumber(p, 10)
else -- when port missing from host header, it defaults to the default for that scheme
port = scheme_to_port[scheme]
if port == nil then
return nil, "unknown scheme"
end
end
local ipv6 = authority:match("^%[([:%x]+)%]$")
if ipv6 then
host = ipv6
else
host = authority
end
return host, port
end
-- Reverse of `split_authority`: converts a host, port and scheme
-- into a string suitable for an :authority header.
local function to_authority(host, port, scheme)
local authority = host
if host:match("^[%x:]+:[%x:]*$") then -- IPv6
authority = "[" .. authority .. "]"
end
local default_port = scheme_to_port[scheme]
if default_port == port then
port = nil
end
if port then
authority = string.format("%s:%d", authority, port)
end
return authority
end
-- HTTP prefered date format
-- See RFC 7231 section 7.1.1.1
local function imf_date(time)
return os.date("!%a, %d %b %Y %H:%M:%S GMT", time)
end
-- This pattern checks if it's argument is a valid token, if so, it returns it as is.
-- Otherwise, it returns it as a quoted string (with any special characters escaped)
local maybe_quote do
local EOF = lpeg.P(-1)
local patt = http_patts.token * EOF
+ lpeg.Cs(lpeg.Cc'"' * ((lpeg.S"\\\"") / "\\%0" + http_patts.qdtext)^0 * lpeg.Cc'"') * EOF
maybe_quote = function (s)
return patt:match(s)
end
end
-- A pcall relative that can be yielded over in PUC 5.1
local yieldable_pcall
-- See if pcall can be yielded over
if coroutine.wrap(function() return pcall(coroutine.yield, true) end)() then
yieldable_pcall = pcall
else
local function handle_resume(co, ok, ...)
if not ok then
return false, ...
elseif coroutine.status(co) == "dead" then
return true, ...
end
return handle_resume(co, coroutine.resume(co, coroutine.yield(...)))
end
yieldable_pcall = function(func, ...)
if type(func) ~= "function" or debug.getinfo(func, "S").what == "C" then
local C_func = func
-- Can't give C functions to coroutine.create
func = function(...) return C_func(...) end
end
local co = coroutine.create(func)
return handle_resume(co, coroutine.resume(co, ...))
end
end
return {
encodeURI = encodeURI;
encodeURIComponent = encodeURIComponent;
decodeURI = decodeURI;
decodeURIComponent = decodeURIComponent;
query_args = query_args;
dict_to_query = dict_to_query;
resolve_relative_path = resolve_relative_path;
scheme_to_port = scheme_to_port;
split_authority = split_authority;
to_authority = to_authority;
imf_date = imf_date;
maybe_quote = maybe_quote;
yieldable_pcall = yieldable_pcall;
}