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)) | ||
