root/HTTP/MIME.lua

Revision 1276 (checked in by rsz, 6 months ago)

cleanup

Line 
1 --------------------------------------------------------------------------------
2 -- Title:               MIME.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 -- import dependencies
12 local table = require( 'table' )
13
14 local getmetatable = getmetatable
15 local ipairs = ipairs
16 local module = module
17 local pcall = pcall
18 local require = require
19 local setmetatable = setmetatable
20 local tostring = tostring
21 local type = type
22 local unpack = unpack
23
24 --------------------------------------------------------------------------------
25 -- MIMEType
26 --------------------------------------------------------------------------------
27
28 module( 'MIMEType' )
29 _VERSION = '1.0'
30
31 a = 'application/x-archive'
32 ag = 'application/x-applixgraphics'
33 aiff = 'audio/x-aiff'
34 arj = 'application/x-arj'
35 arts = 'application/x-artsbuilder'
36 as = 'application/x-applixspread'
37 au = 'audio/basic'
38 avi = 'video/x-msvideo'
39 aw = 'application/x-applixword'
40 bak = 'application/x-trash'
41 bib = 'text/x-bibtex'
42 bmp = 'image/x-bmp'
43 bz = 'application/x-bzip'
44 bz2 = 'application/x-bzip2'
45 c = 'text/x-csrc'
46 cc = 'text/x-c++src'
47 cgm = 'image/cgm'
48 class = 'application/x-java'
49 cls = 'text/x-tex'
50 cpio = 'application/x-cpio'
51 cpp = 'text/x-c++src'
52 csh = 'application/x-shellscript'
53 css = 'text/css'
54 cssl = 'text/css'
55 cxx = 'text/x-c++src'
56 deb = 'application/x-debian-package'
57 desktop = 'application/x-desktop'
58 diff = 'text/x-diff'
59 dvi = 'application/x-dvi'
60 eps = 'image/x-eps'
61 epsf = 'image/x-eps'
62 epsi = 'image/x-eps'
63 exe = 'application/x-executable'
64 flc = 'video/x-flic'
65 fli = 'video/x-flic'
66 g3 = 'image/fax-g3'
67 gif = 'image/gif'
68 gsf = 'application/x-font'
69 gz = 'application/x-gzip'
70 h = 'text/x-chdr'
71 hh = 'text/x-c++hdr'
72 htm = 'text/html'
73 html = 'text/html'
74 ico = 'image/x-ico'
75 ics = 'text/calendar'
76 it = 'audio/x-mod'
77 jar = 'application/x-jar'
78 java = 'text/x-java'
79 jng = 'image/x-jng'
80 jpg = 'image/jpeg'
81 kar = 'audio/x-karaoke'
82 kdelnk = 'application/x-desktop'
83 ksysv = 'application/x-ksysv'
84 ksysv_log = 'text/x-ksysv-log'
85 ktheme = 'application/x-ktheme'
86 lha = 'application/x-lha'
87 log = 'text/x-log'
88 ltx = 'text/x-tex'
89 lua = 'text/plain'
90 lyx = 'text/x-lyx'
91 lzh = 'application/x-lha'
92 lzo = 'application/x-lzop'
93 m15 = 'audio/x-mod'
94 m3u = 'audio/x-mpegurl'
95 man = 'application/x-troff-man'
96 mid = 'audio/x-midi'
97 mng = 'video/x-mng'
98 moc = 'text/x-moc'
99 mod = 'audio/x-mod'
100 moov = 'video/quicktime'
101 mov = 'video/quicktime'
102 mp3 = 'audio/x-mp3'
103 mpeg = 'video/mpeg'
104 mpg = 'video/mpeg'
105 mtm = 'audio/x-mod'
106 o = 'application/x-object'
107 ogg = 'application/x-ogg'
108 old = 'application/x-trash'
109 p = 'text/x-pascal'
110 pas = 'text/x-pascal'
111 patch = 'text/x-diff'
112 pcd = 'image/x-photo-cd'
113 pdf = 'application/pdf'
114 perl = 'application/x-perl'
115 pfa = 'application/x-font'
116 pfb = 'application/x-font'
117 php = 'text/x-php'
118 php3 = 'text/x-php'
119 pl = 'text/x-perl'
120 pls = 'audio/x-scpls'
121 pm = 'text/x-perl'
122 png = 'image/png'
123 po = 'application/x-gettext'
124 pot = 'application/x-gettext'
125 ppt = 'application/mspowerpoint'
126 ppz = 'application/mspowerpoint'
127 ps = 'application/postscript'
128 py = 'application/x-python'
129 pyc = 'application/x-python-bytecode'
130 qt = 'video/quicktime'
131 qtvr = 'video/quicktime'
132 ra = 'audio/x-pn-realaudio'
133 ram = 'audio/x-pn-realaudio'
134 rar = 'application/x-rar'
135 rdf = 'text/rdf'
136 rm = 'audio/x-pn-realaudio'
137 roff = 'application/x-troff'
138 rpm = 'application/x-rpm'
139 rss = 'text/rss'
140 rtf = 'text/rtf'
141 s3m = 'audio/x-mod'
142 sgml = 'text/sgml'
143 sgrd = 'application/x-ksysguard'
144 sh = 'application/x-shellscript'
145 shell = 'application/x-konsole'
146 shtml = 'text/x-ssi-html'
147 sik = 'application/x-trash'
148 smi = 'audio/x-pn-realaudio'
149 smil = 'application/smil'
150 snd = 'audio/basic'
151 stm = 'audio/x-mod'
152 sty = 'text/x-tex'
153 swf = 'application/x-shockwave-flash'
154 tar = 'application/x-tar'
155 tcl = 'text/x-tcl'
156 tex = 'text/x-tex'
157 tgz = 'application/x-tgz'
158 tif = 'image/tiff'
159 tiff = 'image/tiff'
160 tk = 'text/x-tcl'
161 tr = 'application/x-troff'
162 ts = 'application/x-linguist'
163 ttf = 'application/x-truetype-font'
164 txt = 'text/plain'
165 tzo = 'application/x-tzo'
166 ui = 'application/x-designer'
167 ult = 'audio/x-mod'
168 uni = 'audio/x-mod'
169 vcs = 'text/x-vcalendar'
170 vct = 'text/x-vcard'
171 war = 'application/x-webarchive'
172 wav = 'audio/x-wav'
173 wpd = 'application/wordperfect'
174 xbm = 'image/x-xbm'
175 xcf = 'image/x-xcf-gimp'
176 xlc = 'application/msexcel'
177 xll = 'application/msexcel'
178 xls = 'application/msexcel'
179 xlw = 'application/msexcel'
180 xm = 'audio/x-mod'
181 xml = 'text/xml'
182 xpm = 'image/x-xpm'
183 z = 'application/x-compress'
184 zip = 'application/x-zip'
185 zoo = 'application/x-zoo'
186
187 --------------------------------------------------------------------------------
188 -- MIMEMultipartFormData
189 --------------------------------------------------------------------------------
190
191 module( 'MIMEMultipartFormData' )
192 _VERSION = '1.0'
193
194 local self = setmetatable( _M, {} )
195 local meta = getmetatable( self )
196
197 local function ReadFormData( aMultipart )
198     local aFormData = {}
199    
200     aFormData.boundary = aMultipart.boundary
201    
202     for anIndex, aPart in ipairs( aMultipart ) do
203         local aHeader = aPart.header[ 'content-disposition' ] or {}
204         local aDisposition = ( aHeader.value or '' ):lower()
205        
206         if aDisposition == 'form-data' then
207             local aParameter = aHeader.parameter or {}
208             local aKey = aParameter[ 'name' ]
209            
210             if aKey then
211                 local aKey = aKey:lower()
212                 local aValue = aPart.content
213                 local aFileName = aParameter[ 'filename' ]
214                 local aForm = { key = aKey, value = aValue, filename = aFileName }
215            
216                 aFormData[ #aFormData + 1 ] = aForm
217             end
218         end
219     end
220    
221     return aFormData
222 end
223
224 local function NewFormData( aValue, aHeader )
225     local MIMEMultipart = require( 'MIMEMultipart' )
226     local aFormData = nil
227    
228     if type( aValue ) == 'table' then
229         aFormData = ReadFormData( MIMEMultipart( WriteFormData( aValue ), aHeader ) )
230     else
231         aFormData = ReadFormData( MIMEMultipart( aValue, aHeader ) )
232     end
233    
234     setmetatable( aFormData, self )
235    
236     return aFormData
237 end
238
239 function meta:__call( aValue, aHeader )
240     return NewFormData( aValue, aHeader )
241 end
242
243 function self:__index( aKey )
244     if aKey then
245         local someForms = {}
246        
247         aKey = aKey:lower()
248    
249         for anIndex, aForm in ipairs( self ) do
250             if aKey == aForm.key then
251                 someForms[ #someForms + 1 ] = aForm
252             end
253         end
254        
255         return unpack( someForms )
256     end
257    
258     return nil
259 end
260
261 function self:__concat( aValue )
262     return tostring( self ) .. tostring( aValue )
263 end
264
265 function self:__tostring()
266     return 'WriteFormData' --WriteFormData( self )
267 end
268
269 --------------------------------------------------------------------------------
270 -- MIMEMultipart
271 --------------------------------------------------------------------------------
272
273 module( 'MIMEMultipart' )
274 _VERSION = '1.0'
275
276 local self = setmetatable( _M, {} )
277 local meta = getmetatable( self )
278
279 local function ReadBoundary( aHeader )
280     local aType = aHeader[ 'content-type' ] or {}
281     local aParameter = aType.parameter or {}
282     local aBoundary = aParameter[ 'boundary' ]
283    
284     return aBoundary
285 end
286
287 local function ReadMultipart( aValue, aBoundary )
288     local MIME = require( 'MIME' )
289     local aMultipart = { boundary = aBoundary }
290     local aBoundary = '--' .. aBoundary
291     local aLength = aBoundary:len()
292     local aStart = 1
293     local anEnd = nil
294    
295     while true do
296         aStart = aValue:find( aBoundary, aStart, true )
297        
298         if aStart then
299             aStart = aStart + aLength
300             anEnd = aValue:find( aBoundary, aStart, true )
301            
302             if anEnd then
303                 aStart = aValue:find( '\r\n', aStart, true ) + 2
304                
305                 aMultipart[ #aMultipart + 1 ] = MIME( aValue:sub( aStart, anEnd - 3 ) )
306             else
307                 break
308             end
309         else
310             break
311         end
312     end
313    
314     return aMultipart
315 end
316
317 local function NewMultipart( aValue, aHeader )
318     local aMultipart = nil
319    
320     if type( aValue ) == 'table' then
321         aMultipart = ReadMultipart( WriteMultipart( aValue ), aValue.boundary )
322     else
323         aMultipart = ReadMultipart( tostring( aValue or '' ), ReadBoundary( aHeader ) )
324     end
325    
326     setmetatable( aMultipart, self )
327    
328     return aMultipart
329 end
330
331 function meta:__call( aValue, aHeader )
332     return NewMultipart( aValue, aHeader )
333 end
334
335 function self:__concat( aValue )
336     return tostring( self ) .. tostring( aValue )
337 end
338
339 function self:__tostring()
340     return 'WriteMultipart' --WriteMultipart( self )
341 end
342
343 --------------------------------------------------------------------------------
344 -- MIMEHeader
345 --------------------------------------------------------------------------------
346
347 module( 'MIMEHeader' )
348 _VERSION = '1.0'
349
350 local self = setmetatable( _M, {} )
351 local meta = getmetatable( self )
352
353 local function Trim( aString )
354     if aString ~= nil then
355         aString = aString:gsub( '^[%c%s]*', '' )
356         aString = aString:gsub( '[%s%c]*$', '' )
357     end
358    
359     return aString
360 end
361
362 local function ReadKey( aValue )
363     local anIndex = aValue:find( ':', 1, true ) or ( aValue:len() + 1 )
364     local aKey = aValue:sub( 1, anIndex - 1 ):lower()
365    
366     return Trim( aKey )
367 end
368
369 local function ReadValue( aValue )
370     local aValue = aValue .. ';'
371     local anIndex = aValue:find( ':', 1, true ) or 1
372     local anotherIndex = aValue:find( ';%s*([^%s=]+)%s*=(.-);' ) or aValue:len()
373     local aValue = aValue:sub( anIndex + 1, anotherIndex - 1 )
374    
375     return Trim( aValue )
376 end
377
378 local function ReadParameter( aValue )
379     local aValue = aValue .. ';'
380     local aParameter = {}
381    
382     for aKey, aValue in aValue:gmatch( '%s*([^%s=]+)%s*=(.-);' ) do
383             if aValue:sub( 1, 1 ) == '"' then
384                     aValue = aValue:sub( 2, aValue:len() - 1 )
385             end
386    
387             aParameter[ Trim( aKey ):lower() ] = Trim( aValue )
388     end
389    
390     return aParameter
391 end
392
393 local function ReadHeader( aValue )
394     local aHeaderKey = ReadKey( aValue )
395     local aHeaderValue = ReadValue( aValue )
396     local aHeaderParameter = ReadParameter( aValue )
397     local aHeader = { key = aHeaderKey, value = aHeaderValue, parameter = aHeaderParameter }
398
399     return aHeader
400 end
401
402 local function NewHeader( aValue )
403     local aHeader = nil
404    
405     if type( aValue ) == 'table' then
406         aHeader = ReadHeader( WriteHeader( aValue ) )
407     else
408         aHeader = ReadHeader( tostring( aValue or '' ) )
409     end
410    
411     setmetatable( aHeader, self )
412    
413     return aHeader
414 end
415
416 function meta:__call( aValue )
417     return NewHeader( aValue )
418 end
419
420 function self:__concat( aValue )
421     return tostring( self ) .. tostring( aValue )
422 end
423
424 function self:__eq( aValue )
425     return tostring( self ) == tostring( aValue )
426 end
427
428 function self:__lt( aValue )
429     return tostring( self ) < tostring( aValue )
430 end
431
432 function self:__tostring()
433     return 'WriteHeader' --WriteHeader( self )
434 end
435
436 --------------------------------------------------------------------------------
437 -- MIMEHeaders
438 --------------------------------------------------------------------------------
439
440 module( 'MIMEHeaders' )
441 _VERSION = '1.0'
442
443 local self = setmetatable( _M, {} )
444 local meta = getmetatable( self )
445
446 local function ReadHeaders( aValue )
447     local MIMEHeader = require( 'MIMEHeader' )
448     local aHeader = {}
449     local aBuffer = nil
450     local aStart = 1
451     local anEnd = nil
452    
453     while true do
454         anEnd = aValue:find( '\r\n', aStart, true )
455
456         if anEnd then
457             local aLine = aValue:sub( aStart, anEnd - 1 )
458            
459             anEnd = anEnd + 2
460             aStart = anEnd
461            
462             if aLine ~= '' then
463                 local aChar = aLine:sub( 1, 1 )
464    
465                 if aChar ~= ' ' and aChar ~= '\t' then 
466                     if aBuffer then
467                         aHeader[ #aHeader + 1 ] = MIMEHeader( table.concat( aBuffer, ' ' ) )
468                     end
469            
470                     aBuffer = {}
471                 end
472
473                 aBuffer[ #aBuffer + 1 ] = aLine
474             else
475                 break
476             end
477         else
478             break
479         end
480     end
481    
482     if aBuffer and #aBuffer > 0 then
483         aHeader[ #aHeader + 1 ] = MIMEHeader( table.concat( aBuffer, ' ' ) )
484     end
485    
486     return aHeader, ( anEnd or aStart )
487 end
488
489 local function NewHeaders( aValue )
490     local someHeaders = nil
491     local anEnd = nil
492    
493     if type( aValue ) == 'table' then
494         someHeaders, anEnd = ReadHeaders( WriteHeaders( aValue ) )
495     else
496         someHeaders, anEnd = ReadHeaders( tostring( aValue or '' ) )
497     end
498    
499     setmetatable( someHeaders, self )
500    
501     return someHeaders, anEnd
502 end
503
504 function meta:__call( aValue )
505     return NewHeaders( aValue )
506 end
507
508 function self:__index( aKey )
509     if aKey then
510         local someHeaders = {}
511        
512         aKey = aKey:lower()
513    
514         for anIndex, aHeader in ipairs( self ) do
515             if aKey == aHeader.key then
516                 someHeaders[ #someHeaders + 1 ] = aHeader
517             end
518         end
519        
520         return unpack( someHeaders )
521     end
522    
523     return nil
524 end
525
526 function self:__concat( aValue )
527     return tostring( self ) .. tostring( aValue )
528 end
529
530 function self:__eq( aValue )
531     return tostring( self ) == tostring( aValue )
532 end
533
534 function self:__tostring()
535     return 'WriteHeaders'
536 end
537
538 --------------------------------------------------------------------------------
539 -- MIME
540 --------------------------------------------------------------------------------
541
542 module( 'MIME' )
543 _VERSION = '1.0'
544
545 local self = setmetatable( _M, {} )
546 local meta = getmetatable( self )
547
548 local function Capitalize( aValue )
549     return ( aValue:lower():gsub( '(%l)([%w_\']*)', function( first, rest ) return first:upper() .. rest end ) )
550 end
551
552 local function ReadType( aHeader )
553     local aHeader = aHeader or {}
554     local aTypeHeader = aHeader[ 'content-type' ] or {}
555     local aType = ( aTypeHeader.value or 'text/plain' ):lower()
556    
557     return aType
558 end
559
560 local function ContentModule( aType )
561     local aName = 'MIME' .. Capitalize( aType ):gsub( '[^%w]', '' )
562     local ok, aModule = pcall( require, aName )
563    
564     if not ok then
565         local anIndex = aType:find( '/', 1, true )
566        
567         if anIndex then
568             aModule = ContentModule( aType:sub( 1, anIndex - 1 ) )
569         else
570             aModule = nil
571         end
572     end
573    
574     return aModule
575 end
576
577 local function ReadContent( aValue, aType, aHeader )
578     local aModule = ContentModule( aType )
579    
580     if aModule then
581         return aModule( aValue, aHeader )
582     end
583    
584     return aValue
585 end
586
587 local function ReadMIME( aValue )
588     local MIMEHeaders = require( 'MIMEHeaders' )
589     local aHeader, anEnd = MIMEHeaders( aValue )
590     local aType = ReadType( aHeader )
591     local aContent = ReadContent( aValue:sub( anEnd ), aType, aHeader )
592     local aMIME = { header = aHeader, type = aType, content = aContent }
593    
594     return aMIME
595 end
596
597 local function NewMIME( aValue )
598     local aMIME = nil
599    
600     if type( aValue ) == 'table' then
601         aMIME = ReadMIME( WriteMIME( aValue ) )
602     else
603         aMIME = ReadMIME( tostring( aValue or '' ) )
604     end
605    
606     setmetatable( aMIME, self )
607    
608     return aMIME
609 end
610
611 function meta:__call( aValue )
612     return NewMIME( aValue )
613 end
614
615 function self:__concat( aValue )
616     return tostring( self ) .. tostring( aValue )
617 end
618
619 function self:__eq( aValue )
620     return tostring( self ) == tostring( aValue )
621 end
622
623 function self:__tostring()
624     return 'WriteMIME' --WriteMIME( self )
625 end
Note: See TracBrowser for help on using the browser.