root/HTTP/URL.lua

Revision 1182 (checked in by rsz, 7 months ago)

cleanup

Line 
1 --------------------------------------------------------------------------------
2 -- Title:               URL.lua
3 -- Description:         Like a square peg in a round hole
4 -- Author:              Raphaël Szwarc http://alt.textdrive.com/lua/
5 -- Creation Date:       January 30, 2007
6 -- Legal:               Copyright (C) 2007 Raphaël Szwarc
7 --                      Under the terms of the MIT License
8 --                      http://www.opensource.org/licenses/mit-license.html
9 --------------------------------------------------------------------------------
10
11 -- Based on Diego Nehab's URL:
12 -- http://www.cs.princeton.edu/~diego/professional/luasocket/url.html
13
14 -- import dependencies
15 local string = require( 'string' )
16 local table = require( 'table' )
17
18 local getmetatable = getmetatable
19 local ipairs = ipairs
20 local module = module
21 local next = next
22 local pairs = pairs
23 local require = require
24 local setmetatable = setmetatable
25 local tonumber = tonumber
26 local tostring = tostring
27 local type = type
28
29 --------------------------------------------------------------------------------
30 -- Utilities
31 --------------------------------------------------------------------------------
32
33 local function Decode( aValue )
34     local aDecoder = function( aValue )
35         return string.char( tonumber( aValue, 16 ) )
36     end
37
38     return ( aValue:gsub( '%%(%x%x)', aDecoder ) )
39 end
40
41 local function Encode( aValue )
42     local anEncoder = function( aValue )
43         return string.format( '%%%02x', string.byte( aValue ) )
44     end
45    
46     return ( aValue:gsub( '([^A-Za-z0-9_])', anEncoder ) )
47 end
48
49 --------------------------------------------------------------------------------
50 -- URLPath
51 --------------------------------------------------------------------------------
52
53 module( 'URLPath' )
54 _VERSION = '1.0'
55
56 local self = setmetatable( _M, {} )
57 local meta = getmetatable( self )
58
59 local function ReadPath( aValue )
60     local aPath = {}
61     local aReader = function( aValue )
62         aPath[ #aPath + 1 ] = Decode( aValue )
63     end
64
65     aValue:gsub( '([^/]+)', aReader )
66    
67     aPath.absolute = aValue:sub( 1, 1 ) == '/'
68     aPath.directory = aValue:sub( -1, -1 ) == '/'
69
70     return aPath
71 end
72
73 local PathValue = { [ '-' ] = true, [ '_' ] = true, [ '.' ] = true, [ '!' ] = true, [ '~' ] = true, [ '*' ] = true , [ '\'' ] = true, ['(' ] = true, [ ')' ] = true, [ ':' ] = true, [ '@' ] = true, [ '&' ] = true, [ '=' ] = true, [ '+' ] = true, [ '$' ] = true, [ ',' ] = true }
74
75 local function EncodePath( aValue )
76     local anEncoder = function ( aValue )
77         if PathValue[ aValue ] then
78           return aValue
79         end
80        
81         return Encode( aValue )
82     end
83
84     return ( aValue:gsub( '([^A-Za-z0-9_])', anEncoder ) )
85 end
86
87 local function WritePath( aValue )
88     local aBuffer = {}
89    
90     if aValue.absolute then
91         aBuffer[ #aBuffer + 1 ] = ''
92     end
93
94     for anIndex, aValue in ipairs( aValue ) do
95         aBuffer[ #aBuffer + 1 ] = EncodePath( aValue )
96     end
97    
98     if aValue.directory then
99         aBuffer[ #aBuffer + 1 ] = ''
100     end
101    
102     return table.concat( aBuffer, '/' )
103 end
104
105 local function NewPath( aValue )
106     local aPath = ReadPath( tostring( aValue or '' ) )
107    
108     setmetatable( aPath, self )
109    
110     return aPath
111 end
112
113 local function AddPath( aBase, aRelative )
114     local aPath = aRelative
115    
116     if not aPath.absolute then
117         local aNormalizer = function( aValue )
118             if aValue == './' then
119                 return ''
120             end
121            
122             return aValue
123         end
124         local aNormalizedPath = nil
125
126         aPath = tostring( aBase ):gsub( '[^/]*$', '' )
127         aPath = aPath .. aRelative 
128         aPath = aPath:gsub( '([^/]*%./)', aNormalizer )
129         aPath = aPath:gsub( '/%.$', '/' )
130
131         aNormalizer = function( aValue )
132             if aValue == '../../' then
133                 return aValue
134             end
135            
136             return ''
137         end
138        
139         while aNormalizedPath ~= aPath do
140             aNormalizedPath = aPath
141             aPath = aNormalizedPath:gsub( '([^/]*/%.%./)', aNormalizer )
142         end
143
144         aNormalizer = function( aValue )
145             if aValue == '../..' then
146                 return aValue
147             end
148            
149             return ''
150         end
151        
152         aPath = aPath:gsub( '([^/]*/%.%.)$', aNormalizer )
153         aPath = NewPath( aPath )
154     end
155    
156     return aPath
157 end
158
159 function meta:__call( aValue )
160     return NewPath( aValue )
161 end
162
163 function self:__call( aValue )
164     local aPath = NewPath( self )
165    
166     if aValue then
167         aPath[ #aPath + 1 ] = tostring( aValue )
168     else
169         aPath[ #aPath ] = nil
170     end
171
172     return aPath
173 end
174
175 function self:__add( aValue )
176     return AddPath( self(), NewPath( aValue ) )
177 end
178
179 function self:__concat( aValue )
180     return tostring( self ) .. tostring( aValue )
181 end
182
183 function self:__eq( aValue )
184     return tostring( self ) == tostring( aValue )
185 end
186
187 function self:__lt( aValue )
188     return tostring( self ) < tostring( aValue )
189 end
190
191 function self:__tostring()
192     return WritePath( self )
193 end
194
195 --------------------------------------------------------------------------------
196 -- URLParameter
197 --------------------------------------------------------------------------------
198
199 module( 'URLParameter' )
200 _VERSION = '1.0'
201
202 local self = setmetatable( _M, {} )
203 local meta = getmetatable( self )
204
205 local function ReadParameter( aValue )
206     local aParameter = {}
207
208     for aKey, aValue in aValue:gmatch( '([^&=]+)=([^&=]+)' ) do
209         aKey = Decode( aKey:gsub( '+', ' ' ) )
210         aValue = Decode( aValue:gsub( '+', ' ' ) )
211        
212         aParameter[ aKey:lower() ] = aValue
213     end
214    
215     return aParameter
216 end
217
218 local function WriteParameter( aValue )
219     local aBuffer = {}
220    
221     if next( aValue ) then
222         for aKey, aValue in pairs( aValue ) do
223             aBuffer[ #aBuffer + 1 ] = Encode( aKey:lower() ):gsub( '%%20', '+' )
224             aBuffer[ #aBuffer + 1 ] = '='
225             aBuffer[ #aBuffer + 1 ] = Encode( aValue ):gsub( '%%20', '+' )
226             aBuffer[ #aBuffer + 1 ] = '&'
227         end
228        
229         table.remove( aBuffer )
230     end
231
232     return table.concat( aBuffer )
233 end
234
235 local function NewParameter( aValue )
236     local aParameter = ReadParameter( tostring( aValue or '' ) )
237    
238     setmetatable( aParameter, self )
239    
240     return aParameter
241 end
242
243 function meta:__call( aValue )
244     return NewParameter( aValue )
245 end
246
247 function self:__concat( aValue )
248     return tostring( self ) .. tostring( aValue )
249 end
250
251 function self:__eq( aValue )
252     return tostring( self ) == tostring( aValue )
253 end
254
255 function self:__lt( aValue )
256     return tostring( self ) < tostring( aValue )
257 end
258
259 function self:__tostring()
260     return WriteParameter( self )
261 end
262
263 --------------------------------------------------------------------------------
264 -- URL
265 --------------------------------------------------------------------------------
266
267 module( 'URL' )
268 _VERSION = '1.0'
269
270 local self = setmetatable( _M, {} )
271 local meta = getmetatable( self )
272
273 local function ReadURL( aURL )
274     local URLParameter = require( 'URLParameter' )
275     local URLPath = require( 'URLPath' )
276     local someComponents = {}
277    
278     -- fragment
279     aURL = aURL:gsub( '#(.*)$', function( aValue ) someComponents.fragment = aValue return '' end )
280    
281     -- scheme
282     aURL = aURL:gsub( '^([%w][%w%+%-%.]*)%:', function( aValue ) someComponents.scheme = aValue:lower() return '' end )
283    
284     -- authority
285     aURL = aURL:gsub( '^//([^/]*)', function( aValue ) someComponents.authority = aValue return '' end )
286    
287     -- query
288     aURL = aURL:gsub( '%?(.*)', function( aValue ) someComponents.query = aValue return '' end )
289     someComponents.query = URLParameter( someComponents.query )
290    
291     -- parameter
292     aURL = aURL:gsub( '%;(.*)', function( aValue ) someComponents.parameter = aValue return '' end )
293     someComponents.parameter = URLParameter( someComponents.parameter )
294    
295     -- path
296     someComponents.path = URLPath( aURL )
297    
298     if someComponents.authority then
299         local anAuthority = someComponents.authority
300    
301         -- user info
302         anAuthority = anAuthority:gsub( '^([^@]*)@', function( aValue ) someComponents.userInfo = aValue return '' end )
303    
304         -- port
305         anAuthority = anAuthority:gsub( ':([^:]*)$', function( aValue ) someComponents.port = tonumber( aValue ) return '' end )
306    
307         -- host
308         if anAuthority ~= '' then
309             someComponents.host = anAuthority:lower()
310         end
311    
312         if someComponents.userInfo then
313             local anUserInfo = someComponents.userInfo
314    
315             -- password
316             anUserInfo = anUserInfo:gsub( ':([^:]*)$', function( aValue ) someComponents.password = aValue return '' end )
317    
318             -- user
319             someComponents.user = anUserInfo
320         end
321     end
322    
323     return someComponents
324 end
325
326 local function WriteURL( someComponents )
327     local aURL = ''
328     local aPath = someComponents.path
329     local aParameter = someComponents.parameter
330     local aQuery = someComponents.query
331     local anAuthority = someComponents.authority
332     local anHost = someComponents.host
333     local aScheme = someComponents.scheme
334     local aFragment = someComponents.fragment
335    
336     if aPath then
337         aURL = aURL .. aPath
338     end
339    
340     if aParameter  and next( aParameter ) then
341         aURL = aURL .. ';' .. aParameter
342     end
343    
344     if aQuery and next( aQuery ) then
345         aURL = aURL .. '?' .. aQuery
346     end
347    
348     if anHost then
349         local aPort = someComponents.port
350         local anUserInfo = someComponents.userInfo
351         local anUser = someComponents.user
352    
353         anAuthority = anHost
354        
355         if aPort then
356             anAuthority = anAuthority .. ':' .. aPort
357         end
358        
359         if anUser then
360             local aPassword = someComponents.password
361            
362             anUserInfo = anUser
363            
364             if aPassword then
365                 anUserInfo = anUserInfo .. ':' .. aPassword
366             end
367         end
368    
369         if anUserInfo then
370             anAuthority = anUserInfo .. '@' .. anAuthority
371         end
372        
373         if anAuthority then
374             aURL = '//' .. anAuthority .. aURL
375         end
376     end
377    
378     if aScheme then
379         aURL = aScheme .. ':' .. aURL
380     end
381    
382     if aFragment then
383         aURL = aURL .. '#' .. aFragment
384     end
385    
386     return aURL
387 end
388
389 local function NewURL( aValue )
390     local aURL = ReadURL( tostring( aValue or '' ) )
391    
392     setmetatable( aURL, self )
393    
394     return aURL
395 end
396
397 local function AddURL( aBase, aRelative )
398     if not aRelative.scheme then
399         aRelative.scheme = aBase.scheme
400        
401         if not aRelative.authority then
402             aRelative.authority = aBase.authority
403             aRelative.userInfo = aBase.userInfo
404             aRelative.user = aBase.user
405             aRelative.password = aBase.password
406             aRelative.host = aBase.host
407             aRelative.port = aBase.port
408            
409             if tostring( aRelative.path ):len() == 0 then
410                 aRelative.path = aBase.path
411                
412                 if tostring( aRelative.parameter ):len() == 0 then
413                     aRelative.parameter = aBase.parameter
414                    
415                     if tostring( aRelative.query ):len() == 0 then
416                         aRelative.query = aBase.query
417                     end
418                 end
419             else   
420                 aRelative.path = aBase.path + aRelative.path
421             end
422         end
423     end
424    
425     return aRelative
426 end
427
428 function meta:__call( aValue )
429     return NewURL( aValue )
430 end
431
432 function self:__add( aValue )
433     return AddURL( self, NewURL( aValue ) )
434 end
435
436 function self:__concat( aValue )
437     return tostring( self ) .. tostring( aValue )
438 end
439
440 function self:__eq( aValue )
441     return tostring( self ) == tostring( aValue )
442 end
443
444 function self:__lt( aValue )
445     return tostring( self ) < tostring( aValue )
446 end
447
448 function self:__tostring()
449     return WriteURL( self )
450 end
451
Note: See TracBrowser for help on using the browser.