5
5
-- @alias M
6
6
7
7
local M = {}
8
- M .version = " 1.0 .0"
8
+ M .version = " 1.1 .0"
9
9
10
10
--- url options
11
11
-- - `separator` is set to `&` by default but could be anything like `&` or `;`
12
12
-- - `cumulative_parameters` is false by default. If true, query parameters with the same name will be stored in a table.
13
+ -- - `legal_in_path` is a table of characters that will not be url encoded in path components
14
+ -- - `legal_in_query` is a table of characters that will not be url encoded in query values. Query parameters only support a small set of legal characters (-_.).
15
+ -- - `query_plus_is_space` is true by default, so a plus sign in a query value will be converted to %20 (space), not %2B (plus)
13
16
-- @todo Add option to limit the size of the argument table
14
17
-- @todo Add option to limit the depth of the argument table
15
18
-- @todo Add option to process dots in parameter names, ie. `param.filter=1`
16
19
M .options = {
17
20
separator = ' &' ,
18
- cumulative_parameters = false
21
+ cumulative_parameters = false ,
22
+ legal_in_path = {
23
+ [" :" ] = true , [" -" ] = true , [" _" ] = true , [" ." ] = true ,
24
+ [" !" ] = true , [" ~" ] = true , [" *" ] = true , [" '" ] = true ,
25
+ [" (" ] = true , [" )" ] = true , [" @" ] = true , [" &" ] = true ,
26
+ [" =" ] = true , [" $" ] = true , [" ," ] = true ,
27
+ [" ;" ] = true
28
+ },
29
+ legal_in_query = {
30
+ [" :" ] = true , [" -" ] = true , [" _" ] = true , [" ." ] = true ,
31
+ [" ," ] = true , [" !" ] = true , [" ~" ] = true , [" *" ] = true ,
32
+ [" '" ] = true , [" ;" ] = true , [" (" ] = true , [" )" ] = true ,
33
+ [" @" ] = true , [" $" ] = true ,
34
+ },
35
+ query_plus_is_space = true
19
36
}
20
37
21
38
--- list of known and common scheme ports
@@ -51,41 +68,42 @@ M.services = {
51
68
videotex = 516
52
69
}
53
70
54
- local legal = {
55
- [" -" ] = true , [" _" ] = true , [" ." ] = true , [" !" ] = true ,
56
- [" ~" ] = true , [" *" ] = true , [" '" ] = true , [" (" ] = true ,
57
- [" )" ] = true , [" :" ] = true , [" @" ] = true , [" &" ] = true ,
58
- [" =" ] = true , [" +" ] = true , [" $" ] = true , [" ," ] = true ,
59
- [" ;" ] = true -- can be used for parameters in path
60
- }
61
-
62
71
local function decode (str )
63
- local str = str :gsub (' +' , ' ' )
64
72
return (str :gsub (" %%(%x%x)" , function (c )
65
- return string.char (tonumber (c , 16 ))
73
+ return string.char (tonumber (c , 16 ))
66
74
end ))
67
75
end
68
76
69
- local function encode (str )
70
- return (str :gsub (" ([^A-Za-z0-9%_%.%-%~])" , function (v )
71
- return string.upper (string.format (" %%%02x" , string.byte (v )))
77
+ local function encode (str , legal )
78
+ return (str :gsub (" ([^%w])" , function (v )
79
+ if legal [v ] then
80
+ return v
81
+ end
82
+ return string.upper (string.format (" %%%02x" , string.byte (v )))
72
83
end ))
73
84
end
74
85
75
- -- for query values, prefer + instead of %20 for spaces
76
- local function encodeValue (str )
77
- local str = encode (str )
78
- return str :gsub (' %%20' , ' +' )
86
+ -- for query values, + can mean space if configured as such
87
+ local function decodeValue (str )
88
+ if M .options .query_plus_is_space then
89
+ str = str :gsub (' +' , ' ' )
90
+ end
91
+ return decode (str )
79
92
end
80
93
81
- local function encodeSegment (s )
82
- local legalEncode = function (c )
83
- if legal [c ] then
84
- return c
85
- end
86
- return encode (c )
94
+ local function concat (a , b )
95
+ if type (a ) == ' table' then
96
+ return a :build () .. b
97
+ else
98
+ return a .. b :build ()
87
99
end
88
- return s :gsub (' ([^a-zA-Z0-9])' , legalEncode )
100
+ end
101
+
102
+ function M :addSegment (path )
103
+ if type (path ) == ' string' then
104
+ self .path = self .path .. ' /' .. encode (path :gsub (" ^/+" , " " ), M .options .legal_in_path )
105
+ end
106
+ return self
89
107
end
90
108
91
109
--- builds the url
@@ -154,7 +172,7 @@ function M.buildQuery(tab, sep, key)
154
172
end )
155
173
for _ ,name in ipairs (keys ) do
156
174
local value = tab [name ]
157
- name = encode (tostring (name ))
175
+ name = encode (tostring (name ), {[ " - " ] = true , [ " _ " ] = true , [ " . " ] = true } )
158
176
if key then
159
177
if M .options .cumulative_parameters and string.find (name , ' ^%d+$' ) then
160
178
name = tostring (key )
@@ -165,7 +183,7 @@ function M.buildQuery(tab, sep, key)
165
183
if type (value ) == ' table' then
166
184
query [# query + 1 ] = M .buildQuery (value , sep , name )
167
185
else
168
- local value = encodeValue ( decode ( tostring (value )) )
186
+ local value = encode ( tostring (value ), M . options . legal_in_query )
169
187
if value ~= " " then
170
188
query [# query + 1 ] = string.format (' %s=%s' , name , value )
171
189
else
@@ -190,14 +208,14 @@ function M.parseQuery(str, sep)
190
208
191
209
local values = {}
192
210
for key ,val in str :gmatch (string.format (' ([^%q=]+)(=*[^%q=]*)' , sep , sep )) do
193
- local key = decode (key )
211
+ local key = decodeValue (key )
194
212
local keys = {}
195
213
key = key :gsub (' %[([^%]]*)%]' , function (v )
196
214
-- extract keys between balanced brackets
197
215
if string.find (v , " ^-?%d+$" ) then
198
216
v = tonumber (v )
199
217
else
200
- v = decode (v )
218
+ v = decodeValue (v )
201
219
end
202
220
table.insert (keys , v )
203
221
return " ="
@@ -212,11 +230,11 @@ function M.parseQuery(str, sep)
212
230
if # keys > 0 and type (values [key ]) ~= ' table' then
213
231
values [key ] = {}
214
232
elseif # keys == 0 and type (values [key ]) == ' table' then
215
- values [key ] = decode (val )
233
+ values [key ] = decodeValue (val )
216
234
elseif M .options .cumulative_parameters
217
235
and type (values [key ]) == ' string' then
218
236
values [key ] = { values [key ] }
219
- table.insert (values [key ], decode (val ))
237
+ table.insert (values [key ], decodeValue (val ))
220
238
end
221
239
222
240
local t = values [key ]
@@ -231,10 +249,11 @@ function M.parseQuery(str, sep)
231
249
t [k ] = {}
232
250
end
233
251
if i == # keys then
234
- t [k ] = decode ( val )
252
+ t [k ] = val
235
253
end
236
254
t = t [k ]
237
255
end
256
+
238
257
end
239
258
setmetatable (values , { __tostring = M .buildQuery })
240
259
return values
@@ -363,12 +382,14 @@ function M.parse(url)
363
382
return ' '
364
383
end )
365
384
366
- comp .path = url :gsub (" ([^/]+)" , function (s ) return encodeSegment (decode (s )) end )
385
+ comp .path = url :gsub (" ([^/]+)" , function (s ) return encode (decode (s ), M . options . legal_in_path ) end )
367
386
368
387
setmetatable (comp , {
369
388
__index = M ,
370
- __tostring = M .build }
371
- )
389
+ __tostring = M .build ,
390
+ __concat = concat ,
391
+ __div = M .addSegment
392
+ })
372
393
return comp
373
394
end
374
395
0 commit comments