aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDiego Nehab <diego@tecgraf.puc-rio.br>2004-01-19 05:41:30 +0000
committerDiego Nehab <diego@tecgraf.puc-rio.br>2004-01-19 05:41:30 +0000
commit5b8d7dec541a618b4ca7f2205470a28cde2e3e25 (patch)
tree209ad0c80c9a938068401fc5b8fa51942972418f
parent6ac82d50eecdf9bf55f4234ed3a5449afd7a2992 (diff)
downloadluasocket-5b8d7dec541a618b4ca7f2205470a28cde2e3e25.tar.gz
luasocket-5b8d7dec541a618b4ca7f2205470a28cde2e3e25.tar.bz2
luasocket-5b8d7dec541a618b4ca7f2205470a28cde2e3e25.zip
Updated some of the callbacks in callback.lua.
Update get.lua to use the new callbacks. The old "code" module is now the "mime" module. Updated all modules that depended on it. Updated url.lua to use the new namespace scheme, and moved the escape and unescape functions that used to be in the code.lua module to it, since these are specific to urls. Updated the callback entries in the manual.
-rw-r--r--TODO7
-rw-r--r--doc/reference.css1
-rw-r--r--doc/reference.html6
-rw-r--r--doc/stream.html89
-rw-r--r--etc/eol.lua2
-rw-r--r--etc/get.lua83
-rw-r--r--src/http.lua25
-rw-r--r--src/inet.c2
-rw-r--r--src/luasocket.c8
-rw-r--r--src/mime.c614
-rw-r--r--src/mime.h17
-rw-r--r--src/mime.lua104
-rw-r--r--src/tcp.c2
-rw-r--r--src/url.lua179
-rw-r--r--test/httptest.lua16
-rw-r--r--test/mimetest.lua236
16 files changed, 1201 insertions, 190 deletions
diff --git a/TODO b/TODO
index ab88dbd..4b475ae 100644
--- a/TODO
+++ b/TODO
@@ -1,3 +1,10 @@
1
2comment the need of a content-length header in the post method...
3
4comment the callback.lua module and the new mime module.
5 escape and unescape are missing!
6
7add _tostring methods!
1add callback module to manual 8add callback module to manual
2change stay to redirect in http.lua and in manual 9change stay to redirect in http.lua and in manual
3add timeout to request table 10add timeout to request table
diff --git a/doc/reference.css b/doc/reference.css
index 4f17046..cd7de2c 100644
--- a/doc/reference.css
+++ b/doc/reference.css
@@ -16,7 +16,6 @@ blockquote { margin-left: 3em; }
16a[href] { color: #00007f; } 16a[href] { color: #00007f; }
17 17
18p.name { 18p.name {
19 font-size: large;
20 font-family: monospace; 19 font-family: monospace;
21 padding-top: 1em; 20 padding-top: 1em;
22} 21}
diff --git a/doc/reference.html b/doc/reference.html
index 08fd068..99b1ea7 100644
--- a/doc/reference.html
+++ b/doc/reference.html
@@ -92,7 +92,7 @@
92 92
93<!-- http & ftp ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> 93<!-- http & ftp ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
94 94
95<table summary="HTTP and FTP Index" class=index width=100% rules=cols> 95<table summary="HTTP and FTP Index" class=index width=100%>
96<colgroup> <col width="50%"> <col width="50%"> </colgroup> 96<colgroup> <col width="50%"> <col width="50%"> </colgroup>
97<tr> 97<tr>
98<td valign=top><ul> 98<td valign=top><ul>
@@ -135,7 +135,7 @@
135 135
136<!-- smtp & dns ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> 136<!-- smtp & dns ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
137 137
138<table summary="SMTP and DNS Index" class=index width=100% rules=cols> 138<table summary="SMTP and DNS Index" class=index width=100%>
139<colgroup> <col width="50%"> <col width="50%"> </colgroup> 139<colgroup> <col width="50%"> <col width="50%"> </colgroup>
140<tr> 140<tr>
141<td><ul> 141<td><ul>
@@ -158,7 +158,7 @@
158 158
159<!-- url & code ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> 159<!-- url & code ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
160 160
161<table summary="URL and Code Index" class=index width=100% rules=cols> 161<table summary="URL and Code Index" class=index width=100%>
162<colgroup> <col width="50%"> <col width="50%"> </colgroup> 162<colgroup> <col width="50%"> <col width="50%"> </colgroup>
163<tr> 163<tr>
164<td valign=top><ul> 164<td valign=top><ul>
diff --git a/doc/stream.html b/doc/stream.html
index 296ca2e..b88cbb5 100644
--- a/doc/stream.html
+++ b/doc/stream.html
@@ -36,21 +36,21 @@
36<h2 id=stream>Streaming with Callbacks</h2> 36<h2 id=stream>Streaming with Callbacks</h2>
37 37
38<p> 38<p>
39HTTP and FTP transfers sometimes involve large amounts of information. 39HTTP, FTP, and SMTP transfers sometimes involve large amounts of
40Sometimes an application needs to generate outgoing data in real time, 40information. Sometimes an application needs to generate outgoing data
41or needs to process incoming information as it is being received. To 41in real time, or needs to process incoming information as it is being
42address these problems, LuaSocket allows HTTP message bodies and FTP 42received. To address these problems, LuaSocket allows HTTP and SMTP message
43file contents to be received or sent through the callback mechanism 43bodies and FTP file contents to be received or sent through the
44outlined below. 44callback mechanism outlined below.
45</p> 45</p>
46 46
47<p> 47<p>
48Instead of returning the entire contents of a FTP file or HTTP message 48Instead of returning the entire contents of an entity
49body as strings to the Lua application, the library allows the user to 49as strings to the Lua application, the library allows the user to
50provide a <em>receive callback</em> that will be called with successive 50provide a <em>receive callback</em> that will be called with successive
51chunks of data, as the data becomes available. Conversely, the <em>send 51chunks of data, as the data becomes available. Conversely, the <em>send
52callbacks</em> should be used when data needed by LuaSocket 52callbacks</em> can be used when the application wants to incrementally
53is generated incrementally by the application. 53provide LuaSocket with the data to be sent.
54</p> 54</p>
55 55
56<!-- tohostname +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> 56<!-- tohostname +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
@@ -68,21 +68,26 @@ callback receives successive chunks of downloaded data.
68<p class=parameters> 68<p class=parameters>
69<tt>Chunk</tt> contains the current chunk of data. 69<tt>Chunk</tt> contains the current chunk of data.
70When the transmission is over, the function is called with an 70When the transmission is over, the function is called with an
71empty string (i.e.&nbsp;<tt>""</tt>) as the <tt>chunk</tt>. If an error occurs, the 71empty string (i.e.&nbsp;<tt>""</tt>) as the <tt>chunk</tt>.
72function receives <tt>nil</tt> as <tt>chunk</tt> and an error message as 72If an error occurs, the function receives <tt>nil</tt>
73<tt>err</tt>. 73as <tt>chunk</tt> and an error message in <tt>err</tt>.
74</p> 74</p>
75 75
76<p class=return> 76<p class=return>
77The callback can abort transmission by returning 77The callback can abort transmission by returning <tt>nil</tt> as its first
78<tt>nil</tt> as its first return value. In that case, it can also return 78return value, and an optional error message as the
79an error message. Any non-<tt>nil</tt> return value proceeds with the 79second return value. If the application wants to continue receiving
80transmission. 80data, the function should return non-<tt>nil</tt> as it's first return
81value. In this case, the function can optionally return a
82new callback function, to replace itself, as the second return value.
83</p>
84
85<p class=note>
86Note: The <tt>callback</tt> module provides several standard receive callbacks, including the following:
81</p> 87</p>
82 88
83<pre class=example> 89<pre class=example>
84-- The implementation of socket.callback.receive_concat 90function receive.concat(concat)
85function Public.receive_concat(concat)
86 concat = concat or socket.concat.create() 91 concat = concat or socket.concat.create()
87 local callback = function(chunk, err) 92 local callback = function(chunk, err)
88 -- if not finished, add chunk 93 -- if not finished, add chunk
@@ -95,6 +100,12 @@ function Public.receive_concat(concat)
95end 100end
96</pre> 101</pre>
97 102
103<p class=note>
104This function creates a new receive callback that concatenates all
105received chunks into a the same concat object, which can later be
106queried for its contents.
107</p>
108
98<!-- send_cb ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> 109<!-- send_cb ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
99 110
100<p class=name> 111<p class=name>
@@ -107,45 +118,27 @@ library needs more data to be sent.
107</p> 118</p>
108 119
109<p class=return> 120<p class=return>
110Each time the callback is called, it 121Each time the callback is called, it should return the next chunk of data. It
111should return the next part of the information the library is expecting, 122can optionally return, as it's second return value, a new callback to replace
112followed by the total number of bytes to be sent. 123itself. The callback can abort the process at any time by returning
113The callback can abort 124<tt>nil</tt> followed by an optional error message.
114the process at any time by returning <tt>nil</tt> followed by an
115optional error message.
116</p> 125</p>
117 126
118
119<p class=note> 127<p class=note>
120Note: The need for the second return value comes from the fact that, with 128Note: Below is the implementation of the <tt>callback.send.file</tt>
121the HTTP protocol for instance, the library needs to know in advance the 129function. Given an open file handle, it returns a send callback that will send the contents of that file, chunk by chunk.
122total number of bytes that will be sent.
123</p> 130</p>
124 131
125<pre class=example> 132<pre class=example>
126-- The implementation of socket.callback.send_file 133function send.file(file, io_err)
127function Public.send_file(file) 134 -- if successful, return the callback that reads from the file
128 local callback
129 -- if successfull, return the callback that reads from the file
130 if file then 135 if file then
131 -- get total size 136 return function()
132 local size = file:seek("end")
133 -- go back to start of file
134 file:seek("set")
135 callback = function()
136 -- send next block of data 137 -- send next block of data
137 local chunk = file:read(Public.BLOCKSIZE) 138 return (file:read(BLOCKSIZE)) or ""
138 if not chunk then file:close() end
139 return chunk, size
140 end 139 end
141 -- else, return a callback that just aborts the transfer 140 -- else, return a callback that just aborts the transfer
142 else 141 else return fail(io_err or "unable to open file") end
143 callback = function()
144 -- just abort
145 return nil, "unable to open file"
146 end
147 end
148 return callback, file
149end 142end
150</pre> 143</pre>
151 144
diff --git a/etc/eol.lua b/etc/eol.lua
index 234cc4d..fea5da9 100644
--- a/etc/eol.lua
+++ b/etc/eol.lua
@@ -1,7 +1,7 @@
1marker = {['-u'] = '\10', ['-d'] = '\13\10'} 1marker = {['-u'] = '\10', ['-d'] = '\13\10'}
2arg = arg or {'-u'} 2arg = arg or {'-u'}
3marker = marker[arg[1]] or marker['-u'] 3marker = marker[arg[1]] or marker['-u']
4local convert = socket.code.canonic(marker) 4local convert = socket.mime.canonic(marker)
5while 1 do 5while 1 do
6 local chunk = io.read(4096) 6 local chunk = io.read(4096)
7 io.write(convert(chunk)) 7 io.write(convert(chunk))
diff --git a/etc/get.lua b/etc/get.lua
index 2d804a0..9f29a51 100644
--- a/etc/get.lua
+++ b/etc/get.lua
@@ -42,8 +42,8 @@ function nicesize(b)
42end 42end
43 43
44-- returns a string with the current state of the download 44-- returns a string with the current state of the download
45function gauge(got, dt, size) 45function gauge(got, delta, size)
46 local rate = got / dt 46 local rate = got / delta
47 if size and size >= 1 then 47 if size and size >= 1 then
48 return string.format("%s received, %s/s throughput, " .. 48 return string.format("%s received, %s/s throughput, " ..
49 "%.0f%% done, %s remaining", 49 "%.0f%% done, %s remaining",
@@ -55,53 +55,56 @@ function gauge(got, dt, size)
55 return string.format("%s received, %s/s throughput, %s elapsed", 55 return string.format("%s received, %s/s throughput, %s elapsed",
56 nicesize(got), 56 nicesize(got),
57 nicesize(rate), 57 nicesize(rate),
58 nicetime(dt)) 58 nicetime(delta))
59 end 59 end
60end 60end
61 61
62-- creates a new instance of a receive_cb that saves to disk 62-- creates a new instance of a receive_cb that saves to disk
63-- kind of copied from luasocket's manual callback examples 63-- kind of copied from luasocket's manual callback examples
64function receive2disk(file, size) 64function stats(size)
65 local aux = { 65 local start = socket.time()
66 start = socket.time(), 66 local got = 0
67 got = 0, 67 return function(chunk)
68 file = io.open(file, "wb"), 68 -- elapsed time since start
69 size = size 69 local delta = socket.time() - start
70 } 70 if chunk then
71 local receive_cb = function(chunk, err) 71 -- total bytes received
72 local dt = socket.time() - aux.start -- elapsed time since start 72 got = got + string.len(chunk)
73 if not chunk or chunk == "" then 73 -- not enough time for estimate
74 io.write("\n") 74 if delta > 0.1 then
75 aux.file:close() 75 io.stderr:write("\r", gauge(got, delta, size))
76 return 76 io.stderr:flush()
77 end
78 return chunk
79 else
80 -- close up
81 io.stderr:write("\n")
82 return ""
77 end 83 end
78 aux.file:write(chunk)
79 aux.got = aux.got + string.len(chunk) -- total bytes received
80 if dt < 0.1 then return 1 end -- not enough time for estimate
81 io.write("\r", gauge(aux.got, dt, aux.size))
82 return 1
83 end 84 end
84 return receive_cb
85end 85end
86 86
87-- downloads a file using the ftp protocol 87-- downloads a file using the ftp protocol
88function getbyftp(url, file) 88function getbyftp(url, file)
89 local save = socket.callback.receive.file(file or io.stdout)
90 if file then
91 save = socket.callback.receive.chain(stats(gethttpsize(url)), save)
92 end
89 local err = socket.ftp.get_cb { 93 local err = socket.ftp.get_cb {
90 url = url, 94 url = url,
91 content_cb = receive2disk(file), 95 content_cb = save,
92 type = "i" 96 type = "i"
93 } 97 }
94 print()
95 if err then print(err) end 98 if err then print(err) end
96end 99end
97 100
98-- downloads a file using the http protocol 101-- downloads a file using the http protocol
99function getbyhttp(url, file, size) 102function getbyhttp(url, file)
100 local response = socket.http.request_cb( 103 local save = socket.callback.receive.file(file or io.stdout)
101 {url = url}, 104 if file then
102 {body_cb = receive2disk(file, size)} 105 save = socket.callback.receive.chain(stats(gethttpsize(url)), save)
103 ) 106 end
104 print() 107 local response = socket.http.request_cb({url = url}, {body_cb = save})
105 if response.code ~= 200 then print(response.status or response.error) end 108 if response.code ~= 200 then print(response.status or response.error) end
106end 109end
107 110
@@ -116,26 +119,22 @@ function gethttpsize(url)
116 end 119 end
117end 120end
118 121
119-- determines the scheme and the file name of a given url 122-- determines the scheme
120function getschemeandname(url, name) 123function getscheme(url)
121 -- this is an heuristic to solve a common invalid url poblem 124 -- this is an heuristic to solve a common invalid url poblem
122 if not string.find(url, "//") then url = "//" .. url end 125 if not string.find(url, "//") then url = "//" .. url end
123 local parsed = socket.url.parse(url, {scheme = "http"}) 126 local parsed = socket.url.parse(url, {scheme = "http"})
124 if name then return parsed.scheme, name end 127 return parsed.scheme
125 local segment = socket.url.parse_path(parsed.path)
126 name = segment[table.getn(segment)]
127 if segment.is_directory then name = nil end
128 return parsed.scheme, name
129end 128end
130 129
131-- gets a file either by http or ftp, saving as <name> 130-- gets a file either by http or ftp, saving as <name>
132function get(url, name) 131function get(url, name)
133 local scheme 132 local fout = name and io.open(name, "wb")
134 scheme, name = getschemeandname(url, name) 133 local scheme = getscheme(url)
135 if not name then print("unknown file name") 134 if scheme == "ftp" then getbyftp(url, fout)
136 elseif scheme == "ftp" then getbyftp(url, name) 135 elseif scheme == "http" then getbyhttp(url, fout)
137 elseif scheme == "http" then getbyhttp(url, name, gethttpsize(url))
138 else print("unknown scheme" .. scheme) end 136 else print("unknown scheme" .. scheme) end
137 if name then fout:close() end
139end 138end
140 139
141-- main program 140-- main program
diff --git a/src/http.lua b/src/http.lua
index fb13d99..72bde0a 100644
--- a/src/http.lua
+++ b/src/http.lua
@@ -421,7 +421,7 @@ end
421----------------------------------------------------------------------------- 421-----------------------------------------------------------------------------
422local function authorize(reqt, parsed, respt) 422local function authorize(reqt, parsed, respt)
423 reqt.headers["authorization"] = "Basic " .. 423 reqt.headers["authorization"] = "Basic " ..
424 (socket.code.b64(parsed.user .. ":" .. parsed.password)) 424 (socket.mime.b64(parsed.user .. ":" .. parsed.password))
425 local autht = { 425 local autht = {
426 nredirects = reqt.nredirects, 426 nredirects = reqt.nredirects,
427 method = reqt.method, 427 method = reqt.method,
@@ -429,8 +429,8 @@ local function authorize(reqt, parsed, respt)
429 body_cb = reqt.body_cb, 429 body_cb = reqt.body_cb,
430 headers = reqt.headers, 430 headers = reqt.headers,
431 timeout = reqt.timeout, 431 timeout = reqt.timeout,
432 host = reqt.host, 432 proxyhost = reqt.proxyhost,
433 port = reqt.port 433 proxyport = reqt.proxyport
434 } 434 }
435 return request_cb(autht, respt) 435 return request_cb(autht, respt)
436end 436end
@@ -471,8 +471,8 @@ local function redirect(reqt, respt)
471 body_cb = reqt.body_cb, 471 body_cb = reqt.body_cb,
472 headers = reqt.headers, 472 headers = reqt.headers,
473 timeout = reqt.timeout, 473 timeout = reqt.timeout,
474 host = reqt.host, 474 proxyhost = reqt.proxyhost,
475 port = reqt.port 475 proxyport = reqt.proxyport
476 } 476 }
477 respt = request_cb(redirt, respt) 477 respt = request_cb(redirt, respt)
478 -- we pass the location header as a clue we tried to redirect 478 -- we pass the location header as a clue we tried to redirect
@@ -482,8 +482,8 @@ end
482 482
483----------------------------------------------------------------------------- 483-----------------------------------------------------------------------------
484-- Computes the request URI from the parsed request URL 484-- Computes the request URI from the parsed request URL
485-- If host and port are given in the request table, we use he 485-- If we are using a proxy, we use the absoluteURI format.
486-- absoluteURI format. Otherwise, we use the abs_path format. 486-- Otherwise, we use the abs_path format.
487-- Input 487-- Input
488-- parsed: parsed URL 488-- parsed: parsed URL
489-- Returns 489-- Returns
@@ -491,7 +491,7 @@ end
491----------------------------------------------------------------------------- 491-----------------------------------------------------------------------------
492local function request_uri(reqt, parsed) 492local function request_uri(reqt, parsed)
493 local url 493 local url
494 if not reqt.host and not reqt.port then 494 if not reqt.proxyhost and not reqt.proxyport then
495 url = { 495 url = {
496 path = parsed.path, 496 path = parsed.path,
497 params = parsed.params, 497 params = parsed.params,
@@ -543,6 +543,7 @@ end
543-- error: error message, or nil if successfull 543-- error: error message, or nil if successfull
544----------------------------------------------------------------------------- 544-----------------------------------------------------------------------------
545function request_cb(reqt, respt) 545function request_cb(reqt, respt)
546 local sock, ret
546 local parsed = socket.url.parse(reqt.url, { 547 local parsed = socket.url.parse(reqt.url, {
547 host = "", 548 host = "",
548 port = PORT, 549 port = PORT,
@@ -561,14 +562,14 @@ function request_cb(reqt, respt)
561 -- fill default headers 562 -- fill default headers
562 reqt.headers = fill_headers(reqt.headers, parsed) 563 reqt.headers = fill_headers(reqt.headers, parsed)
563 -- try to connect to server 564 -- try to connect to server
564 local sock
565 sock, respt.error = socket.tcp() 565 sock, respt.error = socket.tcp()
566 if not sock then return respt end 566 if not sock then return respt end
567 -- set connection timeout so that we do not hang forever 567 -- set connection timeout so that we do not hang forever
568 sock:settimeout(reqt.timeout or TIMEOUT) 568 sock:settimeout(reqt.timeout or TIMEOUT)
569 local ret 569 ret, respt.error = sock:connect(
570 ret, respt.error = sock:connect(reqt.host or parsed.host, 570 reqt.proxyhost or PROXYHOST or parsed.host,
571 reqt.port or parsed.port) 571 reqt.proxyport or PROXYPORT or parsed.port
572 )
572 if not ret then 573 if not ret then
573 sock:close() 574 sock:close()
574 return respt 575 return respt
diff --git a/src/inet.c b/src/inet.c
index 282d616..6aea596 100644
--- a/src/inet.c
+++ b/src/inet.c
@@ -234,7 +234,7 @@ const char *inet_trybind(p_sock ps, const char *address, unsigned short port,
234 return sock_bindstrerror(); 234 return sock_bindstrerror();
235 } else { 235 } else {
236 sock_setnonblocking(ps); 236 sock_setnonblocking(ps);
237 if (backlog > 0) sock_listen(ps, backlog); 237 if (backlog >= 0) sock_listen(ps, backlog);
238 return NULL; 238 return NULL;
239 } 239 }
240} 240}
diff --git a/src/luasocket.c b/src/luasocket.c
index 8b30f4d..578d65c 100644
--- a/src/luasocket.c
+++ b/src/luasocket.c
@@ -33,7 +33,7 @@
33#include "tcp.h" 33#include "tcp.h"
34#include "udp.h" 34#include "udp.h"
35#include "select.h" 35#include "select.h"
36#include "code.h" 36#include "mime.h"
37 37
38/*=========================================================================*\ 38/*=========================================================================*\
39* Exported functions 39* Exported functions
@@ -52,13 +52,13 @@ LUASOCKET_API int luaopen_socket(lua_State *L)
52 tcp_open(L); 52 tcp_open(L);
53 udp_open(L); 53 udp_open(L);
54 select_open(L); 54 select_open(L);
55 code_open(L); 55 mime_open(L);
56#ifdef LUASOCKET_COMPILED 56#ifdef LUASOCKET_COMPILED
57#include "auxiliar.lch" 57#include "auxiliar.lch"
58#include "concat.lch" 58#include "concat.lch"
59#include "url.lch" 59#include "url.lch"
60#include "callback.lch" 60#include "callback.lch"
61#include "code.lch" 61#include "mime.lch"
62#include "smtp.lch" 62#include "smtp.lch"
63#include "ftp.lch" 63#include "ftp.lch"
64#include "http.lch" 64#include "http.lch"
@@ -67,7 +67,7 @@ LUASOCKET_API int luaopen_socket(lua_State *L)
67 lua_dofile(L, "concat.lua"); 67 lua_dofile(L, "concat.lua");
68 lua_dofile(L, "url.lua"); 68 lua_dofile(L, "url.lua");
69 lua_dofile(L, "callback.lua"); 69 lua_dofile(L, "callback.lua");
70 lua_dofile(L, "code.lua"); 70 lua_dofile(L, "mime.lua");
71 lua_dofile(L, "smtp.lua"); 71 lua_dofile(L, "smtp.lua");
72 lua_dofile(L, "ftp.lua"); 72 lua_dofile(L, "ftp.lua");
73 lua_dofile(L, "http.lua"); 73 lua_dofile(L, "http.lua");
diff --git a/src/mime.c b/src/mime.c
new file mode 100644
index 0000000..6807af5
--- /dev/null
+++ b/src/mime.c
@@ -0,0 +1,614 @@
1/*=========================================================================*\
2* Encoding support functions
3* LuaSocket toolkit
4*
5* RCS ID: $Id$
6\*=========================================================================*/
7#include <string.h>
8
9#include <lua.h>
10#include <lauxlib.h>
11
12#include "luasocket.h"
13#include "mime.h"
14
15/*=========================================================================*\
16* Don't want to trust escape character constants
17\*=========================================================================*/
18#define CR 0x0D
19#define LF 0x0A
20#define HT 0x09
21#define SP 0x20
22
23typedef unsigned char UC;
24static const UC CRLF[2] = {CR, LF};
25static const UC EQCRLF[3] = {'=', CR, LF};
26
27/*=========================================================================*\
28* Internal function prototypes.
29\*=========================================================================*/
30static int mime_global_fmt(lua_State *L);
31static int mime_global_b64(lua_State *L);
32static int mime_global_unb64(lua_State *L);
33static int mime_global_qp(lua_State *L);
34static int mime_global_unqp(lua_State *L);
35static int mime_global_qpfmt(lua_State *L);
36static int mime_global_eol(lua_State *L);
37
38static void b64fill(UC *b64unbase);
39static size_t b64encode(UC c, UC *input, size_t size, luaL_Buffer *buffer);
40static size_t b64pad(const UC *input, size_t size, luaL_Buffer *buffer);
41static size_t b64decode(UC c, UC *input, size_t size, luaL_Buffer *buffer);
42
43static void qpfill(UC *qpclass, UC *qpunbase);
44static void qpquote(UC c, luaL_Buffer *buffer);
45static size_t qpdecode(UC c, UC *input, size_t size, luaL_Buffer *buffer);
46static size_t qpencode(UC c, UC *input, size_t size,
47 const UC *marker, luaL_Buffer *buffer);
48
49/* code support functions */
50static luaL_reg func[] = {
51 { "eol", mime_global_eol },
52 { "qp", mime_global_qp },
53 { "unqp", mime_global_unqp },
54 { "qpfmt", mime_global_qpfmt },
55 { "b64", mime_global_b64 },
56 { "unb64", mime_global_unb64 },
57 { "fmt", mime_global_fmt },
58 { NULL, NULL }
59};
60
61/*-------------------------------------------------------------------------*\
62* Quoted-printable globals
63\*-------------------------------------------------------------------------*/
64static UC qpclass[256];
65static UC qpbase[] = "0123456789ABCDEF";
66static UC qpunbase[256];
67enum {QP_PLAIN, QP_QUOTED, QP_CR, QP_IF_LAST};
68
69/*-------------------------------------------------------------------------*\
70* Base64 globals
71\*-------------------------------------------------------------------------*/
72static const UC b64base[] =
73 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
74static UC b64unbase[256];
75
76/*=========================================================================*\
77* Exported functions
78\*=========================================================================*/
79/*-------------------------------------------------------------------------*\
80* Initializes module
81\*-------------------------------------------------------------------------*/
82void mime_open(lua_State *L)
83{
84 lua_pushstring(L, LUASOCKET_LIBNAME);
85 lua_gettable(L, LUA_GLOBALSINDEX);
86 if (lua_isnil(L, -1)) {
87 lua_pop(L, 1);
88 lua_newtable(L);
89 lua_pushstring(L, LUASOCKET_LIBNAME);
90 lua_pushvalue(L, -2);
91 lua_settable(L, LUA_GLOBALSINDEX);
92 }
93 lua_pushstring(L, "mime");
94 lua_newtable(L);
95 luaL_openlib(L, NULL, func, 0);
96 lua_settable(L, -3);
97 lua_pop(L, 1);
98 /* initialize lookup tables */
99 qpfill(qpclass, qpunbase);
100 b64fill(b64unbase);
101}
102
103/*=========================================================================*\
104* Global Lua functions
105\*=========================================================================*/
106/*-------------------------------------------------------------------------*\
107* Incrementaly breaks a string into lines
108* A, n = fmt(B, length, left)
109* A is a copy of B, broken into lines of at most 'length' bytes.
110* Left is how many bytes are left in the first line of B. 'n' is the number
111* of bytes left in the last line of A.
112\*-------------------------------------------------------------------------*/
113static int mime_global_fmt(lua_State *L)
114{
115 size_t size = 0;
116 const UC *input = lua_isnil(L, 1)? NULL: luaL_checklstring(L, 1, &size);
117 const UC *last = input + size;
118 int length = (int) luaL_checknumber(L, 2);
119 int left = (int) luaL_optnumber(L, 3, length);
120 const UC *marker = luaL_optstring(L, 4, CRLF);
121 luaL_Buffer buffer;
122 luaL_buffinit(L, &buffer);
123 while (input < last) {
124 luaL_putchar(&buffer, *input++);
125 if (--left <= 0) {
126 luaL_addstring(&buffer, marker);
127 left = length;
128 }
129 }
130 if (!input && left < length) {
131 luaL_addstring(&buffer, marker);
132 left = length;
133 }
134 luaL_pushresult(&buffer);
135 lua_pushnumber(L, left);
136 return 2;
137}
138
139/*-------------------------------------------------------------------------*\
140* Fill base64 decode map.
141\*-------------------------------------------------------------------------*/
142static void b64fill(UC *b64unbase)
143{
144 int i;
145 for (i = 0; i < 255; i++) b64unbase[i] = 255;
146 for (i = 0; i < 64; i++) b64unbase[b64base[i]] = i;
147 b64unbase['='] = 0;
148}
149
150/*-------------------------------------------------------------------------*\
151* Acumulates bytes in input buffer until 3 bytes are available.
152* Translate the 3 bytes into Base64 form and append to buffer.
153* Returns new number of bytes in buffer.
154\*-------------------------------------------------------------------------*/
155static size_t b64encode(UC c, UC *input, size_t size,
156 luaL_Buffer *buffer)
157{
158 input[size++] = c;
159 if (size == 3) {
160 UC code[4];
161 unsigned long value = 0;
162 value += input[0]; value <<= 8;
163 value += input[1]; value <<= 8;
164 value += input[2];
165 code[3] = b64base[value & 0x3f]; value >>= 6;
166 code[2] = b64base[value & 0x3f]; value >>= 6;
167 code[1] = b64base[value & 0x3f]; value >>= 6;
168 code[0] = b64base[value];
169 luaL_addlstring(buffer, code, 4);
170 size = 0;
171 }
172 return size;
173}
174
175/*-------------------------------------------------------------------------*\
176* Encodes the Base64 last 1 or 2 bytes and adds padding '='
177* Result, if any, is appended to buffer.
178* Returns 0.
179\*-------------------------------------------------------------------------*/
180static size_t b64pad(const UC *input, size_t size,
181 luaL_Buffer *buffer)
182{
183 unsigned long value = 0;
184 UC code[4] = "====";
185 switch (size) {
186 case 1:
187 value = input[0] << 4;
188 code[1] = b64base[value & 0x3f]; value >>= 6;
189 code[0] = b64base[value];
190 luaL_addlstring(buffer, code, 4);
191 break;
192 case 2:
193 value = input[0]; value <<= 8;
194 value |= input[1]; value <<= 2;
195 code[2] = b64base[value & 0x3f]; value >>= 6;
196 code[1] = b64base[value & 0x3f]; value >>= 6;
197 code[0] = b64base[value];
198 luaL_addlstring(buffer, code, 4);
199 break;
200 case 0: /* fall through */
201 default:
202 break;
203 }
204 return 0;
205}
206
207/*-------------------------------------------------------------------------*\
208* Acumulates bytes in input buffer until 4 bytes are available.
209* Translate the 4 bytes from Base64 form and append to buffer.
210* Returns new number of bytes in buffer.
211\*-------------------------------------------------------------------------*/
212static size_t b64decode(UC c, UC *input, size_t size,
213 luaL_Buffer *buffer)
214{
215
216 /* ignore invalid characters */
217 if (b64unbase[c] > 64) return size;
218 input[size++] = c;
219 /* decode atom */
220 if (size == 4) {
221 UC decoded[3];
222 int valid, value = 0;
223 value = b64unbase[input[0]]; value <<= 6;
224 value |= b64unbase[input[1]]; value <<= 6;
225 value |= b64unbase[input[2]]; value <<= 6;
226 value |= b64unbase[input[3]];
227 decoded[2] = (UC) (value & 0xff); value >>= 8;
228 decoded[1] = (UC) (value & 0xff); value >>= 8;
229 decoded[0] = (UC) value;
230 /* take care of paddding */
231 valid = (input[2] == '=') ? 1 : (input[3] == '=') ? 2 : 3;
232 luaL_addlstring(buffer, decoded, valid);
233 return 0;
234 /* need more data */
235 } else return size;
236}
237
238/*-------------------------------------------------------------------------*\
239* Incrementally applies the Base64 transfer content encoding to a string
240* A, B = b64(C, D)
241* A is the encoded version of the largest prefix of C .. D that is
242* divisible by 3. B has the remaining bytes of C .. D, *without* encoding.
243* The easiest thing would be to concatenate the two strings and
244* encode the result, but we can't afford that or Lua would dupplicate
245* every chunk we received.
246\*-------------------------------------------------------------------------*/
247static int mime_global_b64(lua_State *L)
248{
249 UC atom[3];
250 size_t isize = 0, asize = 0;
251 const UC *input = luaL_checklstring(L, 1, &isize);
252 const UC *last = input + isize;
253 luaL_Buffer buffer;
254 luaL_buffinit(L, &buffer);
255 while (input < last)
256 asize = b64encode(*input++, atom, asize, &buffer);
257 input = luaL_optlstring(L, 2, NULL, &isize);
258 if (input) {
259 last = input + isize;
260 while (input < last)
261 asize = b64encode(*input++, atom, asize, &buffer);
262 } else
263 asize = b64pad(atom, asize, &buffer);
264 luaL_pushresult(&buffer);
265 lua_pushlstring(L, atom, asize);
266 return 2;
267}
268
269/*-------------------------------------------------------------------------*\
270* Incrementally removes the Base64 transfer content encoding from a string
271* A, B = b64(C, D)
272* A is the encoded version of the largest prefix of C .. D that is
273* divisible by 4. B has the remaining bytes of C .. D, *without* encoding.
274\*-------------------------------------------------------------------------*/
275static int mime_global_unb64(lua_State *L)
276{
277 UC atom[4];
278 size_t isize = 0, asize = 0;
279 const UC *input = luaL_checklstring(L, 1, &isize);
280 const UC *last = input + isize;
281 luaL_Buffer buffer;
282 luaL_buffinit(L, &buffer);
283 while (input < last)
284 asize = b64decode(*input++, atom, asize, &buffer);
285 input = luaL_optlstring(L, 2, NULL, &isize);
286 if (input) {
287 last = input + isize;
288 while (input < last)
289 asize = b64decode(*input++, atom, asize, &buffer);
290 }
291 luaL_pushresult(&buffer);
292 lua_pushlstring(L, atom, asize);
293 return 2;
294}
295
296/*-------------------------------------------------------------------------*\
297* Quoted-printable encoding scheme
298* all (except CRLF in text) can be =XX
299* CLRL in not text must be =XX=XX
300* 33 through 60 inclusive can be plain
301* 62 through 120 inclusive can be plain
302* 9 and 32 can be plain, unless in the end of a line, where must be =XX
303* encoded lines must be no longer than 76 not counting CRLF
304* soft line-break are =CRLF
305* !"#$@[\]^`{|}~ should be =XX for EBCDIC compatibility
306* To encode one byte, we need to see the next two.
307* Worst case is when we see a space, and wonder if a CRLF is comming
308\*-------------------------------------------------------------------------*/
309/*-------------------------------------------------------------------------*\
310* Split quoted-printable characters into classes
311* Precompute reverse map for encoding
312\*-------------------------------------------------------------------------*/
313static void qpfill(UC *qpclass, UC *qpunbase)
314{
315 int i;
316 for (i = 0; i < 256; i++) qpclass[i] = QP_QUOTED;
317 for (i = 33; i <= 60; i++) qpclass[i] = QP_PLAIN;
318 for (i = 62; i <= 120; i++) qpclass[i] = QP_PLAIN;
319 qpclass[HT] = QP_IF_LAST; qpclass[SP] = QP_IF_LAST;
320 qpclass['!'] = QP_QUOTED; qpclass['"'] = QP_QUOTED;
321 qpclass['#'] = QP_QUOTED; qpclass['$'] = QP_QUOTED;
322 qpclass['@'] = QP_QUOTED; qpclass['['] = QP_QUOTED;
323 qpclass['\\'] = QP_QUOTED; qpclass[']'] = QP_QUOTED;
324 qpclass['^'] = QP_QUOTED; qpclass['`'] = QP_QUOTED;
325 qpclass['{'] = QP_QUOTED; qpclass['|'] = QP_QUOTED;
326 qpclass['}'] = QP_QUOTED; qpclass['~'] = QP_QUOTED;
327 qpclass['}'] = QP_QUOTED; qpclass[CR] = QP_CR;
328 for (i = 0; i < 256; i++) qpunbase[i] = 255;
329 qpunbase['0'] = 0; qpunbase['1'] = 1; qpunbase['2'] = 2;
330 qpunbase['3'] = 3; qpunbase['4'] = 4; qpunbase['5'] = 5;
331 qpunbase['6'] = 6; qpunbase['7'] = 7; qpunbase['8'] = 8;
332 qpunbase['9'] = 9; qpunbase['A'] = 10; qpunbase['a'] = 10;
333 qpunbase['B'] = 11; qpunbase['b'] = 11; qpunbase['C'] = 12;
334 qpunbase['c'] = 12; qpunbase['D'] = 13; qpunbase['d'] = 13;
335 qpunbase['E'] = 14; qpunbase['e'] = 14; qpunbase['F'] = 15;
336 qpunbase['f'] = 15;
337}
338
339/*-------------------------------------------------------------------------*\
340* Output one character in form =XX
341\*-------------------------------------------------------------------------*/
342static void qpquote(UC c, luaL_Buffer *buffer)
343{
344 luaL_putchar(buffer, '=');
345 luaL_putchar(buffer, qpbase[c >> 4]);
346 luaL_putchar(buffer, qpbase[c & 0x0F]);
347}
348
349/*-------------------------------------------------------------------------*\
350* Accumulate characters until we are sure about how to deal with them.
351* Once we are sure, output the to the buffer, in the correct form.
352\*-------------------------------------------------------------------------*/
353static size_t qpencode(UC c, UC *input, size_t size,
354 const UC *marker, luaL_Buffer *buffer)
355{
356 input[size++] = c;
357 /* deal with all characters we can have */
358 while (size > 0) {
359 switch (qpclass[input[0]]) {
360 /* might be the CR of a CRLF sequence */
361 case QP_CR:
362 if (size < 2) return size;
363 if (input[1] == LF) {
364 luaL_addstring(buffer, marker);
365 return 0;
366 } else qpquote(input[0], buffer);
367 break;
368 /* might be a space and that has to be quoted if last in line */
369 case QP_IF_LAST:
370 if (size < 3) return size;
371 /* if it is the last, quote it and we are done */
372 if (input[1] == CR && input[2] == LF) {
373 qpquote(input[0], buffer);
374 luaL_addstring(buffer, marker);
375 return 0;
376 } else luaL_putchar(buffer, input[0]);
377 break;
378 /* might have to be quoted always */
379 case QP_QUOTED:
380 qpquote(input[0], buffer);
381 break;
382 /* might never have to be quoted */
383 default:
384 luaL_putchar(buffer, input[0]);
385 break;
386 }
387 input[0] = input[1]; input[1] = input[2];
388 size--;
389 }
390 return 0;
391}
392
393/*-------------------------------------------------------------------------*\
394* Deal with the final characters
395\*-------------------------------------------------------------------------*/
396static void qppad(UC *input, size_t size, luaL_Buffer *buffer)
397{
398 size_t i;
399 for (i = 0; i < size; i++) {
400 if (qpclass[input[i]] == QP_PLAIN) luaL_putchar(buffer, input[i]);
401 else qpquote(input[i], buffer);
402 }
403 luaL_addstring(buffer, EQCRLF);
404}
405
406/*-------------------------------------------------------------------------*\
407* Incrementally converts a string to quoted-printable
408* A, B = qp(C, D, marker)
409* Crlf is the text to be used to replace CRLF sequences found in A.
410* A is the encoded version of the largest prefix of C .. D that
411* can be encoded without doubts.
412* B has the remaining bytes of C .. D, *without* encoding.
413\*-------------------------------------------------------------------------*/
414static int mime_global_qp(lua_State *L)
415{
416
417 size_t asize = 0, isize = 0;
418 UC atom[3];
419 const UC *input = lua_isnil(L, 1) ? NULL: luaL_checklstring(L, 1, &isize);
420 const UC *last = input + isize;
421 const UC *marker = luaL_optstring(L, 3, CRLF);
422 luaL_Buffer buffer;
423 luaL_buffinit(L, &buffer);
424 while (input < last)
425 asize = qpencode(*input++, atom, asize, marker, &buffer);
426 input = luaL_optlstring(L, 2, NULL, &isize);
427 if (input) {
428 last = input + isize;
429 while (input < last)
430 asize = qpencode(*input++, atom, asize, marker, &buffer);
431 } else qppad(atom, asize, &buffer);
432 luaL_pushresult(&buffer);
433 lua_pushlstring(L, atom, asize);
434 return 2;
435}
436
437/*-------------------------------------------------------------------------*\
438* Accumulate characters until we are sure about how to deal with them.
439* Once we are sure, output the to the buffer, in the correct form.
440\*-------------------------------------------------------------------------*/
441static size_t qpdecode(UC c, UC *input, size_t size,
442 luaL_Buffer *buffer)
443{
444 input[size++] = c;
445 /* deal with all characters we can deal */
446 while (size > 0) {
447 int c, d;
448 switch (input[0]) {
449 /* if we have an escape character */
450 case '=':
451 if (size < 3) return size;
452 /* eliminate soft line break */
453 if (input[1] == CR && input[2] == LF) return 0;
454 /* decode quoted representation */
455 c = qpunbase[input[1]]; d = qpunbase[input[2]];
456 /* if it is an invalid, do not decode */
457 if (c > 15 || d > 15) luaL_addlstring(buffer, input, 3);
458 else luaL_putchar(buffer, (c << 4) + d);
459 return 0;
460 case CR:
461 if (size < 2) return size;
462 if (input[1] == LF) luaL_addlstring(buffer, input, 2);
463 return 0;
464 default:
465 if (input[0] == HT || (input[0] > 31 && input[0] < 127))
466 luaL_putchar(buffer, input[0]);
467 return 0;
468 }
469 input[0] = input[1]; input[1] = input[2];
470 size--;
471 }
472 return 0;
473}
474
475/*-------------------------------------------------------------------------*\
476* Incrementally decodes a string in quoted-printable
477* A, B = qp(C, D)
478* A is the decoded version of the largest prefix of C .. D that
479* can be decoded without doubts.
480* B has the remaining bytes of C .. D, *without* decoding.
481\*-------------------------------------------------------------------------*/
482static int mime_global_unqp(lua_State *L)
483{
484
485 size_t asize = 0, isize = 0;
486 UC atom[3];
487 const UC *input = lua_isnil(L, 1) ? NULL: luaL_checklstring(L, 1, &isize);
488 const UC *last = input + isize;
489 luaL_Buffer buffer;
490 luaL_buffinit(L, &buffer);
491 while (input < last)
492 asize = qpdecode(*input++, atom, asize, &buffer);
493 input = luaL_optlstring(L, 2, NULL, &isize);
494 if (input) {
495 last = input + isize;
496 while (input < last)
497 asize = qpdecode(*input++, atom, asize, &buffer);
498 }
499 luaL_pushresult(&buffer);
500 lua_pushlstring(L, atom, asize);
501 return 2;
502}
503
504/*-------------------------------------------------------------------------*\
505* Incrementally breaks a quoted-printed string into lines
506* A, n = qpfmt(B, length, left)
507* A is a copy of B, broken into lines of at most 'length' bytes.
508* Left is how many bytes are left in the first line of B. 'n' is the number
509* of bytes left in the last line of A.
510* There are two complications: lines can't be broken in the middle
511* of an encoded =XX, and there might be line breaks already
512\*-------------------------------------------------------------------------*/
513static int mime_global_qpfmt(lua_State *L)
514{
515 size_t size = 0;
516 const UC *input = lua_isnil(L, 1)? NULL: luaL_checklstring(L, 1, &size);
517 const UC *last = input + size;
518 int length = (int) luaL_checknumber(L, 2);
519 int left = (int) luaL_optnumber(L, 3, length);
520 luaL_Buffer buffer;
521 luaL_buffinit(L, &buffer);
522 while (input < last) {
523 left--;
524 switch (*input) {
525 case '=':
526 /* if there's no room in this line for the quoted char,
527 * output a soft line break now */
528 if (left <= 3) {
529 luaL_addstring(&buffer, EQCRLF);
530 left = length;
531 }
532 break;
533 /* \r\n starts a new line */
534 case CR:
535 break;
536 case LF:
537 left = length;
538 break;
539 default:
540 /* if in last column, output a soft line break */
541 if (left <= 1) {
542 luaL_addstring(&buffer, EQCRLF);
543 left = length;
544 }
545 }
546 luaL_putchar(&buffer, *input);
547 input++;
548 }
549 if (!input && left < length) {
550 luaL_addstring(&buffer, EQCRLF);
551 left = length;
552 }
553 luaL_pushresult(&buffer);
554 lua_pushnumber(L, left);
555 return 2;
556}
557
558/*-------------------------------------------------------------------------*\
559* Here is what we do: \n, \r and \f are considered candidates for line
560* break. We issue *one* new line marker if any of them is seen alone, or
561* followed by a different one. That is, \n\n, \r\r and \f\f will issue two
562* end of line markers each, but \r\n, \n\r, \r\f etc will only issue *one*
563* marker. This covers Mac OS, Mac OS X, VMS, Unix and DOS, as well as
564* probably other more obscure conventions.
565\*-------------------------------------------------------------------------*/
566#define eolcandidate(c) (c == CR || c == LF)
567static size_t eolconvert(UC c, UC *input, size_t size,
568 const UC *marker, luaL_Buffer *buffer)
569{
570 input[size++] = c;
571 /* deal with all characters we can deal */
572 if (eolcandidate(input[0])) {
573 if (size < 2) return size;
574 luaL_addstring(buffer, marker);
575 if (eolcandidate(input[1])) {
576 if (input[0] == input[1]) luaL_addstring(buffer, marker);
577 } else luaL_putchar(buffer, input[1]);
578 return 0;
579 } else {
580 luaL_putchar(buffer, input[0]);
581 return 0;
582 }
583}
584
585/*-------------------------------------------------------------------------*\
586* Converts a string to uniform EOL convention.
587* A, B = eol(C, D, marker)
588* A is the converted version of the largest prefix of C .. D that
589* can be converted without doubts.
590* B has the remaining bytes of C .. D, *without* convertion.
591\*-------------------------------------------------------------------------*/
592static int mime_global_eol(lua_State *L)
593{
594 size_t asize = 0, isize = 0;
595 UC atom[2];
596 const UC *input = lua_isnil(L, 1)? NULL: luaL_checklstring(L, 1, &isize);
597 const UC *last = input + isize;
598 const UC *marker = luaL_optstring(L, 3, CRLF);
599 luaL_Buffer buffer;
600 luaL_buffinit(L, &buffer);
601 while (input < last)
602 asize = eolconvert(*input++, atom, asize, marker, &buffer);
603 input = luaL_optlstring(L, 2, NULL, &isize);
604 if (input) {
605 last = input + isize;
606 while (input < last)
607 asize = eolconvert(*input++, atom, asize, marker, &buffer);
608 /* if there is something in atom, it's one character, and it
609 * is a candidate. so we output a new line */
610 } else if (asize > 0) luaL_addstring(&buffer, marker);
611 luaL_pushresult(&buffer);
612 lua_pushlstring(L, atom, asize);
613 return 2;
614}
diff --git a/src/mime.h b/src/mime.h
new file mode 100644
index 0000000..8323783
--- /dev/null
+++ b/src/mime.h
@@ -0,0 +1,17 @@
1#ifndef MIME_H
2#define MIME_H
3/*=========================================================================*\
4* Mime support functions
5* LuaSocket toolkit
6*
7* This module provides functions to implement transfer content encodings
8* and formatting conforming to RFC 2045. It is used by mime.lua, which
9* provide a higher level interface to this functionality.
10*
11* RCS ID: $Id$
12\*=========================================================================*/
13#include <lua.h>
14
15void mime_open(lua_State *L);
16
17#endif /* MIME_H */
diff --git a/src/mime.lua b/src/mime.lua
new file mode 100644
index 0000000..86b3af2
--- /dev/null
+++ b/src/mime.lua
@@ -0,0 +1,104 @@
1-- make sure LuaSocket is loaded
2if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end
3-- get LuaSocket namespace
4local socket = _G[LUASOCKET_LIBNAME]
5if not socket then error('module requires LuaSocket') end
6-- create code namespace inside LuaSocket namespace
7local mime = socket.mime or {}
8socket.mime = mime
9-- make all module globals fall into mime namespace
10setmetatable(mime, { __index = _G })
11setfenv(1, mime)
12
13base64 = {}
14qprint = {}
15
16function base64.encode()
17 local unfinished = ""
18 return function(chunk)
19 local done
20 done, unfinished = b64(unfinished, chunk)
21 return done
22 end
23end
24
25function base64.decode()
26 local unfinished = ""
27 return function(chunk)
28 local done
29 done, unfinished = unb64(unfinished, chunk)
30 return done
31 end
32end
33
34function qprint.encode(mode)
35 mode = (mode == "binary") and "=0D=0A" or "\13\10"
36 local unfinished = ""
37 return function(chunk)
38 local done
39 done, unfinished = qp(unfinished, chunk, mode)
40 return done
41 end
42end
43
44function qprint.decode()
45 local unfinished = ""
46 return function(chunk)
47 local done
48 done, unfinished = unqp(unfinished, chunk)
49 return done
50 end
51end
52
53function split(length, marker)
54 length = length or 76
55 local left = length
56 return function(chunk)
57 local done
58 done, left = fmt(chunk, length, left, marker)
59 return done
60 end
61end
62
63function qprint.split(length)
64 length = length or 76
65 local left = length
66 return function(chunk)
67 local done
68 done, left = qpfmt(chunk, length, left)
69 return done
70 end
71end
72
73function canonic(marker)
74 local unfinished = ""
75 return function(chunk)
76 local done
77 done, unfinished = eol(unfinished, chunk, marker)
78 return done
79 end
80end
81
82function chain(...)
83 local layers = table.getn(arg)
84 return function (chunk)
85 if not chunk then
86 local parts = {}
87 for i = 1, layers do
88 for j = i, layers do
89 chunk = arg[j](chunk)
90 end
91 table.insert(parts, chunk)
92 chunk = nil
93 end
94 return table.concat(parts)
95 else
96 for j = 1, layers do
97 chunk = arg[j](chunk)
98 end
99 return chunk
100 end
101 end
102end
103
104return code
diff --git a/src/tcp.c b/src/tcp.c
index ce2ae17..b4b9fd9 100644
--- a/src/tcp.c
+++ b/src/tcp.c
@@ -238,7 +238,7 @@ static int meth_bind(lua_State *L)
238 return 2; 238 return 2;
239 } 239 }
240 /* turn master object into a server object if there was a listen */ 240 /* turn master object into a server object if there was a listen */
241 if (backlog > 0) aux_setclass(L, "tcp{server}", 1); 241 if (backlog >= 0) aux_setclass(L, "tcp{server}", 1);
242 lua_pushnumber(L, 1); 242 lua_pushnumber(L, 1);
243 return 1; 243 return 1;
244} 244}
diff --git a/src/url.lua b/src/url.lua
index 27e7928..ab3a922 100644
--- a/src/url.lua
+++ b/src/url.lua
@@ -6,9 +6,102 @@
6-- RCS ID: $Id$ 6-- RCS ID: $Id$
7---------------------------------------------------------------------------- 7----------------------------------------------------------------------------
8 8
9local Public, Private = {}, {} 9-- make sure LuaSocket is loaded
10local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace 10if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end
11socket.url = Public 11-- get LuaSocket namespace
12local socket = _G[LUASOCKET_LIBNAME]
13if not socket then error('module requires LuaSocket') end
14-- create smtp namespace inside LuaSocket namespace
15local url = {}
16socket.url = url
17-- make all module globals fall into smtp namespace
18setmetatable(url, { __index = _G })
19setfenv(1, url)
20
21-----------------------------------------------------------------------------
22-- Encodes a string into its escaped hexadecimal representation
23-- Input
24-- s: binary string to be encoded
25-- Returns
26-- escaped representation of string binary
27-----------------------------------------------------------------------------
28function escape(s)
29 return string.gsub(s, "(.)", function(c)
30 return string.format("%%%02x", string.byte(c))
31 end)
32end
33
34-----------------------------------------------------------------------------
35-- Protects a path segment, to prevent it from interfering with the
36-- url parsing.
37-- Input
38-- s: binary string to be encoded
39-- Returns
40-- escaped representation of string binary
41-----------------------------------------------------------------------------
42local function make_set(t)
43 local s = {}
44 for i = 1, table.getn(t) do
45 s[t[i]] = 1
46 end
47 return s
48end
49
50-- these are allowed withing a path segment, along with alphanum
51-- other characters must be escaped
52local segment_set = make_set {
53 "-", "_", ".", "!", "~", "*", "'", "(",
54 ")", ":", "@", "&", "=", "+", "$", ",",
55}
56
57local function protect_segment(s)
58 return string.gsub(s, "(%W)", function (c)
59 if segment_set[c] then return c
60 else return escape(c) end
61 end)
62end
63
64-----------------------------------------------------------------------------
65-- Encodes a string into its escaped hexadecimal representation
66-- Input
67-- s: binary string to be encoded
68-- Returns
69-- escaped representation of string binary
70-----------------------------------------------------------------------------
71function unescape(s)
72 return string.gsub(s, "%%(%x%x)", function(hex)
73 return string.char(tonumber(hex, 16))
74 end)
75end
76
77-----------------------------------------------------------------------------
78-- Builds a path from a base path and a relative path
79-- Input
80-- base_path
81-- relative_path
82-- Returns
83-- corresponding absolute path
84-----------------------------------------------------------------------------
85local function absolute_path(base_path, relative_path)
86 if string.sub(relative_path, 1, 1) == "/" then return relative_path end
87 local path = string.gsub(base_path, "[^/]*$", "")
88 path = path .. relative_path
89 path = string.gsub(path, "([^/]*%./)", function (s)
90 if s ~= "./" then return s else return "" end
91 end)
92 path = string.gsub(path, "/%.$", "/")
93 local reduced
94 while reduced ~= path do
95 reduced = path
96 path = string.gsub(reduced, "([^/]*/%.%./)", function (s)
97 if s ~= "../../" then return "" else return s end
98 end)
99 end
100 path = string.gsub(reduced, "([^/]*/%.%.)$", function (s)
101 if s ~= "../.." then return "" else return s end
102 end)
103 return path
104end
12 105
13----------------------------------------------------------------------------- 106-----------------------------------------------------------------------------
14-- Parses a url and returns a table with all its parts according to RFC 2396 107-- Parses a url and returns a table with all its parts according to RFC 2396
@@ -28,7 +121,7 @@ socket.url = Public
28-- Obs: 121-- Obs:
29-- the leading '/' in {/<path>} is considered part of <path> 122-- the leading '/' in {/<path>} is considered part of <path>
30----------------------------------------------------------------------------- 123-----------------------------------------------------------------------------
31function Public.parse(url, default) 124function parse(url, default)
32 -- initialize default parameters 125 -- initialize default parameters
33 local parsed = default or {} 126 local parsed = default or {}
34 -- empty url is parsed to nil 127 -- empty url is parsed to nil
@@ -66,11 +159,11 @@ end
66-- Rebuilds a parsed URL from its components. 159-- Rebuilds a parsed URL from its components.
67-- Components are protected if any reserved or unallowed characters are found 160-- Components are protected if any reserved or unallowed characters are found
68-- Input 161-- Input
69-- parsed: parsed URL, as returned by Public.parse 162-- parsed: parsed URL, as returned by parse
70-- Returns 163-- Returns
71-- a stringing with the corresponding URL 164-- a stringing with the corresponding URL
72----------------------------------------------------------------------------- 165-----------------------------------------------------------------------------
73function Public.build(parsed) 166function build(parsed)
74 local url = parsed.path or "" 167 local url = parsed.path or ""
75 if parsed.params then url = url .. ";" .. parsed.params end 168 if parsed.params then url = url .. ";" .. parsed.params end
76 if parsed.query then url = url .. "?" .. parsed.query end 169 if parsed.query then url = url .. "?" .. parsed.query end
@@ -102,9 +195,9 @@ end
102-- Returns 195-- Returns
103-- corresponding absolute url 196-- corresponding absolute url
104----------------------------------------------------------------------------- 197-----------------------------------------------------------------------------
105function Public.absolute(base_url, relative_url) 198function absolute(base_url, relative_url)
106 local base = Public.parse(base_url) 199 local base = parse(base_url)
107 local relative = Public.parse(relative_url) 200 local relative = parse(relative_url)
108 if not base then return relative_url 201 if not base then return relative_url
109 elseif not relative then return base_url 202 elseif not relative then return base_url
110 elseif relative.scheme then return relative_url 203 elseif relative.scheme then return relative_url
@@ -121,10 +214,10 @@ function Public.absolute(base_url, relative_url)
121 end 214 end
122 end 215 end
123 else 216 else
124 relative.path = Private.absolute_path(base.path,relative.path) 217 relative.path = absolute_path(base.path,relative.path)
125 end 218 end
126 end 219 end
127 return Public.build(relative) 220 return build(relative)
128 end 221 end
129end 222end
130 223
@@ -135,13 +228,13 @@ end
135-- Returns 228-- Returns
136-- segment: a table with one entry per segment 229-- segment: a table with one entry per segment
137----------------------------------------------------------------------------- 230-----------------------------------------------------------------------------
138function Public.parse_path(path) 231function parse_path(path)
139 local parsed = {} 232 local parsed = {}
140 path = path or "" 233 path = path or ""
141 path = string.gsub(path, "%s", "") 234 path = string.gsub(path, "%s", "")
142 string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) 235 string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end)
143 for i = 1, table.getn(parsed) do 236 for i = 1, table.getn(parsed) do
144 parsed[i] = socket.code.unescape(parsed[i]) 237 parsed[i] = unescape(parsed[i])
145 end 238 end
146 if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end 239 if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end
147 if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end 240 if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end
@@ -154,9 +247,9 @@ end
154-- parsed: path segments 247-- parsed: path segments
155-- unsafe: if true, segments are not protected before path is built 248-- unsafe: if true, segments are not protected before path is built
156-- Returns 249-- Returns
157-- path: correspondin path stringing 250-- path: corresponding path stringing
158----------------------------------------------------------------------------- 251-----------------------------------------------------------------------------
159function Public.build_path(parsed, unsafe) 252function build_path(parsed, unsafe)
160 local path = "" 253 local path = ""
161 local n = table.getn(parsed) 254 local n = table.getn(parsed)
162 if unsafe then 255 if unsafe then
@@ -170,66 +263,14 @@ function Public.build_path(parsed, unsafe)
170 end 263 end
171 else 264 else
172 for i = 1, n-1 do 265 for i = 1, n-1 do
173 path = path .. Private.protect_segment(parsed[i]) 266 path = path .. protect_segment(parsed[i])
174 path = path .. "/" 267 path = path .. "/"
175 end 268 end
176 if n > 0 then 269 if n > 0 then
177 path = path .. Private.protect_segment(parsed[n]) 270 path = path .. protect_segment(parsed[n])
178 if parsed.is_directory then path = path .. "/" end 271 if parsed.is_directory then path = path .. "/" end
179 end 272 end
180 end 273 end
181 if parsed.is_absolute then path = "/" .. path end 274 if parsed.is_absolute then path = "/" .. path end
182 return path 275 return path
183end 276end
184
185function Private.make_set(t)
186 local s = {}
187 for i = 1, table.getn(t) do
188 s[t[i]] = 1
189 end
190 return s
191end
192
193-- these are allowed withing a path segment, along with alphanum
194-- other characters must be escaped
195Private.segment_set = Private.make_set {
196 "-", "_", ".", "!", "~", "*", "'", "(",
197 ")", ":", "@", "&", "=", "+", "$", ",",
198}
199
200function Private.protect_segment(s)
201 local segment_set = Private.segment_set
202 return string.gsub(s, "(%W)", function (c)
203 if segment_set[c] then return c
204 else return socket.code.escape(c) end
205 end)
206end
207
208-----------------------------------------------------------------------------
209-- Builds a path from a base path and a relative path
210-- Input
211-- base_path
212-- relative_path
213-- Returns
214-- corresponding absolute path
215-----------------------------------------------------------------------------
216function Private.absolute_path(base_path, relative_path)
217 if string.sub(relative_path, 1, 1) == "/" then return relative_path end
218 local path = string.gsub(base_path, "[^/]*$", "")
219 path = path .. relative_path
220 path = string.gsub(path, "([^/]*%./)", function (s)
221 if s ~= "./" then return s else return "" end
222 end)
223 path = string.gsub(path, "/%.$", "/")
224 local reduced
225 while reduced ~= path do
226 reduced = path
227 path = string.gsub(reduced, "([^/]*/%.%./)", function (s)
228 if s ~= "../../" then return "" else return s end
229 end)
230 end
231 path = string.gsub(reduced, "([^/]*/%.%.)$", function (s)
232 if s ~= "../.." then return "" else return s end
233 end)
234 return path
235end
diff --git a/test/httptest.lua b/test/httptest.lua
index 3d0db87..dc90741 100644
--- a/test/httptest.lua
+++ b/test/httptest.lua
@@ -5,14 +5,14 @@
5-- needs "AllowOverride AuthConfig" on /home/c/diego/tec/luasocket/test/auth 5-- needs "AllowOverride AuthConfig" on /home/c/diego/tec/luasocket/test/auth
6dofile("noglobals.lua") 6dofile("noglobals.lua")
7 7
8local host, proxyh, proxyp, request, response 8local host, proxyhost, proxyport, request, response
9local ignore, expect, index, prefix, cgiprefix 9local ignore, expect, index, prefix, cgiprefix
10 10
11local t = socket.time() 11local t = socket.time()
12 12
13host = host or "diego.princeton.edu" 13host = host or "diego.princeton.edu"
14proxyh = proxyh or "localhost" 14proxyhost = proxyhost or "localhost"
15proxyp = proxyp or 3128 15proxyport = proxyport or 3128
16prefix = prefix or "/luasocket-test" 16prefix = prefix or "/luasocket-test"
17cgiprefix = cgiprefix or "/luasocket-test-cgi" 17cgiprefix = cgiprefix or "/luasocket-test-cgi"
18 18
@@ -129,8 +129,8 @@ request = {
129 method = "POST", 129 method = "POST",
130 body = index, 130 body = index,
131 headers = { ["content-length"] = string.len(index) }, 131 headers = { ["content-length"] = string.len(index) },
132 port = proxyp, 132 proxyport = proxyport,
133 host = proxyh 133 proxyhost = proxyhost
134} 134}
135expect = { 135expect = {
136 body = index, 136 body = index,
@@ -170,8 +170,8 @@ check_request(request, expect, ignore)
170io.write("testing proxy with redirection: ") 170io.write("testing proxy with redirection: ")
171request = { 171request = {
172 url = "http://" .. host .. prefix, 172 url = "http://" .. host .. prefix,
173 host = proxyh, 173 proxyhost = proxyhost,
174 port = proxyp 174 proxyport = proxyport
175} 175}
176expect = { 176expect = {
177 body = index, 177 body = index,
@@ -267,7 +267,7 @@ io.write("testing manual basic auth: ")
267request = { 267request = {
268 url = "http://" .. host .. prefix .. "/auth/index.html", 268 url = "http://" .. host .. prefix .. "/auth/index.html",
269 headers = { 269 headers = {
270 authorization = "Basic " .. (socket.code.b64("luasocket:password")) 270 authorization = "Basic " .. (socket.mime.b64("luasocket:password"))
271 } 271 }
272} 272}
273expect = { 273expect = {
diff --git a/test/mimetest.lua b/test/mimetest.lua
new file mode 100644
index 0000000..5485db1
--- /dev/null
+++ b/test/mimetest.lua
@@ -0,0 +1,236 @@
1dofile("noglobals.lua")
2
3local qptest = "qptest.bin"
4local eqptest = "qptest.bin2"
5local dqptest = "qptest.bin3"
6
7local b64test = "luasocket"
8local eb64test = "b64test.bin"
9local db64test = "b64test.bin2"
10
11-- from Machado de Assis, "A Mão e a Rosa"
12local mao = [[
13 Cursavam estes dois moços a academia de S. Paulo, estando
14 Luís Alves no quarto ano e Estêvão no terceiro.
15 Conheceram-se na academia, e ficaram amigos íntimos, tanto
16 quanto podiam sê-lo dois espíritos diferentes, ou talvez por
17 isso mesmo que o eram. Estêvão, dotado de extrema
18 sensibilidade, e não menor fraqueza de ânimo, afetuoso e
19 bom, não daquela bondade varonil, que é apanágio de uma alma
20 forte, mas dessa outra bondade mole e de cera, que vai à
21 mercê de todas as circunstâncias, tinha, além de tudo isso,
22 o infortúnio de trazer ainda sobre o nariz os óculos
23 cor-de-rosa de suas virginais ilusões. Luís Alves via bem
24 com os olhos da cara. Não era mau rapaz, mas tinha o seu
25 grão de egoísmo, e se não era incapaz de afeições, sabia
26 regê-las, moderá-las, e sobretudo guiá-las ao seu próprio
27 interesse. Entre estes dois homens travara-se amizade
28 íntima, nascida para um na simpatia, para outro no costume.
29 Eram eles os naturais confidentes um do outro, com a
30 diferença que Luís Alves dava menos do que recebia, e, ainda
31 assim, nem tudo o que dava exprimia grande confiança.
32]]
33
34local fail = function(s)
35 s = s or "failed"
36 assert(nil, s)
37end
38
39local readfile = function(name)
40 local f = io.open(name, "r")
41 if not f then return nil end
42 local s = f:read("*a")
43 f:close()
44 return s
45end
46
47local function transform(input, output, filter)
48 local fi, err = io.open(input, "rb")
49 if not fi then fail(err) end
50 local fo, err = io.open(output, "wb")
51 if not fo then fail(err) end
52 while 1 do
53 local chunk = fi:read(math.random(0, 256))
54 fo:write(filter(chunk))
55 if not chunk then break end
56 end
57 fi:close()
58 fo:close()
59end
60
61local function compare(input, output)
62 local original = readfile(input)
63 local recovered = readfile(output)
64 if original ~= recovered then fail("recovering failed")
65 else print("ok") end
66end
67
68local function encode_qptest(mode)
69 local encode = socket.mime.qprint.encode(mode)
70 local split = socket.mime.qprint.split()
71 local chain = socket.mime.chain(encode, split)
72 transform(qptest, eqptest, chain)
73end
74
75local function compare_qptest()
76 compare(qptest, dqptest)
77end
78
79local function decode_qptest()
80 local decode = socket.mime.qprint.decode()
81 transform(eqptest, dqptest, decode)
82end
83
84local function create_qptest()
85 local f, err = io.open(qptest, "wb")
86 if not f then fail(err) end
87 -- try all characters
88 for i = 0, 255 do
89 f:write(string.char(i))
90 end
91 -- try all characters and different line sizes
92 for i = 0, 255 do
93 for j = 0, i do
94 f:write(string.char(i))
95 end
96 f:write("\r\n")
97 end
98 -- test latin text
99 f:write(mao)
100 -- force soft line breaks and treatment of space/tab in end of line
101 local tab
102 f:write(string.gsub(mao, "(%s)", function(c)
103 if tab then
104 tab = nil
105 return "\t"
106 else
107 tab = 1
108 return " "
109 end
110 end))
111 -- test crazy end of line conventions
112 local eol = { "\r\n", "\r", "\n", "\n\r" }
113 local which = 0
114 f:write(string.gsub(mao, "(\n)", function(c)
115 which = which + 1
116 if which > 4 then which = 1 end
117 return eol[which]
118 end))
119 for i = 1, 4 do
120 for j = 1, 4 do
121 f:write(eol[i])
122 f:write(eol[j])
123 end
124 end
125 -- try long spaced and tabbed lines
126 f:write("\r\n")
127 for i = 0, 255 do
128 f:write(string.char(9))
129 end
130 f:write("\r\n")
131 for i = 0, 255 do
132 f:write(' ')
133 end
134 f:write("\r\n")
135 for i = 0, 255 do
136 f:write(string.char(9),' ')
137 end
138 f:write("\r\n")
139 for i = 0, 255 do
140 f:write(' ',string.char(32))
141 end
142 f:write("\r\n")
143
144 f:close()
145end
146
147local function cleanup_qptest()
148 os.remove(qptest)
149 os.remove(eqptest)
150 os.remove(dqptest)
151end
152
153local function encode_b64test()
154 local e1 = socket.mime.base64.encode()
155 local e2 = socket.mime.base64.encode()
156 local e3 = socket.mime.base64.encode()
157 local e4 = socket.mime.base64.encode()
158 local sp4 = socket.mime.split()
159 local sp3 = socket.mime.split(59)
160 local sp2 = socket.mime.split(30)
161 local sp1 = socket.mime.split(27)
162 local chain = socket.mime.chain(e1, sp1, e2, sp2, e3, sp3, e4, sp4)
163 transform(b64test, eb64test, chain)
164end
165
166local function decode_b64test()
167 local d1 = socket.mime.base64.decode()
168 local d2 = socket.mime.base64.decode()
169 local d3 = socket.mime.base64.decode()
170 local d4 = socket.mime.base64.decode()
171 local chain = socket.mime.chain(d1, d2, d3, d4)
172 transform(eb64test, db64test, chain)
173end
174
175local function cleanup_b64test()
176 os.remove(eb64test)
177 os.remove(db64test)
178end
179
180local function compare_b64test()
181 compare(b64test, db64test)
182end
183
184local function padcheck(original, encoded)
185 local e = (socket.mime.b64(original))
186 local d = (socket.mime.unb64(encoded))
187 if e ~= encoded then fail("encoding failed") end
188 if d ~= original then fail("decoding failed") end
189end
190
191local function chunkcheck(original, encoded)
192 local len = string.len(original)
193 for i = 0, len do
194 local a = string.sub(original, 1, i)
195 local b = string.sub(original, i+1)
196 local e, r = socket.mime.b64(a, b)
197 local f = (socket.mime.b64(r))
198 if (e .. f ~= encoded) then fail(e .. f) end
199 end
200end
201
202local function padding_b64test()
203 padcheck("a", "YQ==")
204 padcheck("ab", "YWI=")
205 padcheck("abc", "YWJj")
206 padcheck("abcd", "YWJjZA==")
207 padcheck("abcde", "YWJjZGU=")
208 padcheck("abcdef", "YWJjZGVm")
209 padcheck("abcdefg", "YWJjZGVmZw==")
210 padcheck("abcdefgh", "YWJjZGVmZ2g=")
211 padcheck("abcdefghi", "YWJjZGVmZ2hp")
212 padcheck("abcdefghij", "YWJjZGVmZ2hpag==")
213 chunkcheck("abcdefgh", "YWJjZGVmZ2g=")
214 chunkcheck("abcdefghi", "YWJjZGVmZ2hp")
215 chunkcheck("abcdefghij", "YWJjZGVmZ2hpag==")
216 print("ok")
217end
218
219local t = socket.time()
220
221create_qptest()
222encode_qptest()
223decode_qptest()
224compare_qptest()
225encode_qptest("binary")
226decode_qptest()
227compare_qptest()
228cleanup_qptest()
229
230encode_b64test()
231decode_b64test()
232compare_b64test()
233cleanup_b64test()
234padding_b64test()
235
236print(string.format("done in %.2fs", socket.time() - t))