diff options
-rw-r--r-- | TODO | 7 | ||||
-rw-r--r-- | doc/reference.css | 1 | ||||
-rw-r--r-- | doc/reference.html | 6 | ||||
-rw-r--r-- | doc/stream.html | 89 | ||||
-rw-r--r-- | etc/eol.lua | 2 | ||||
-rw-r--r-- | etc/get.lua | 83 | ||||
-rw-r--r-- | src/http.lua | 25 | ||||
-rw-r--r-- | src/inet.c | 2 | ||||
-rw-r--r-- | src/luasocket.c | 8 | ||||
-rw-r--r-- | src/mime.c | 614 | ||||
-rw-r--r-- | src/mime.h | 17 | ||||
-rw-r--r-- | src/mime.lua | 104 | ||||
-rw-r--r-- | src/tcp.c | 2 | ||||
-rw-r--r-- | src/url.lua | 179 | ||||
-rw-r--r-- | test/httptest.lua | 16 | ||||
-rw-r--r-- | test/mimetest.lua | 236 |
16 files changed, 1201 insertions, 190 deletions
@@ -1,3 +1,10 @@ | |||
1 | |||
2 | comment the need of a content-length header in the post method... | ||
3 | |||
4 | comment the callback.lua module and the new mime module. | ||
5 | escape and unescape are missing! | ||
6 | |||
7 | add _tostring methods! | ||
1 | add callback module to manual | 8 | add callback module to manual |
2 | change stay to redirect in http.lua and in manual | 9 | change stay to redirect in http.lua and in manual |
3 | add timeout to request table | 10 | add 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; } | |||
16 | a[href] { color: #00007f; } | 16 | a[href] { color: #00007f; } |
17 | 17 | ||
18 | p.name { | 18 | p.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> |
39 | HTTP and FTP transfers sometimes involve large amounts of information. | 39 | HTTP, FTP, and SMTP transfers sometimes involve large amounts of |
40 | Sometimes an application needs to generate outgoing data in real time, | 40 | information. Sometimes an application needs to generate outgoing data |
41 | or needs to process incoming information as it is being received. To | 41 | in real time, or needs to process incoming information as it is being |
42 | address these problems, LuaSocket allows HTTP message bodies and FTP | 42 | received. To address these problems, LuaSocket allows HTTP and SMTP message |
43 | file contents to be received or sent through the callback mechanism | 43 | bodies and FTP file contents to be received or sent through the |
44 | outlined below. | 44 | callback mechanism outlined below. |
45 | </p> | 45 | </p> |
46 | 46 | ||
47 | <p> | 47 | <p> |
48 | Instead of returning the entire contents of a FTP file or HTTP message | 48 | Instead of returning the entire contents of an entity |
49 | body as strings to the Lua application, the library allows the user to | 49 | as strings to the Lua application, the library allows the user to |
50 | provide a <em>receive callback</em> that will be called with successive | 50 | provide a <em>receive callback</em> that will be called with successive |
51 | chunks of data, as the data becomes available. Conversely, the <em>send | 51 | chunks of data, as the data becomes available. Conversely, the <em>send |
52 | callbacks</em> should be used when data needed by LuaSocket | 52 | callbacks</em> can be used when the application wants to incrementally |
53 | is generated incrementally by the application. | 53 | provide 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. |
70 | When the transmission is over, the function is called with an | 70 | When the transmission is over, the function is called with an |
71 | empty string (i.e. <tt>""</tt>) as the <tt>chunk</tt>. If an error occurs, the | 71 | empty string (i.e. <tt>""</tt>) as the <tt>chunk</tt>. |
72 | function receives <tt>nil</tt> as <tt>chunk</tt> and an error message as | 72 | If an error occurs, the function receives <tt>nil</tt> |
73 | <tt>err</tt>. | 73 | as <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> |
77 | The callback can abort transmission by returning | 77 | The 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 | 78 | return value, and an optional error message as the |
79 | an error message. Any non-<tt>nil</tt> return value proceeds with the | 79 | second return value. If the application wants to continue receiving |
80 | transmission. | 80 | data, the function should return non-<tt>nil</tt> as it's first return |
81 | value. In this case, the function can optionally return a | ||
82 | new callback function, to replace itself, as the second return value. | ||
83 | </p> | ||
84 | |||
85 | <p class=note> | ||
86 | Note: 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 | 90 | function receive.concat(concat) |
85 | function 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) | |||
95 | end | 100 | end |
96 | </pre> | 101 | </pre> |
97 | 102 | ||
103 | <p class=note> | ||
104 | This function creates a new receive callback that concatenates all | ||
105 | received chunks into a the same concat object, which can later be | ||
106 | queried 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> |
110 | Each time the callback is called, it | 121 | Each time the callback is called, it should return the next chunk of data. It |
111 | should return the next part of the information the library is expecting, | 122 | can optionally return, as it's second return value, a new callback to replace |
112 | followed by the total number of bytes to be sent. | 123 | itself. The callback can abort the process at any time by returning |
113 | The callback can abort | 124 | <tt>nil</tt> followed by an optional error message. |
114 | the process at any time by returning <tt>nil</tt> followed by an | ||
115 | optional error message. | ||
116 | </p> | 125 | </p> |
117 | 126 | ||
118 | |||
119 | <p class=note> | 127 | <p class=note> |
120 | Note: The need for the second return value comes from the fact that, with | 128 | Note: Below is the implementation of the <tt>callback.send.file</tt> |
121 | the HTTP protocol for instance, the library needs to know in advance the | 129 | function. Given an open file handle, it returns a send callback that will send the contents of that file, chunk by chunk. |
122 | total 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 | 133 | function send.file(file, io_err) |
127 | function 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 | ||
149 | end | 142 | end |
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 @@ | |||
1 | marker = {['-u'] = '\10', ['-d'] = '\13\10'} | 1 | marker = {['-u'] = '\10', ['-d'] = '\13\10'} |
2 | arg = arg or {'-u'} | 2 | arg = arg or {'-u'} |
3 | marker = marker[arg[1]] or marker['-u'] | 3 | marker = marker[arg[1]] or marker['-u'] |
4 | local convert = socket.code.canonic(marker) | 4 | local convert = socket.mime.canonic(marker) |
5 | while 1 do | 5 | while 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) | |||
42 | end | 42 | end |
43 | 43 | ||
44 | -- returns a string with the current state of the download | 44 | -- returns a string with the current state of the download |
45 | function gauge(got, dt, size) | 45 | function 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 |
60 | end | 60 | end |
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 |
64 | function receive2disk(file, size) | 64 | function 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 | ||
85 | end | 85 | end |
86 | 86 | ||
87 | -- downloads a file using the ftp protocol | 87 | -- downloads a file using the ftp protocol |
88 | function getbyftp(url, file) | 88 | function 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 |
96 | end | 99 | end |
97 | 100 | ||
98 | -- downloads a file using the http protocol | 101 | -- downloads a file using the http protocol |
99 | function getbyhttp(url, file, size) | 102 | function 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 |
106 | end | 109 | end |
107 | 110 | ||
@@ -116,26 +119,22 @@ function gethttpsize(url) | |||
116 | end | 119 | end |
117 | end | 120 | end |
118 | 121 | ||
119 | -- determines the scheme and the file name of a given url | 122 | -- determines the scheme |
120 | function getschemeandname(url, name) | 123 | function 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 | ||
129 | end | 128 | end |
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> |
132 | function get(url, name) | 131 | function 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 | ||
139 | end | 138 | end |
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 | ----------------------------------------------------------------------------- |
422 | local function authorize(reqt, parsed, respt) | 422 | local 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) |
436 | end | 436 | end |
@@ -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 | ----------------------------------------------------------------------------- |
492 | local function request_uri(reqt, parsed) | 492 | local 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 | ----------------------------------------------------------------------------- |
545 | function request_cb(reqt, respt) | 545 | function 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 |
@@ -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 | |||
23 | typedef unsigned char UC; | ||
24 | static const UC CRLF[2] = {CR, LF}; | ||
25 | static const UC EQCRLF[3] = {'=', CR, LF}; | ||
26 | |||
27 | /*=========================================================================*\ | ||
28 | * Internal function prototypes. | ||
29 | \*=========================================================================*/ | ||
30 | static int mime_global_fmt(lua_State *L); | ||
31 | static int mime_global_b64(lua_State *L); | ||
32 | static int mime_global_unb64(lua_State *L); | ||
33 | static int mime_global_qp(lua_State *L); | ||
34 | static int mime_global_unqp(lua_State *L); | ||
35 | static int mime_global_qpfmt(lua_State *L); | ||
36 | static int mime_global_eol(lua_State *L); | ||
37 | |||
38 | static void b64fill(UC *b64unbase); | ||
39 | static size_t b64encode(UC c, UC *input, size_t size, luaL_Buffer *buffer); | ||
40 | static size_t b64pad(const UC *input, size_t size, luaL_Buffer *buffer); | ||
41 | static size_t b64decode(UC c, UC *input, size_t size, luaL_Buffer *buffer); | ||
42 | |||
43 | static void qpfill(UC *qpclass, UC *qpunbase); | ||
44 | static void qpquote(UC c, luaL_Buffer *buffer); | ||
45 | static size_t qpdecode(UC c, UC *input, size_t size, luaL_Buffer *buffer); | ||
46 | static size_t qpencode(UC c, UC *input, size_t size, | ||
47 | const UC *marker, luaL_Buffer *buffer); | ||
48 | |||
49 | /* code support functions */ | ||
50 | static 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 | \*-------------------------------------------------------------------------*/ | ||
64 | static UC qpclass[256]; | ||
65 | static UC qpbase[] = "0123456789ABCDEF"; | ||
66 | static UC qpunbase[256]; | ||
67 | enum {QP_PLAIN, QP_QUOTED, QP_CR, QP_IF_LAST}; | ||
68 | |||
69 | /*-------------------------------------------------------------------------*\ | ||
70 | * Base64 globals | ||
71 | \*-------------------------------------------------------------------------*/ | ||
72 | static const UC b64base[] = | ||
73 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | ||
74 | static UC b64unbase[256]; | ||
75 | |||
76 | /*=========================================================================*\ | ||
77 | * Exported functions | ||
78 | \*=========================================================================*/ | ||
79 | /*-------------------------------------------------------------------------*\ | ||
80 | * Initializes module | ||
81 | \*-------------------------------------------------------------------------*/ | ||
82 | void 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 | \*-------------------------------------------------------------------------*/ | ||
113 | static 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 | \*-------------------------------------------------------------------------*/ | ||
142 | static 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 | \*-------------------------------------------------------------------------*/ | ||
155 | static 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 | \*-------------------------------------------------------------------------*/ | ||
180 | static 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 | \*-------------------------------------------------------------------------*/ | ||
212 | static 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 | \*-------------------------------------------------------------------------*/ | ||
247 | static 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 | \*-------------------------------------------------------------------------*/ | ||
275 | static 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 | \*-------------------------------------------------------------------------*/ | ||
313 | static 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 | \*-------------------------------------------------------------------------*/ | ||
342 | static 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 | \*-------------------------------------------------------------------------*/ | ||
353 | static 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 | \*-------------------------------------------------------------------------*/ | ||
396 | static 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 | \*-------------------------------------------------------------------------*/ | ||
414 | static 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 | \*-------------------------------------------------------------------------*/ | ||
441 | static 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 | \*-------------------------------------------------------------------------*/ | ||
482 | static 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 | \*-------------------------------------------------------------------------*/ | ||
513 | static 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) | ||
567 | static 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 | \*-------------------------------------------------------------------------*/ | ||
592 | static 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 | |||
15 | void 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 | ||
2 | if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end | ||
3 | -- get LuaSocket namespace | ||
4 | local socket = _G[LUASOCKET_LIBNAME] | ||
5 | if not socket then error('module requires LuaSocket') end | ||
6 | -- create code namespace inside LuaSocket namespace | ||
7 | local mime = socket.mime or {} | ||
8 | socket.mime = mime | ||
9 | -- make all module globals fall into mime namespace | ||
10 | setmetatable(mime, { __index = _G }) | ||
11 | setfenv(1, mime) | ||
12 | |||
13 | base64 = {} | ||
14 | qprint = {} | ||
15 | |||
16 | function base64.encode() | ||
17 | local unfinished = "" | ||
18 | return function(chunk) | ||
19 | local done | ||
20 | done, unfinished = b64(unfinished, chunk) | ||
21 | return done | ||
22 | end | ||
23 | end | ||
24 | |||
25 | function base64.decode() | ||
26 | local unfinished = "" | ||
27 | return function(chunk) | ||
28 | local done | ||
29 | done, unfinished = unb64(unfinished, chunk) | ||
30 | return done | ||
31 | end | ||
32 | end | ||
33 | |||
34 | function 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 | ||
42 | end | ||
43 | |||
44 | function qprint.decode() | ||
45 | local unfinished = "" | ||
46 | return function(chunk) | ||
47 | local done | ||
48 | done, unfinished = unqp(unfinished, chunk) | ||
49 | return done | ||
50 | end | ||
51 | end | ||
52 | |||
53 | function 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 | ||
61 | end | ||
62 | |||
63 | function 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 | ||
71 | end | ||
72 | |||
73 | function canonic(marker) | ||
74 | local unfinished = "" | ||
75 | return function(chunk) | ||
76 | local done | ||
77 | done, unfinished = eol(unfinished, chunk, marker) | ||
78 | return done | ||
79 | end | ||
80 | end | ||
81 | |||
82 | function 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 | ||
102 | end | ||
103 | |||
104 | return code | ||
@@ -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 | ||
9 | local Public, Private = {}, {} | 9 | -- make sure LuaSocket is loaded |
10 | local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace | 10 | if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end |
11 | socket.url = Public | 11 | -- get LuaSocket namespace |
12 | local socket = _G[LUASOCKET_LIBNAME] | ||
13 | if not socket then error('module requires LuaSocket') end | ||
14 | -- create smtp namespace inside LuaSocket namespace | ||
15 | local url = {} | ||
16 | socket.url = url | ||
17 | -- make all module globals fall into smtp namespace | ||
18 | setmetatable(url, { __index = _G }) | ||
19 | setfenv(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 | ----------------------------------------------------------------------------- | ||
28 | function escape(s) | ||
29 | return string.gsub(s, "(.)", function(c) | ||
30 | return string.format("%%%02x", string.byte(c)) | ||
31 | end) | ||
32 | end | ||
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 | ----------------------------------------------------------------------------- | ||
42 | local 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 | ||
48 | end | ||
49 | |||
50 | -- these are allowed withing a path segment, along with alphanum | ||
51 | -- other characters must be escaped | ||
52 | local segment_set = make_set { | ||
53 | "-", "_", ".", "!", "~", "*", "'", "(", | ||
54 | ")", ":", "@", "&", "=", "+", "$", ",", | ||
55 | } | ||
56 | |||
57 | local 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) | ||
62 | end | ||
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 | ----------------------------------------------------------------------------- | ||
71 | function unescape(s) | ||
72 | return string.gsub(s, "%%(%x%x)", function(hex) | ||
73 | return string.char(tonumber(hex, 16)) | ||
74 | end) | ||
75 | end | ||
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 | ----------------------------------------------------------------------------- | ||
85 | local 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 | ||
104 | end | ||
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 | ----------------------------------------------------------------------------- |
31 | function Public.parse(url, default) | 124 | function 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 | ----------------------------------------------------------------------------- |
73 | function Public.build(parsed) | 166 | function 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 | ----------------------------------------------------------------------------- |
105 | function Public.absolute(base_url, relative_url) | 198 | function 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 |
129 | end | 222 | end |
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 | ----------------------------------------------------------------------------- |
138 | function Public.parse_path(path) | 231 | function 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 | ----------------------------------------------------------------------------- |
159 | function Public.build_path(parsed, unsafe) | 252 | function 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 |
183 | end | 276 | end |
184 | |||
185 | function 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 | ||
191 | end | ||
192 | |||
193 | -- these are allowed withing a path segment, along with alphanum | ||
194 | -- other characters must be escaped | ||
195 | Private.segment_set = Private.make_set { | ||
196 | "-", "_", ".", "!", "~", "*", "'", "(", | ||
197 | ")", ":", "@", "&", "=", "+", "$", ",", | ||
198 | } | ||
199 | |||
200 | function 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) | ||
206 | end | ||
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 | ----------------------------------------------------------------------------- | ||
216 | function 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 | ||
235 | end | ||
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 |
6 | dofile("noglobals.lua") | 6 | dofile("noglobals.lua") |
7 | 7 | ||
8 | local host, proxyh, proxyp, request, response | 8 | local host, proxyhost, proxyport, request, response |
9 | local ignore, expect, index, prefix, cgiprefix | 9 | local ignore, expect, index, prefix, cgiprefix |
10 | 10 | ||
11 | local t = socket.time() | 11 | local t = socket.time() |
12 | 12 | ||
13 | host = host or "diego.princeton.edu" | 13 | host = host or "diego.princeton.edu" |
14 | proxyh = proxyh or "localhost" | 14 | proxyhost = proxyhost or "localhost" |
15 | proxyp = proxyp or 3128 | 15 | proxyport = proxyport or 3128 |
16 | prefix = prefix or "/luasocket-test" | 16 | prefix = prefix or "/luasocket-test" |
17 | cgiprefix = cgiprefix or "/luasocket-test-cgi" | 17 | cgiprefix = 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 | } |
135 | expect = { | 135 | expect = { |
136 | body = index, | 136 | body = index, |
@@ -170,8 +170,8 @@ check_request(request, expect, ignore) | |||
170 | io.write("testing proxy with redirection: ") | 170 | io.write("testing proxy with redirection: ") |
171 | request = { | 171 | request = { |
172 | url = "http://" .. host .. prefix, | 172 | url = "http://" .. host .. prefix, |
173 | host = proxyh, | 173 | proxyhost = proxyhost, |
174 | port = proxyp | 174 | proxyport = proxyport |
175 | } | 175 | } |
176 | expect = { | 176 | expect = { |
177 | body = index, | 177 | body = index, |
@@ -267,7 +267,7 @@ io.write("testing manual basic auth: ") | |||
267 | request = { | 267 | request = { |
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 | } |
273 | expect = { | 273 | expect = { |
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 @@ | |||
1 | dofile("noglobals.lua") | ||
2 | |||
3 | local qptest = "qptest.bin" | ||
4 | local eqptest = "qptest.bin2" | ||
5 | local dqptest = "qptest.bin3" | ||
6 | |||
7 | local b64test = "luasocket" | ||
8 | local eb64test = "b64test.bin" | ||
9 | local db64test = "b64test.bin2" | ||
10 | |||
11 | -- from Machado de Assis, "A Mão e a Rosa" | ||
12 | local 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 | |||
34 | local fail = function(s) | ||
35 | s = s or "failed" | ||
36 | assert(nil, s) | ||
37 | end | ||
38 | |||
39 | local 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 | ||
45 | end | ||
46 | |||
47 | local 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() | ||
59 | end | ||
60 | |||
61 | local 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 | ||
66 | end | ||
67 | |||
68 | local 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) | ||
73 | end | ||
74 | |||
75 | local function compare_qptest() | ||
76 | compare(qptest, dqptest) | ||
77 | end | ||
78 | |||
79 | local function decode_qptest() | ||
80 | local decode = socket.mime.qprint.decode() | ||
81 | transform(eqptest, dqptest, decode) | ||
82 | end | ||
83 | |||
84 | local 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() | ||
145 | end | ||
146 | |||
147 | local function cleanup_qptest() | ||
148 | os.remove(qptest) | ||
149 | os.remove(eqptest) | ||
150 | os.remove(dqptest) | ||
151 | end | ||
152 | |||
153 | local 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) | ||
164 | end | ||
165 | |||
166 | local 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) | ||
173 | end | ||
174 | |||
175 | local function cleanup_b64test() | ||
176 | os.remove(eb64test) | ||
177 | os.remove(db64test) | ||
178 | end | ||
179 | |||
180 | local function compare_b64test() | ||
181 | compare(b64test, db64test) | ||
182 | end | ||
183 | |||
184 | local 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 | ||
189 | end | ||
190 | |||
191 | local 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 | ||
200 | end | ||
201 | |||
202 | local 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") | ||
217 | end | ||
218 | |||
219 | local t = socket.time() | ||
220 | |||
221 | create_qptest() | ||
222 | encode_qptest() | ||
223 | decode_qptest() | ||
224 | compare_qptest() | ||
225 | encode_qptest("binary") | ||
226 | decode_qptest() | ||
227 | compare_qptest() | ||
228 | cleanup_qptest() | ||
229 | |||
230 | encode_b64test() | ||
231 | decode_b64test() | ||
232 | compare_b64test() | ||
233 | cleanup_b64test() | ||
234 | padding_b64test() | ||
235 | |||
236 | print(string.format("done in %.2fs", socket.time() - t)) | ||