From 58096449c6044b7aade5cd41cfd71c6bec1d273d Mon Sep 17 00:00:00 2001
From: Diego Nehab
FTP (File Transfer Protocol) is a protocol used to transfer files
-between hosts. The module ftp.lua offers simple FTP support,
-allowing applications to download and upload files, and list directory
-contents. The implementation conforms to
+between hosts. The module ftp.lua offers simple FTP support.
+Applications can easily download and upload files.
+The implementation conforms to
RFC 959.
+High level functions are provided supporting the most common operations.
+These high level functions are implemented on top of a lower level
+interface. By using the low-level interface, users can easily create their
+own functions to access any operation supported by the FTP
+protocol. For that, check the implementation.
+
+To use some of the functions in this module, a good understanding of
+
+LTN012, Filters sources and sinks is necessary.
+
+The following constants can be set to control the default behaviour of
+the FTP module:
+
-socket.ftp.get(url)
-Downloads an URL from a FTP server.
+The get function has two forms. The simple form has fixed
+functionality: it downloads the contents of a URL and returns it as a
+string. The generic form allows a lot more control, as explained
+below.
-The function can be called either directly with a url
-or with a request table.
-Fields passed explicitly in the request table override those
-present in the url.
-
-The parameter type accepts values 'a' (ASCII, the
-default), 'i' (binary) or 'd' (directory listing) and
-determines the transfer type. If <path> ends with a
-'/' or type is 'd', a directory listing of
-<path> is returned. If no user is provided in the
-url or explicitly, the function tries to log in as user
-'anonymous'.
+If the argument of the get function is a table, the function
+expects at least the fields host, sink, and one of
+argument or path (argument takes
+precedence). Host is the server to connect to. Sink is
+the LTN12 sink that will receive the downloaded data. Argument or
+path give the target path to the resource in the server. The
+optional arguments are the following:
-If successful, the function returns
-the file content as a string. In case of error, the function returns
-nil and an error message describing the error.
+If successful, the simple version returns the URL contents as a
+string, and the generic function returns 1. In case of error, both
+functions return nil and an error message describing the
+error.
-socket.ftp.get_cb{
-Same as get, but the library returns
-the content of the downloaded file to the receive callback
-content_cb.
-
-Note: for more information on callbacks, refer to
-Streaming with callbacks.
-
-socket.ftp.put(url, content)
-Upload a file to a FTP server.
+The put function has two forms. The simple form has fixed
+functionality: it uploads a string of content into a URL. The generic form
+allows a lot more control, as explained below.
-The function can be called directly with a
-url and content parameters, or with a
-request table.
-Values passed explicitly in the request table override those present in
-the url. The parameter type accept values
-'a' (ASCII, the default) or 'i' (binary) and
-determines the transfer type. If no user is provided, the
-function tries to log in as 'anonymous'.
+If the argument of the put function is a table, the function
+expects at least the fields host, source, and one of
+argument or path (argument takes
+precedence). Host is the server to connect to. Source is
+the LTN12 source that will provide the contents to be uploaded.
+Argument or
+path give the target path to the resource in the server. The
+optional arguments are the following:
-If successful, the function returns 1. In case of error, the
-function returns nil followed by a string describing the error.
+Both functions return 1 if successful, or nil and an error
+message describing the reason for failure.
-socket.ftp.put_cb{
-Same as put, but the
-library obtains the contents of the file to be uploaded using the send
-callback content_cb.
-
-Note: for more information on callbacks, refer to
-Streaming with callbacks.
- The LTN12 module implements the ideas described in
+
+LTN012, Filters sources and sinks. This manual simply describe the
+functions. Please refer to the LTN for a deeper explanation of the
+functionality provided by this module.
+
+ltn12.filter.chain(filter1, filter2
+[, ... filterN])
+
+Returns a filter that passes all data it receives through each of a
+series of given filters.
+
+Filter1 to filterN are simple
+filters.
+
+The function returns the chained filter.
+
+The nesting of filters can be arbritrary. For instance, the useless filter
+below doesn't do anything but return the data that was passed to it,
+unaltered.
+
+ltn12.filter.cycle(low [, ctx, extra])
+
+Returns a high-level filter that cycles though a low-level filter by
+passing it each chunk and updating a context between calls.
+
+Low is the low-level filter to be cycled,
+ctx is the initial context and extra is any extra
+argument the low-level filter might take.
+
+The function returns the high-level filter.
+
+ltn12.pump.all(source, sink)
+
+Pumps all data from a source to a sink.
+
+If successful, the function returns a value that evaluates to
+true. In case
+of error, the function returns a false value, followed by an error message.
+
+ltn12.pump.step(source, sink)
+
+Pumps one chunk of data from a source to a sink.
+
+If successful, the function returns a value that evaluates to
+true. In case
+of error, the function returns a false value, followed by an error message.
+
+ltn12.sink.chain(filter, sink)
+
+Creates a new sink that passes data through a filter before sending
+it to a given sink.
+
+The function returns the new sink.
+
+ltn12.sink.error(message)
+
+Creates and returns a sink that aborts transmission with an error
+message.
+
+ltn12.sink.file(handle, message)
+
+Creates a sink that sends data to a file.
+
+Handle is a file handle. If handle is nil,
+message should give the reason for failure.
+
+The function returns a sink that sends all data to the given handle
+and closes the file when done, or a sink that aborts the transmission with
+an error message
+
+In the following example, notice how the prototype is designed to
+fit nicely with the io.open function.
+
+ltn12.sink.null()
+
+Returns a sink that ignores all data it receives.
+
+ltn12.sink.simplify(sink)
+
+Creates and returns a simple sink given a fancy sink.
+
+ltn12.sink.table([table])
+
+Creates a sink that stores all chunks in a table. The chunks can later be
+efficiently concatenated into a single string.
+
+Table is used to hold the chunks. If
+nil, the function creates its own table.
+
+The function returns the sink and the table.
+
+ltn12.source.cat(source1 [, source2, ...,
+sourceN])
+
+Creates a new source that produces the concatenation of the data produced
+by a number of sources.
+
+Source1 to sourceN are the original
+sources.
+
+The function returns the new source.
+
+ltn12.source.chain(source, filter)
+
+Creates a new source that passes data through a filter
+before returning it.
+
+The function returns the new source.
+
+ltn12.source.empty()
+
+Creates and returns an empty source.
+
+ltn12.source.error(message)
+
+Creates and returns a source that aborts transmission with an error
+message.
+
+ltn12.source.file(handle, message)
+
+Creates a source that produces the contents of a file.
+
+Handle is a file handle. If handle is nil,
+message should give the reason for failure.
+
+The function returns a source that reads chunks of data from
+given handle and returns it to the user,
+closing the file when done, or a source that aborts the transmission with
+an error message
+
+In the following example, notice how the prototype is designed to
+fit nicely with the io.open function.
+
+ltn12.source.simplify(source)
+
+Creates and returns a simple source given a fancy source.
+
+ltn12.source.string(string)
+
+Creates and returns a source that produces the contents of a
+string, chunk by chunk.
+
+The MIME module offers filters that apply and remove common
+content transfer encodings, such as Base64 and Quoted-Printable.
+It also provides functions to break text into lines and change
+the end-of-line convention.
+MIME is described mainly in
+RFC 2045,
+2046,
+2047,
+2048 and
+2049.
+
+All functionality provided by the MIME module
+follows the ideas presented in
+
+LTN012, Filters sources and sinks.
+
+mime.normalize([marker])
+
+Converts most common end-of-line markers to a specific given marker.
+
+Marker is the new marker. It defaults to CRLF, the canonic
+end-of-line marker defined by the MIME standard.
+
+The function returns a filter that performs the conversion.
+
+Note: There is no perfect solution to this problem. Different end-of-line
+markers are an evil that will probably plague developers forever.
+This function, however, will work perfectly for text created with any of
+the most common end-of-line markers, i.e. the MacOS (CR), the Unix (LF),
+or the DOS (CRLF) conventions. Even if the data has mixed end-of-line
+markers, the function will still work well, although it doesn't
+guarantee that the number of empty lines will be correct.
+
+mime.decode("base64")
+Returns a filter that decodes data from a given transfer content
+encoding.
+
+The function returns the created filter.
+
+mime.encode("base64")
+Returns a filter that encodes data according to a given transfer content
+encoding.
+
+In the Quoted-Printable case, the user can specify whether the data is
+textual or binary, by passing the mode strings "text" or
+"binary". Mode defaults to "text".
+
+The function returns the created filter.
+
+Although both transfer content encodings specify a limit for the line
+length, the encoding filters do not break text into lines (for
+added flexibility).
+Below is a filter that converts binary data to the Base64 transfer content
+encoding and breaks it into lines of the correct size.
+
+Note: Text data has to be converted to canonic form
+before being encoded.
+
+mime.wrap("text" [, length])
+Returns a filter that breaks data into lines.
+
+The "text" line-wrap filter simply breaks text into lines by
+inserting CRLF end-of-line markers at appropriate positions.
+Length defaults 76.
+The "base64" line-wrap filter works just like the default
+"text" line-wrap filter with default length.
+The function can also wrap "quoted-printable" lines, taking care
+not to break lines in the middle of an escaped character. In that case, the
+line length is fixed at 76.
+
+The function returns the created filter.
+
+For example, to create an encoding filter for the Quoted-Printable transfer content encoding of text data, do the following:
+
+Note: To break into lines with a different end-of-line convention, apply
+a normalization filter after the line break filter.
+
+A, B = mime.b64(C [, D])
+
+Low-level filter to perform Base64 encoding.
+
+A is the encoded version of the largest prefix of
+C..D
+that can be encoded unambiguously. B has the remaining bytes of
+C..D, before encoding.
+If D is nil, A is padded with
+the encoding of the remaining bytes of C.
+
+Note: The simplest use of this function is to encode a string into it's
+Base64 transfer content encoding. Notice the extra parenthesis around the
+call to mime.b64, to discard the second return value.
+
+A, B = mime.eol(C [, D, marker])
+
+Low-level filter to perform end-of-line marker translation.
+For each chunk, the function needs to know if the last character of the
+previous chunk could be part of an end-of-line marker or not. This is the
+context the function receives besides the chunk. An updated version of
+the context is returned after each new chunk.
+
+A is the translated version of D. C is the
+ASCII value of the last character of the previous chunk, if it was a
+candidate for line break, or 0 otherwise.
+B is the same as C, but for the current
+chunk. If D is nil, A includes a
+new end-of-line marker, depending on C.
+Marker gives the new end-of-line marker and defaults to CRLF.
+
+A, B = mime.qp(C [, D, marker])
+
+Low-level filter to perform Quoted-Printable encoding.
+
+A is the encoded version of the largest prefix of
+C..D
+that can be encoded unambiguously. B has the remaining bytes of
+C..D, before encoding.
+If D is nil, A is padded with
+the encoding of the remaining bytes of C.
+Throughout encoding, occurences of CRLF are replaced by the
+marker, which itself defaults to CRLF.
+
+Note: The simplest use of this function is to encode a string into it's
+Quoted-Printable transfer content encoding.
+Notice the extra parenthesis around the call to mime.qp, to discard the second return value.
+
+A, m = mime.qpwrp(n [, B, length])
+
+Low-level filter to break Quoted-Printable text into lines.
+
+A is a copy of B, broken into lines of at most
+length bytes (defaults to 76).
+'n' should tell how many bytes are left for the first
+line of B and 'm' returns the number of bytes
+left in the last line of A.
+
+Note: Besides breaking text into lines, this function makes sure the line
+breaks don't fall in the middle of an escaped character combination. Also,
+this function only breaks lines that are bigger than length bytes.
+
+A, B = mime.unb64(C [, D])
+
+Low-level filter to perform Base64 decoding.
+
+A is the decoded version of the largest prefix of
+C..D
+that can be decoded unambiguously. B has the remaining bytes of
+C..D, before decoding.
+If D is nil, A is the empty string
+and B returns whatever couldn't be decoded.
+
+Note: The simplest use of this function is to decode a string from it's
+Base64 transfer content encoding.
+Notice the extra parenthesis around the call to mime.unqp, to discard the second return value.
+
+A, B = mime.unqp(C [, D])
+
+Low-level filter to remove the Quoted-Printable transfer content encoding
+from data.
+
+A is the decoded version of the largest prefix of
+C..D
+that can be decoded unambiguously. B has the remaining bytes of
+C..D, before decoding.
+If D is nil, A is augmented with
+the encoding of the remaining bytes of C.
+
+Note: The simplest use of this function is to decode a string from it's
+Quoted-Printable transfer content encoding.
+Notice the extra parenthesis around the call to mime.unqp, to discard the second return value.
+
+A, m = mime.wrp(n [, B, length])
+
+Low-level filter to break text into lines with CRLF marker.
+Text is assumed to be in the normalize form.
+
+A is a copy of B, broken into lines of at most
+length bytes (defaults to 76).
+'n' should tell how many bytes are left for the first
+line of B and 'm' returns the number of bytes
+left in the last line of A.
+
+Note: This function only breaks lines that are bigger than
+length bytes. The resulting line length does not include the CRLF
+marker.
+
-The smtp.lua module provides functionality to send e-mail
+ The smtp.lua module provides functionality to send e-mail
messages. The implementation conforms to the Simple Mail Transfer Protocol,
RFC 2821.
-The other RFC of interest in this implementation is
-RFC 2822,
+Another RFC of interest is RFC 2822,
which governs the Internet Message Format.
+Multipart messages (those that contain attatchments) are part
+of the MIME standard, but described mainly
+in RFC
+2046
- In the description below, good understanding of LTN012, Filters
+sources and sinks and the MIME module is
+assumed. In fact, the SMTP module was the main reason for their
+creation.
MIME headers are represented as a Lua table in the form:
@@ -78,29 +85,56 @@ Note: MIME headers are independent of order. Therefore, there is no problem
in representing them in a Lua table.
+The following constants can be set to control the default behaviour of
+the SMTP module:
+
-socket.smtp.mail{
+smtp.send{
-Sends a message to a recipient list.
+Sends a message to a recipient list. Since sending messages is not as
+simple as downloading an URL from a FTP or HTTP server, this function
+doesn't have a simple interface. However, see the
+message source factory for
+a very powerful way to define the message contents.
-Rcpt is a Lua table with one entry for each recipient, or a string
+The sender is given by the e-mail address in the from field.
+Rcpt is a Lua table with one entry for each recipient e-mail
+address, or a string
in case there is just one recipient.
-The sender is given by the e-mail address from.
-The message is composed by the optional MIME Headers headers
-and text body. The message is sent using the server
-server.
+The contents of the message are given by a LTN12 source. Several
+arguments are optional:
+
-[ftp://][<user>[:<password>]@]<host>[:<port>][/<path>][type=a|i|d]
+[ftp://][<user>[:<password>]@]<host>[:<port>][/<path>][type=a|i]
+
+
+
-socket.ftp.get{
- url = string,
- type = string,
- user = string,
- password = string
+ftp.get(url)
+ftp.get{
+ host = string,
+ sink = LTN12 sink,
+ argument or path = string,
+ [user = string,]
+ [password = string]
+ [command = string,]
+ [port = number,]
+ [type = string,]
+ [step = LTN12 pump step],
}
+
--- Log as user "anonymous" on server "ftp.tecgraf.puc-rio.br",
--- go to directory "pub/lua" and get file "lua.tar.gz" as binary.
-f, e = socket.ftp.get("ftp://ftp.tecgraf.puc-rio.br/pub/lua/lua.tar.gz;type=i")
+-- load the ftp support
+local ftp = require("ftp")
-- Log as user "anonymous" on server "ftp.tecgraf.puc-rio.br",
--- go to director "pub" and retrieve directory listing of directory "lua"
-f, e = socket.ftp.get("ftp://ftp.tecgraf.puc-rio.br/pub/lua;type=d")
-
--- Log as user "diego", password "nehab", on server "ftp.tecgraf.puc-rio.br",
--- go to directory "tec/luasocket/bin" and retrieve file "luasocket.exe"
--- (actually, fails because of wrong password, of course)
-f, e = socket.ftp.get{
- url = "ftp://ftp.tecgraf.puc-rio.br/tec/luasocket/bin/luasocket.exe",
- user = "diego",
- password = "nehab",
- type = "i"
-}
--- f returns nil, and e returns an appropriate error message
+-- and get file "lua.tar.gz" from directory "pub/lua" as binary.
+f, e = ftp.get("ftp://ftp.tecgraf.puc-rio.br/pub/lua/lua.tar.gz;type=i")
-
+
+-- load needed modules
+local ftp = require("ftp")
+local ltn12 = require("ltn12")
+local url = require("url")
-
- url = string,
- type = string,
- content_cb = receive-callback,
- user = string,
- password = string
-}
-
-socket.ftp.put{
- url = string,
- content = string,
- type = string,
- user = string,
- password = string
+ftp.put(url, content)
+ftp.put{
+ host = string,
+ source = LTN12 sink,
+ argument or path = string,
+ [user = string,]
+ [password = string]
+ [command = string,]
+ [port = number,]
+ [type = string,]
+ [step = LTN12 pump step],
}
+
--- Log as user "anonymous" on server "ftp.free.org" and store file
--- "hello" with contents "hello world!", using binary mode for the transfer
-r, e = socket.ftp.put("ftp://ftp.free.org/hello;type=i", "hello world!\n")
+-- load the ftp support
+local ftp = require("ftp")
--- Does exactly the same, but logging in as diego
-r, e = socket.ftp.put{
- url = "ftp://ftp.free.org/hello",
- type = "i",
+-- Log as user "diego" on server "ftp.tecgraf.puc-rio.br",
+-- using password "nehab", and store a file "README" with contents
+-- "wrong password, of course"
+f, e = ftp.put("ftp://diego:nehab@ftp.tecgraf.puc-rio.br/README", "wrong password, of course")
+
+
+
+-- load the ftp support
+local ftp = require("ftp")
+local ltn12 = require("ltn12")
+
+-- Log as user "diego" on server "ftp.tecgraf.puc-rio.br",
+-- using password "nehab", and append to the file "LOG", sending the
+-- contents of a local file
+f, e = ftp.put{
+ host = "ftp.tecgraf.puc-rio.br",
user = "diego",
password = "nehab",
- content = "hello world\n"
+ command = "appe",
+ argument = "LOG",
+ source = ltn12.source.file(io.open("LOCAL-LOG", "r"))
}
-
-
-
-
- url = string,
- type = string,
- content_cb = send-callback,
- user = string,
- password = string
-}
-
--- Log as user "anonymous" on server "ftp.free.org" and store file
--- "hello" with contents of the same file in the current directory,
--- using binary mode for the transfer
-r, e = socket.ftp.put_cb{
- url = "ftp://ftp.free.org/hello",
- type = "i",
- content_cb = socket.callback.send_file(io.open("hello", "r"))
-}
-
-
diff --git a/doc/ltn12.html b/doc/ltn12.html
new file mode 100644
index 0000000..363ce43
--- /dev/null
+++ b/doc/ltn12.html
@@ -0,0 +1,421 @@
+
+
+
+
+LTN12
+
+Filters
+
+
+
+
+-- load required modules
+ltn12 = require("ltn12")
+mime = require("mime")
+
+-- create a silly identity filter
+id = ltn12.filter.chain(
+ mime.encode("quoted-printable"),
+ mime.encode("base64"),
+ mime.decode("base64"),
+ mime.decode("quoted-printable")
+)
+
+
+
+
+
+-- load the ltn12 module
+local ltn12 = require("ltn12")
+
+-- the base64 mime filter factory
+encodet['base64'] = function()
+ return ltn12.filter.cycle(b64, "")
+end
+
+
+
+
+Pumps
+
+
+
+Sinks
+
+
+
+
+-- load the ltn12 module
+local ltn12 = require("ltn12")
+
+-- copy a file
+ltn12.pump.all(
+ ltn12.source.file(io.open("original.png")),
+ ltn12.sink.file(io.open("copy.png"))
+)
+
+
+
+
+
+-- load needed modules
+local http = require("http")
+local ltn12 = require("ltn12")
+
+-- the http.get function
+function get(u)
+ local t = {}
+ local respt = request{
+ url = u,
+ sink = ltn12.sink.table(t)
+ }
+ return table.concat(t), respt.headers, respt.code, respt.error
+end
+
+
+
+
+Sources
+
+
+
+
+-- load the ltn12 module
+local ltn12 = require("ltn12")
+
+-- copy a file
+ltn12.pump.all(
+ ltn12.source.file(io.open("original.png")),
+ ltn12.sink.file(io.open("copy.png"))
+)
+
+
+
+
+MIME
+
+High-level filters
+
+
+
+
+mime.decode("quoted-printable")
+
+mime.encode("quoted-printable" [, mode])
+
+base64 = ltn12.filter.chain(
+ mime.encode("base64"),
+ mime.wrap("base64")
+)
+
+
+
+base64 = ltn12.filter.chain(
+ mime.normalize(),
+ mime.encode("base64"),
+ mime.wrap("base64")
+)
+
+
+
+
+
+mime.wrap("base64")
+mime.wrap("quoted-printable")
+
+qp = ltn12.filter.chain(
+ mime.normalize(),
+ mime.encode("quoted-printable"),
+ mime.wrap("quoted-printable")
+)
+
+
+Low-level filters
+
+
+
+
+print((mime.b64("diego:password")))
+--> ZGllZ286cGFzc3dvcmQ=
+
+
+
+
+
+-- translates the end-of-line marker to UNIX
+unix = mime.eol(0, dos, "\n")
+
+
+
+
+
+print((mime.qp("maçã")))
+--> ma=E7=E3=
+
+
+
+
+
+print((mime.unb64("ZGllZ286cGFzc3dvcmQ=")))
+--> diego:password
+
+
+
+
+
+print((mime.qp("ma=E7=E3=")))
+--> maçã
+
+
+
+
+Reference
-DNS services (socket.dns)
+DNS (in socket)
toip,
tohostname,
@@ -47,31 +47,17 @@
-FTP (socket.ftp)
+FTP
-
-
-
get,
-put,
-open.
+put
-Global symbols
-
-
-
-LUASOCKET_LIBNAME,
-mime,
-ltn12,
-socket.
-
-
-HTTP (socket.http)
+HTTP
get,
post,
@@ -82,46 +68,45 @@
-LTN012 (ltn12)
+LTN12
filter:
-chain,
-cycle.
+chain,
+cycle.
pump:
-all,
-step.
+all,
+step.
sink:
-chain,
-error,
-file,
-null,
-simplify,
-table.
+chain,
+error,
+file,
+null,
+simplify,
+table.
source:
-cat,
-chain,
-empty,
-file,
-simplify,
-rewind,
-string.
+cat,
+chain,
+empty,
+error,
+file,
+simplify,
+string.
-MIME (mime)
+MIME
high-level:
normalize,
-chain,
decode,
encode,
wrap.
@@ -141,9 +126,8 @@
-SMTP (socket.smtp)
+SMTP
-open,
message,
send.
@@ -152,26 +136,20 @@
-The socket namespace (socket)
+Socket
@@ -179,7 +157,7 @@
-bind,
-connect,
-debug,
+DEBUG,
dns,
-ftp,
-http,
protect,
select,
sink,
source,
sleep,
-smtp,
time,
tcp,
try,
udp,
-url,
-version.
+VERSION.
-TCP (socket.tcp)
+TCP (in socket)
accept,
bind,
@@ -198,7 +176,7 @@
-UDP (socket.udp)
+UDP (in socket)
close,
getpeername,
@@ -210,23 +188,22 @@
setpeername,
setsockname,
setoption,
-settimeout,
-shutdown.
+settimeout.
-URL (socket.url)
+URL
diff --git a/doc/smtp.html b/doc/smtp.html
index 0862de0..b0ae634 100644
--- a/doc/smtp.html
+++ b/doc/smtp.html
@@ -35,15 +35,22 @@
absolute,
build,
build_path,
-quote,
+escape,
parse,
parse_path,
-unquote.
+unescape.
SMTP
-
+
+
+
+
+
+
from = string,
rcpt = string or string-table,
- body = string,
- headers = headers-table,
- server = string
+ source = LTN12 source,
+ [server = string],
+ [port = string]
+ [domain = string],
+ [step = LTN12 pump step],
}
+
@@ -108,6 +142,13 @@ If successful, the function returns 1. Otherwise, the function returns nil followed by an error message.
++Note: SMTP servers are can be very picky with the format of e-mail +addresses. To be safe, use only addresses of the form +"<fulano@tecgraf.puc-rio.br>" in the from and +rcpt arguments to the send function. In headers, e-mail +addresses can take whatever form you like.
+Big note: There is a good deal of misconception with the use of the destination address field headers, i.e., the 'To', 'Cc', @@ -117,11 +158,12 @@ exact opposite of what you expect.
-Only recipients specified in the recipient list will receive a copy of the +Only recipients specified in the rcpt list will receive a copy of the message. Each recipient of an SMTP mail message receives a copy of the -message body along with the headers, and nothing more. The headers are -considered as part of the message. The list of recipients is not -part of the message. +message body along with the headers, and nothing more. The headers +are part of the message and should be produced by the LTN12 +source function. The rcpt list is not +part of the message and will not be sent to anyone.
@@ -143,9 +185,9 @@ Copy") contains addresses of recipients of the message whose addresses are not t
-The LuaSocket mail function does not interpret the headers you -pass to, but it gives you full control over what is sent and to whom -it is sent: +The LuaSocket send function does not care or interpret the +headers you send, but it gives you full control over what is sent and +to whom it is sent:
+-- load the smtp support +local smtp = require("smtp") + -- Connects to server "localhost" and sends a message to users -- "fulano@tecgraf.puc-rio.br", "beltrano@tecgraf.puc-rio.br", -- and "sicrano@tecgraf.puc-rio.br". -- Note that "fulano" is the primary recipient, "beltrano" receives a -- carbon copy and neither of them knows that "sicrano" received a blind -- carbon copy of the message. -headers = { - to = "fulano@tecgraf.puc-rio.br", - cc = "beltrano@tecgraf.puc-rio.br", - subject = "LuaSocket test message" -} - -from = "luasocket@tecgraf.puc-rio.br" +from = "<luasocket@tecgraf.puc-rio.br>" rcpt = { - "fulano@tecgraf.puc-rio.br", - "beltrano@tecgraf.puc-rio.br", - "sicrano@tecgraf.puc-rio.br" + "<fulano@tecgraf.puc-rio.br>", + "<beltrano@tecgraf.puc-rio.br>", + "<sicrano@tecgraf.puc-rio.br>" } -body = "This is a test message. Please ignore." +mesgt = { + headers = { + to = "Fulano da Silva <fulano@tecgraf.puc-rio.br>", + cc = '"Beltrano F. Nunes" <beltrano@tecgraf.puc-rio.br>', + subject = "My first message" + } + body = "I hope this works. If it does, I can send you another 1000 copies." +} -server = "localhost" - -r, e = socket.smtp.mail{ +r, e = smtp.send{ from = from, rcpt = rcpt, - headers = headers, - body = body, - server = server + source = smtp.message(mesgt) +} ++ + + +
+smtp.message(mesgt) +
+ ++Returns a LTN12 source that sends an SMTP message body, possibly multipart +(arbitrarily deep). +
+ ++The only parameter of the function is a table describing the message. +Mesgt has the following form (notice the recursive structure): +
+ +++ ++
++ +mesgt = {
+ headers = header-table,
+ body = LTN12 source or string or +multipart-mesgt
+}
+
+multipart-mesgt = {
+ preamble = string
+ [1] = mesgt,
+ [2] = mesgt,
+ ...
+ [n] = mesgt,
+ epilogue = string,
+}
+
+For a simple message, all that is needed is a set of headers +and the body. The message body can be given as a string +or as a LTN12 source. For multipart messages, the body is a table that +recursively defines each part as an independent message, plus a preamble +and an epilogue. +
+ ++The function returns an LTN12 source that produces the message contents as +defined by mesgt. Hopefuly, the following example will make +things clear. When in doubt, refer to the appropriate RFC as listed in the +introduction.
+ ++-- load the smtp support and its friends +local smtp = require("smtp") +local mime = require("mime") +local ltn12 = require("ltn12") + +-- creates a source to send a message with two parts. The first part is +-- plain text, the second part is a PNG image, encoded as base64. +source = smtp.message{ + headers = { + -- Remember that headers are *ignored* by smtp.send. + from = "Sicrano de Oliveira <sicrano@tecgraf.puc-rio.br>", + to = "Fulano da Silva <fulano@tecgraf.puc-rio.br>", + subject = "Here is a message with attachments" + }, + body = { + preamble = "If your client doesn't understand attachments, \r\n" .. + "it will still display the preamble and the epilogue.\r\n", + "Preamble might show up even in a MIME enabled client.", + -- first part: no headers means plain text, us-ascii. + -- The mime.eol low-level filter normalizes end-of-line markers. + [1] = { + body = mime.eol(0, [[ + Lines in a message body should always end with CRLF. + The smtp module will *NOT* perform translation. It will + perform necessary stuffing or '.' characters, though. + ]]) + }, + -- second part: headers describe content to be a png image, + -- sent under the base64 transfer content encoding. + -- notice that nothing happens until the message is actually sent. + -- small chunks are loaded into memory right before transmission and + -- translation happens on the fly. + [2] = { + headers = { + ["content-type"] = 'image/png; name="image.png"', + ["content-disposition"] = 'attachment; filename="image.png"', + ["content-description"] = 'a beautiful image', + ["content-transfer-encoding"] = "BASE64" + }, + body = ltn12.source.chain( + ltn12.source.file(io.open("image.png", "rb")), + ltn12.filter.chain( + mime.encode("base64"), + mime.wrap() + ) + ) + }, + epilogue = "This might also show up, but after the attachments" + } +} + +-- finally send it +r, e = smtp.send{ + from = "<sicrano@tecgraf.puc-rio.br>", + rcpt = "<fulano@tecgraf.puc-rio.br>", + source = source, }diff --git a/doc/socket.html b/doc/socket.html index bde882b..eccc676 100644 --- a/doc/socket.html +++ b/doc/socket.html @@ -36,16 +36,13 @@
-The socket namespace contains the namespace tables for all -LuaSocket modules as well as function that didn't belong in any specific -module, functions that are so commonly used that deserve a shortcut and a -few constants. +The socket namespace contains the core functionality of LuaSocket.
-socket.debug +socket.DEBUG
@@ -57,7 +54,7 @@ with debug support.
-socket.protect(function) +socket.protect(func)
@@ -65,12 +62,12 @@ Converts a function that throws exceptions into a safe function.
-Function is a function that calls +Funct is a function that calls try to throw exceptions.
-The function an equivalent function that instead of throwing exceptoins, +Returns an equivalent function that instead of throwing exceptions, returns nil followed by an error message.
@@ -103,16 +100,16 @@ simplify the test if a specific socket has changed status.-Important Note: a known bug in WinSock causes select to fail +Important note: a known bug in WinSock causes select to fail on non-blocking TCP sockets. The function may return a socket as writable even though the socket is not ready for sending.
-Important note: calling select with a server socket in the receive +Another important note: calling select with a server socket in the receive parameter before a call to accept does not guarantee accept will return immediately. -Use the timeout +Use the settimeout method or accept might block forever.
@@ -131,7 +128,7 @@ socket.sink(mode, socket)Creates an -LTN012 +LTN12 sink from a stream socket object.
@@ -163,7 +160,7 @@ socket.source(mode, socket [, length])Creates an -LTN012 +LTN12 source from a stream socket object.
@@ -217,7 +214,7 @@ c = socket.try(socket.connect("localhost", 80))-socket.version +socket.VERSION
diff --git a/doc/tcp.html b/doc/tcp.html index 34d6c6e..7a49660 100644 --- a/doc/tcp.html +++ b/doc/tcp.html @@ -241,18 +241,16 @@ method returns nil followed by an error message.
-client:receive([pattern1, pattern2, -... patternN]) +client:receive([pattern])
Reads data from a client object, according to the specified read -patterns. Patterns follow the Lua file I/O format, and the difference in performance between all patterns is negligible. +pattern. Patterns follow the Lua file I/O format, and the difference in performance between all patterns is negligible.
-The parameters pattern1, pattern2, ... -patternN can be any of the following: +Pattern can be any of the following:
-The method returns one value for each pattern, followed by a single -error code that can be nil in case of success, the string -'closed' in case the connection was closed before the -transmission was completed or the string 'timeout' in case -there was a timeout during the operation. +If successful, the method returns the received pattern. In case of error, +the method returns nil followed by an error message which +can be the string 'closed' in case the connection was +closed before the transmission was completed or the string +'timeout' in case there was a timeout during the operation. +Also, after the error message, the function returns the partial result of +the transmission.
-Note: In case of error, the method always return everything it managed -to download before the error condition was met. +Important note: This function was changed severely. It used +to support multiple patterns (but I have never seen this feature used) and +partial results used to be returned in the same way as successful results. +This last feature violated the idea that all functions should return +nil on error. Thus the change.
@@ -428,7 +431,7 @@ client:shutdown(mode)-Shuts down part of a full duplex connection. +Shuts down part of a full-duplex connection.
diff --git a/doc/udp.html b/doc/udp.html index 9f5cf8f..5ae0a89 100644 --- a/doc/udp.html +++ b/doc/udp.html @@ -29,6 +29,11 @@
diff --git a/doc/url.html b/doc/url.html index f3a7cb7..cd699a2 100644 --- a/doc/url.html +++ b/doc/url.html @@ -59,7 +59,7 @@ An URL is defined by the following grammar:
-socket.url.absolute(base, relative) +url.absolute(base, relative)
@@ -79,7 +79,7 @@ The function returns a string with the absolute URL. Note: The rules that govern the composition are fairly complex, and are described in detail in RFC 2396. -The example bellow should give an idea of what are the rules. +The example bellow should give an idea of what the rules are.
@@ -114,7 +114,7 @@ g;x?y#s = http://a/b/c/g;x?y#s-socket.url.build(parsed_url) +url.build(parsed_url)
@@ -135,7 +135,7 @@ The function returns a string with the built URL.
-socket.url.build_path(segments, unsafe) +url.build_path(segments, unsafe)
@@ -157,10 +157,39 @@ The function returns a string with the built <path> component.
+ + ++url.escape(content) +
+ ++Applies the URL escaping content coding to a string +Each byte is encoded as a percent character followed +by the two byte hexadecimal representation of its integer +value. +
+ ++Content is the string to be encoded. +
+ ++The function returns the encoded string. +
+ ++-- load url module +url = require("url") + +code = url.escape("/#?;") +-- code = "%2f%23%3f%3b" ++-socket.url.parse(url, default) +url.parse(url, default)
@@ -196,7 +225,10 @@ parsed_url = {
-parsed_url = socket.url.parse("http://www.puc-rio.br/~diego/index.lua?a=2#there") +-- load url module +url = require("url") + +parsed_url = url.parse("http://www.puc-rio.br/~diego/index.lua?a=2#there") -- parsed_url = { -- scheme = "http", -- authority = "www.puc-rio.br", @@ -206,7 +238,7 @@ parsed_url = socket.url.parse("http://www.puc-rio.br/~diego/index.lua?a=2#there" -- host = "www.puc-rio.br", -- } -parsed_url = socket.url.parse("ftp://root:passwd@unsafe.org/pub/virus.exe;type=i") +parsed_url = url.parse("ftp://root:passwd@unsafe.org/pub/virus.exe;type=i") -- parsed_url = { -- scheme = "ftp", -- authority = "root:passwd@unsafe.org", @@ -222,7 +254,7 @@ parsed_url = socket.url.parse("ftp://root:passwd@unsafe.org/pub/virus.exe;type=i-socket.url.parse_path(path) +url.parse_path(path)
@@ -241,36 +273,10 @@ returning a list with all the parsed segments, the function unescapes all of them.
- - --socket.url.escape(content) -
- --Applies the URL escaping content coding to a string -Each byte is encoded as a percent character followed -by the two byte hexadecimal representation of its integer -value. -
- --Content is the string to be encoded. -
- --The function returns the encoded string. -
- --code = socket.url.escape("/#?;") --- code = "%2f%23%3f%3b" ---socket.url.unescape(content) +url.unescape(content)
diff --git a/etc/check-memory.lua b/etc/check-memory.lua new file mode 100644 index 0000000..fdc6b9b --- /dev/null +++ b/etc/check-memory.lua @@ -0,0 +1,17 @@ +function load(s) + collectgarbage() + local a = gcinfo() + _G[s] = require(s) + collectgarbage() + local b = gcinfo() + print(s .. ":\t " .. (b-a) .. "k") +end + +load("socket") +load("url") +load("ltn12") +load("mime") +load("tp") +load("smtp") +load("http") +load("ftp") diff --git a/etc/get.lua b/etc/get.lua index c1e0542..0603ce5 100644 --- a/etc/get.lua +++ b/etc/get.lua @@ -8,6 +8,7 @@ socket = require("socket") http = require("http") ftp = require("ftp") url = require("url") +ltn12 = require("ltn12") -- formats a number of seconds into human readable form function nicetime(s) diff --git a/samples/tinyirc.lua b/samples/tinyirc.lua index eac979d..85ebe29 100644 --- a/samples/tinyirc.lua +++ b/samples/tinyirc.lua @@ -4,7 +4,7 @@ -- Author: Diego Nehab -- RCS ID: $Id$ ----------------------------------------------------------------------------- -require("socket") +socket = require("socket") host = host or "*" port1 = port1 or 8080 port2 = port2 or 8181 diff --git a/src/auxiliar.c b/src/auxiliar.c index b1f9203..9a37e10 100644 --- a/src/auxiliar.c +++ b/src/auxiliar.c @@ -7,7 +7,6 @@ #include
#include -#include "luasocket.h" #include "auxiliar.h" /*=========================================================================*\ @@ -16,16 +15,15 @@ /*-------------------------------------------------------------------------*\ * Initializes the module \*-------------------------------------------------------------------------*/ -int aux_open(lua_State *L) -{ +int aux_open(lua_State *L) { return 0; } /*-------------------------------------------------------------------------*\ * Creates a new class with given methods +* Methods whose names start with __ are passed directly to the metatable. \*-------------------------------------------------------------------------*/ -void aux_newclass(lua_State *L, const char *classname, luaL_reg *func) -{ +void aux_newclass(lua_State *L, const char *classname, luaL_reg *func) { luaL_newmetatable(L, classname); /* mt */ /* create __index table to place methods */ lua_pushstring(L, "__index"); /* mt,"__index" */ @@ -45,11 +43,31 @@ void aux_newclass(lua_State *L, const char *classname, luaL_reg *func) lua_pop(L, 1); } +/*-------------------------------------------------------------------------*\ +* Prints the value of a class in a nice way +\*-------------------------------------------------------------------------*/ +int aux_tostring(lua_State *L) { + char buf[32]; + if (!lua_getmetatable(L, 1)) goto error; + lua_pushstring(L, "__index"); + lua_gettable(L, -2); + if (!lua_istable(L, -1)) goto error; + lua_pushstring(L, "class"); + lua_gettable(L, -2); + if (!lua_isstring(L, -1)) goto error; + sprintf(buf, "%p", lua_touserdata(L, 1)); + lua_pushfstring(L, "%s: %s", lua_tostring(L, -1), buf); + return 1; +error: + lua_pushstring(L, "invalid object passed to 'auxiliar.c:__tostring'"); + lua_error(L); + return 1; +} + /*-------------------------------------------------------------------------*\ * Insert class into group \*-------------------------------------------------------------------------*/ -void aux_add2group(lua_State *L, const char *classname, const char *groupname) -{ +void aux_add2group(lua_State *L, const char *classname, const char *groupname) { luaL_getmetatable(L, classname); lua_pushstring(L, groupname); lua_pushboolean(L, 1); @@ -60,8 +78,7 @@ void aux_add2group(lua_State *L, const char *classname, const char *groupname) /*-------------------------------------------------------------------------*\ * Make sure argument is a boolean \*-------------------------------------------------------------------------*/ -int aux_checkboolean(lua_State *L, int objidx) -{ +int aux_checkboolean(lua_State *L, int objidx) { if (!lua_isboolean(L, objidx)) luaL_typerror(L, objidx, lua_typename(L, LUA_TBOOLEAN)); return lua_toboolean(L, objidx); @@ -71,8 +88,7 @@ int aux_checkboolean(lua_State *L, int objidx) * Return userdata pointer if object belongs to a given class, abort with * error otherwise \*-------------------------------------------------------------------------*/ -void *aux_checkclass(lua_State *L, const char *classname, int objidx) -{ +void *aux_checkclass(lua_State *L, const char *classname, int objidx) { void *data = aux_getclassudata(L, classname, objidx); if (!data) { char msg[45]; @@ -86,8 +102,7 @@ void *aux_checkclass(lua_State *L, const char *classname, int objidx) * Return userdata pointer if object belongs to a given group, abort with * error otherwise \*-------------------------------------------------------------------------*/ -void *aux_checkgroup(lua_State *L, const char *groupname, int objidx) -{ +void *aux_checkgroup(lua_State *L, const char *groupname, int objidx) { void *data = aux_getgroupudata(L, groupname, objidx); if (!data) { char msg[45]; @@ -100,8 +115,7 @@ void *aux_checkgroup(lua_State *L, const char *groupname, int objidx) /*-------------------------------------------------------------------------*\ * Set object class \*-------------------------------------------------------------------------*/ -void aux_setclass(lua_State *L, const char *classname, int objidx) -{ +void aux_setclass(lua_State *L, const char *classname, int objidx) { luaL_getmetatable(L, classname); if (objidx < 0) objidx--; lua_setmetatable(L, objidx); @@ -111,8 +125,7 @@ void aux_setclass(lua_State *L, const char *classname, int objidx) * Get a userdata pointer if object belongs to a given group. Return NULL * otherwise \*-------------------------------------------------------------------------*/ -void *aux_getgroupudata(lua_State *L, const char *groupname, int objidx) -{ +void *aux_getgroupudata(lua_State *L, const char *groupname, int objidx) { if (!lua_getmetatable(L, objidx)) return NULL; lua_pushstring(L, groupname); @@ -130,7 +143,6 @@ void *aux_getgroupudata(lua_State *L, const char *groupname, int objidx) * Get a userdata pointer if object belongs to a given class. Return NULL * otherwise \*-------------------------------------------------------------------------*/ -void *aux_getclassudata(lua_State *L, const char *classname, int objidx) -{ +void *aux_getclassudata(lua_State *L, const char *classname, int objidx) { return luaL_checkudata(L, objidx, classname); } diff --git a/src/auxiliar.h b/src/auxiliar.h index bc45182..70f4704 100644 --- a/src/auxiliar.h +++ b/src/auxiliar.h @@ -2,26 +2,28 @@ #define AUX_H /*=========================================================================*\ * Auxiliar routines for class hierarchy manipulation -* LuaSocket toolkit +* LuaSocket toolkit (but completely independent of other LuaSocket modules) * * A LuaSocket class is a name associated with Lua metatables. A LuaSocket -* group is a name associated to a class. A class can belong to any number +* group is a name associated with a class. A class can belong to any number * of groups. This module provides the functionality to: * * - create new classes * - add classes to groups -* - set the class of object +* - set the class of objects * - check if an object belongs to a given class or group +* - get the userdata associated to objects +* - print objects in a pretty way * * LuaSocket class names follow the convention { }. Modules * can define any number of classes and groups. The module tcp.c, for * example, defines the classes tcp{master}, tcp{client} and tcp{server} and -* the groups tcp{client, server} and tcp{any}. Module functions can then -* perform type-checking on it's arguments by either class or group. +* the groups tcp{client,server} and tcp{any}. Module functions can then +* perform type-checking on their arguments by either class or group. * * LuaSocket metatables define the __index metamethod as being a table. This -* table has one field for each method supported by the class. In DEBUG -* mode, it also has one field with the class name. +* table has one field for each method supported by the class, and a field +* "class" with the class name. * * The mapping from class name to the corresponding metatable and the * reverse mapping are done using lauxlib. @@ -32,14 +34,6 @@ #include #include -/* min and max macros */ -#ifndef MIN -#define MIN(x, y) ((x) < (y) ? x : y) -#endif -#ifndef MAX -#define MAX(x, y) ((x) > (y) ? x : y) -#endif - int aux_open(lua_State *L); void aux_newclass(lua_State *L, const char *classname, luaL_reg *func); void aux_add2group(lua_State *L, const char *classname, const char *group); @@ -49,5 +43,6 @@ void *aux_checkgroup(lua_State *L, const char *groupname, int objidx); void *aux_getclassudata(lua_State *L, const char *groupname, int objidx); void *aux_getgroupudata(lua_State *L, const char *groupname, int objidx); int aux_checkboolean(lua_State *L, int objidx); +int aux_tostring(lua_State *L); #endif /* AUX_H */ diff --git a/src/buffer.c b/src/buffer.c index b771047..fd885a2 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -7,7 +7,6 @@ #include #include -#include "auxiliar.h" #include "buffer.h" /*=========================================================================*\ @@ -20,6 +19,14 @@ static int buf_get(p_buf buf, const char **data, size_t *count); static void buf_skip(p_buf buf, size_t count); static int sendraw(p_buf buf, const char *data, size_t count, size_t *sent); +/* min and max macros */ +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? x : y) +#endif +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? x : y) +#endif + /*=========================================================================*\ * Exported functions \*=========================================================================*/ diff --git a/src/except.c b/src/except.c new file mode 100644 index 0000000..c9eb20e --- /dev/null +++ b/src/except.c @@ -0,0 +1,52 @@ +#include +#include + +#include "except.h" + +static int global_try(lua_State *L); +static int global_protect(lua_State *L); +static int protected(lua_State *L); + +static luaL_reg func[] = { + {"try", global_try}, + {"protect", global_protect}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Exception handling: try method +\*-------------------------------------------------------------------------*/ +static int global_try(lua_State *L) { + if (lua_isnil(L, 1) || (lua_isboolean(L, 1) && !lua_toboolean(L, 1))) { + lua_settop(L, 2); + lua_error(L); + return 0; + } else return lua_gettop(L); +} + +/*-------------------------------------------------------------------------*\ +* Exception handling: protect factory +\*-------------------------------------------------------------------------*/ +static int protected(lua_State *L) { + lua_pushvalue(L, lua_upvalueindex(1)); + lua_insert(L, 1); + if (lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0) != 0) { + lua_pushnil(L); + lua_insert(L, 1); + return 2; + } else return lua_gettop(L); +} + +static int global_protect(lua_State *L) { + lua_insert(L, 1); + lua_pushcclosure(L, protected, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Init module +\*-------------------------------------------------------------------------*/ +int except_open(lua_State *L) { + luaL_openlib(L, NULL, func, 0); + return 0; +} diff --git a/src/except.h b/src/except.h new file mode 100644 index 0000000..2c57b27 --- /dev/null +++ b/src/except.h @@ -0,0 +1,35 @@ +#ifndef EXCEPT_H +#define EXCEPT_H +/*=========================================================================*\ +* Exception control +* LuaSocket toolkit (but completely independent from other modules) +* +* This provides support for simple exceptions in Lua. During the +* development of the HTTP/FTP/SMTP support, it became aparent that +* error checking was taking a substantial amount of the coding. These +* function greatly simplify the task of checking errors. +* +* The main idea is that functions should return nil as its first return +* value when it finds an error, and return an error message (or value) +* following nil. In case of success, as long as the first value is not nil, +* the other values don't matter. +* +* The idea is to nest function calls with the "try" function. This function +* checks the first value, and calls "error" on the second if the first is +* nil. Otherwise, it returns all values it received. +* +* The protect function returns a new function that behaves exactly like the +* function it receives, but the new function doesn't throw exceptions: it +* returns nil followed by the error message instead. +* +* With these two function, it's easy to write functions that throw +* exceptions on error, but that don't interrupt the user script. +* +* RCS ID: $Id$ +\*=========================================================================*/ + +#include + +int except_open(lua_State *L); + +#endif diff --git a/src/ftp.lua b/src/ftp.lua index 79772f8..c130d1a 100644 --- a/src/ftp.lua +++ b/src/ftp.lua @@ -7,7 +7,7 @@ ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- --- Load other required modules +-- Load required modules ----------------------------------------------------------------------------- local socket = require("socket") local ltn12 = require("ltn12") @@ -17,10 +17,7 @@ local tp = require("tp") ----------------------------------------------------------------------------- -- Setup namespace ----------------------------------------------------------------------------- -local ftp = {} --- make all module globals fall into namespace -setmetatable(ftp, { __index = _G }) -setfenv(1, ftp) +_LOADED["ftp"] = getfenv(1) ----------------------------------------------------------------------------- -- Program constants @@ -32,9 +29,7 @@ PORT = 21 -- this is the default anonymous password. used when no password is -- provided in url. should be changed to your e-mail. USER = "ftp" -EMAIL = "anonymous@anonymous.org" --- block size used in transfers -BLOCKSIZE = 2048 +PASSWORD = "anonymous@anonymous.org" ----------------------------------------------------------------------------- -- Low level FTP API @@ -42,7 +37,7 @@ BLOCKSIZE = 2048 local metat = { __index = {} } function open(server, port) - local tp = socket.try(socket.tp.connect(server, port or PORT)) + local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT)) return setmetatable({tp = tp}, metat) end @@ -51,14 +46,17 @@ local function port(portt) end local function pasv(pasvt) - return socket.connect(pasvt.ip, pasvt.port) + local data = socket.try(socket.tcp()) + socket.try(data:settimeout(TIMEOUT)) + socket.try(data:connect(pasvt.ip, pasvt.port)) + return data end function metat.__index:login(user, password) socket.try(self.tp:command("user", user or USER)) local code, reply = socket.try(self.tp:check{"2..", 331}) if code == 331 then - socket.try(self.tp:command("pass", password or EMAIL)) + socket.try(self.tp:command("pass", password or PASSWORD)) socket.try(self.tp:check("2..")) end return 1 @@ -104,6 +102,7 @@ function metat.__index:send(sendt) socket.try(self.pasvt or self.portt, "need port or pasv first") if self.pasvt then data = socket.try(pasv(self.pasvt)) end local argument = sendt.argument or string.gsub(sendt.path, "^/", "") + if argument == "" then argument = nil end local command = sendt.command or "stor" socket.try(self.tp:command(command, argument)) local code, reply = socket.try(self.tp:check{"2..", "1.."}) @@ -133,6 +132,7 @@ function metat.__index:receive(recvt) socket.try(self.pasvt or self.portt, "need port or pasv first") if self.pasvt then data = socket.try(pasv(self.pasvt)) end local argument = recvt.argument or string.gsub(recvt.path, "^/", "") + if argument == "" then argument = nil end local command = recvt.command or "retr" socket.try(self.tp:command(command, argument)) local code = socket.try(self.tp:check{"1..", "2.."}) @@ -182,14 +182,14 @@ end -- High level FTP API ----------------------------------------------------------------------------- local function tput(putt) - local ftp = socket.ftp.open(putt.host, putt.port) - ftp:greet() - ftp:login(putt.user, putt.password) - if putt.type then ftp:type(putt.type) end - ftp:pasv() - ftp:send(putt) - ftp:quit() - return ftp:close() + local con = ftp.open(putt.host, putt.port) + con:greet() + con:login(putt.user, putt.password) + if putt.type then con:type(putt.type) end + con:pasv() + con:send(putt) + con:quit() + return con:close() end local default = { @@ -198,15 +198,16 @@ local default = { } local function parse(u) - local putt = socket.try(url.parse(u, default)) - socket.try(putt.scheme == "ftp", "invalid scheme '" .. putt.scheme .. "'") - socket.try(putt.host, "invalid host") + local t = socket.try(url.parse(u, default)) + socket.try(t.scheme == "ftp", "invalid scheme '" .. t.scheme .. "'") + socket.try(t.host, "invalid host") local pat = "^type=(.)$" - if putt.params then - putt.type = socket.skip(2, string.find(putt.params, pat)) - socket.try(putt.type == "a" or putt.type == "i") + if t.params then + t.type = socket.skip(2, string.find(t.params, pat)) + socket.try(t.type == "a" or t.type == "i", + "invalid type '" .. t.type .. "'") end - return putt + return t end local function sput(u, body) @@ -221,17 +222,17 @@ put = socket.protect(function(putt, body) end) local function tget(gett) - local ftp = socket.ftp.open(gett.host, gett.port) - ftp:greet() - ftp:login(gett.user, gett.password) - if gett.type then ftp:type(gett.type) end - ftp:pasv() - ftp:receive(gett) - ftp:quit() - return ftp:close() + local con = ftp.open(gett.host, gett.port) + con:greet() + con:login(gett.user, gett.password) + if gett.type then con:type(gett.type) end + con:pasv() + con:receive(gett) + con:quit() + return con:close() end -local function sget(u, body) +local function sget(u) local gett = parse(u) local t = {} gett.sink = ltn12.sink.table(t) @@ -240,7 +241,7 @@ local function sget(u, body) end get = socket.protect(function(gett) - if type(gett) == "string" then return sget(gett, body) + if type(gett) == "string" then return sget(gett) else return tget(gett) end end) diff --git a/src/http.lua b/src/http.lua index ebe6b54..129b562 100644 --- a/src/http.lua +++ b/src/http.lua @@ -7,7 +7,7 @@ ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- --- Load other required modules +-- Load required modules ------------------------------------------------------------------------------- local socket = require("socket") local ltn12 = require("ltn12") @@ -17,42 +17,68 @@ local url = require("url") ----------------------------------------------------------------------------- -- Setup namespace ------------------------------------------------------------------------------- -http = {} --- make all module globals fall into namespace -setmetatable(http, { __index = _G }) -setfenv(1, http) +_LOADED["http"] = getfenv(1) ----------------------------------------------------------------------------- -- Program constants ----------------------------------------------------------------------------- -- connection timeout in seconds -TIMEOUT = 60 +TIMEOUT = 4 -- default port for document retrieval PORT = 80 -- user agent field sent in request -USERAGENT = socket.version +USERAGENT = socket.VERSION -- block size used in transfers BLOCKSIZE = 2048 ----------------------------------------------------------------------------- --- Function return value selectors +-- Low level HTTP API ----------------------------------------------------------------------------- -local function second(a, b) - return b +local metat = { __index = {} } + +function open(host, port) + local con = socket.try(socket.tcp()) + socket.try(con:settimeout(TIMEOUT)) + port = port or PORT + socket.try(con:connect(host, port)) + return setmetatable({ con = con }, metat) end -local function third(a, b, c) - return c +function metat.__index:sendrequestline(method, uri) + local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) + return socket.try(self.con:send(reqline)) end -local function receive_headers(reqt, respt, tmp) - local sock = tmp.sock +function metat.__index:sendheaders(headers) + for i, v in pairs(headers) do + socket.try(self.con:send(i .. ": " .. v .. "\r\n")) + end + -- mark end of request headers + socket.try(self.con:send("\r\n")) + return 1 +end + +function metat.__index:sendbody(headers, source, step) + source = source or ltn12.source.empty() + step = step or ltn12.pump.step + -- if we don't know the size in advance, send chunked and hope for the best + local mode + if headers["content-length"] then mode = "keep-open" + else mode = "http-chunked" end + return socket.try(ltn12.pump.all(source, socket.sink(mode, self.con), step)) +end + +function metat.__index:receivestatusline() + local status = socket.try(self.con:receive()) + local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) + return socket.try(tonumber(code), status) +end + +function metat.__index:receiveheaders() local line, name, value local headers = {} - -- store results - respt.headers = headers -- get first line - line = socket.try(sock:receive()) + line = socket.try(self.con:receive()) -- headers go until a blank line is found while line ~= "" do -- get field-name and value @@ -60,189 +86,137 @@ local function receive_headers(reqt, respt, tmp) socket.try(name and value, "malformed reponse headers") name = string.lower(name) -- get next line (value might be folded) - line = socket.try(sock:receive()) + line = socket.try(self.con:receive()) -- unfold any folded values while string.find(line, "^%s") do value = value .. line - line = socket.try(sock:receive()) + line = socket.try(self.con:receive()) end -- save pair in table if headers[name] then headers[name] = headers[name] .. ", " .. value else headers[name] = value end end + return headers end -local function receive_body(reqt, respt, tmp) - local sink = reqt.sink or ltn12.sink.null() - local step = reqt.step or ltn12.pump.step - local source - local te = respt.headers["transfer-encoding"] - if te and te ~= "identity" then - -- get by chunked transfer-coding of message body - source = socket.source("http-chunked", tmp.sock) - elseif tonumber(respt.headers["content-length"]) then - -- get by content-length - local length = tonumber(respt.headers["content-length"]) - source = socket.source("by-length", tmp.sock, length) - else - -- get it all until connection closes - source = socket.source(tmp.sock) - end - socket.try(ltn12.pump.all(source, sink, step)) +function metat.__index:receivebody(headers, sink, step) + sink = sink or ltn12.sink.null() + step = step or ltn12.pump.step + local length = tonumber(headers["content-length"]) + local TE = headers["transfer-encoding"] + local mode + if TE and TE ~= "identity" then mode = "http-chunked" + elseif tonumber(headers["content-length"]) then mode = "by-length" + else mode = "default" end + return socket.try(ltn12.pump.all(socket.source(mode, self.con, length), + sink, step)) end -local function send_headers(sock, headers) - -- send request headers - for i, v in pairs(headers) do - socket.try(sock:send(i .. ": " .. v .. "\r\n")) - end - -- mark end of request headers - socket.try(sock:send("\r\n")) +function metat.__index:close() + return self.con:close() end -local function should_receive_body(reqt, respt, tmp) - if reqt.method == "HEAD" then return nil end - if respt.code == 204 or respt.code == 304 then return nil end - if respt.code >= 100 and respt.code < 200 then return nil end - return 1 -end - -local function receive_status(reqt, respt, tmp) - local status = socket.try(tmp.sock:receive()) - local code = third(string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) - -- store results - respt.code, respt.status = tonumber(code), status -end - -local function request_uri(reqt, respt, tmp) - local u = tmp.parsed - if not reqt.proxy then - local parsed = tmp.parsed - u = { - path = parsed.path, - params = parsed.params, - query = parsed.query, - fragment = parsed.fragment +----------------------------------------------------------------------------- +-- High level HTTP API +----------------------------------------------------------------------------- +local function uri(reqt) + local u = reqt + if not reqt.proxy and not PROXY then + u = { + path = reqt.path, + params = reqt.params, + query = reqt.query, + fragment = reqt.fragment } end return url.build(u) end -local function send_request(reqt, respt, tmp) - local uri = request_uri(reqt, respt, tmp) - local headers = tmp.headers - local step = reqt.step or ltn12.pump.step - -- send request line - socket.try(tmp.sock:send((reqt.method or "GET") - .. " " .. uri .. " HTTP/1.1\r\n")) - if reqt.source and not headers["content-length"] then - headers["transfer-encoding"] = "chunked" - end - send_headers(tmp.sock, headers) - -- send request message body, if any - if not reqt.source then return end - if headers["content-length"] then - socket.try(ltn12.pump.all(reqt.source, - socket.sink(tmp.sock), step)) - else - socket.try(ltn12.pump.all(reqt.source, - socket.sink("http-chunked", tmp.sock), step)) - end -end - -local function open(reqt, respt, tmp) - local proxy = reqt.proxy or PROXY - local host, port - if proxy then - local pproxy = url.parse(proxy) - socket.try(pproxy.port and pproxy.host, "invalid proxy") - host, port = pproxy.host, pproxy.port - else - host, port = tmp.parsed.host, tmp.parsed.port - end - -- store results - tmp.sock = socket.try(socket.tcp()) - socket.try(tmp.sock:settimeout(reqt.timeout or TIMEOUT)) - socket.try(tmp.sock:connect(host, port)) -end - -local function adjust_headers(reqt, respt, tmp) +local function adjustheaders(headers, host) local lower = {} -- override with user values - for i,v in (reqt.headers or lower) do + for i,v in (headers or lower) do lower[string.lower(i)] = v end lower["user-agent"] = lower["user-agent"] or USERAGENT -- these cannot be overriden - lower["host"] = tmp.parsed.host - lower["connection"] = "close" - -- store results - tmp.headers = lower + lower["host"] = host + return lower end -local function parse_url(reqt, respt, tmp) +local function adjustrequest(reqt) -- parse url with default fields local parsed = url.parse(reqt.url, { host = "", - port = PORT, + port = PORT, path ="/", - scheme = "http" + scheme = "http" }) - -- scheme has to be http - socket.try(parsed.scheme == "http", - string.format("unknown scheme '%s'", parsed.scheme)) - -- explicit authentication info overrides that given by the URL - parsed.user = reqt.user or parsed.user - parsed.password = reqt.password or parsed.password - -- store results - tmp.parsed = parsed + -- explicit info in reqt overrides that given by the URL + for i,v in reqt do parsed[i] = v end + -- compute uri if user hasn't overriden + parsed.uri = parsed.uri or uri(parsed) + -- adjust headers in request + parsed.headers = adjustheaders(parsed.headers, parsed.host) + return parsed end --- forward declaration -local request_p +local function shouldredirect(reqt, respt) + return (reqt.redirect ~= false) and + (respt.code == 301 or respt.code == 302) and + (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") + and (not reqt.nredirects or reqt.nredirects < 5) +end -local function should_authorize(reqt, respt, tmp) +local function shouldauthorize(reqt, respt) -- if there has been an authorization attempt, it must have failed if reqt.headers and reqt.headers["authorization"] then return nil end -- if last attempt didn't fail due to lack of authentication, -- or we don't have authorization information, we can't retry - return respt.code == 401 and tmp.parsed.user and tmp.parsed.password + return respt.code == 401 and reqt.user and reqt.password end -local function clone(headers) - if not headers then return nil end - local copy = {} - for i,v in pairs(headers) do - copy[i] = v +local function shouldreceivebody(reqt, respt) + if reqt.method == "HEAD" then return nil end + local code = respt.code + if code == 204 or code == 304 then return nil end + if code >= 100 and code < 200 then return nil end + return 1 +end + +local requestp, authorizep, redirectp + +function requestp(reqt) + local reqt = adjustrequest(reqt) + local respt = {} + local con = open(reqt.host, reqt.port) + con:sendrequestline(reqt.method, reqt.uri) + con:sendheaders(reqt.headers) + con:sendbody(reqt.headers, reqt.source, reqt.step) + respt.code, respt.status = con:receivestatusline() + respt.headers = con:receiveheaders() + if shouldredirect(reqt, respt) then + con:close() + return redirectp(reqt, respt) + elseif shouldauthorize(reqt, respt) then + con:close() + return authorizep(reqt, respt) + elseif shouldreceivebody(reqt, respt) then + con:receivebody(respt.headers, reqt.sink, reqt.step) end - return copy + con:close() + return respt end -local function authorize(reqt, respt, tmp) - local headers = clone(reqt.headers) or {} - headers["authorization"] = "Basic " .. - (mime.b64(tmp.parsed.user .. ":" .. tmp.parsed.password)) - local autht = { - method = reqt.method, - url = reqt.url, - source = reqt.source, - sink = reqt.sink, - headers = headers, - timeout = reqt.timeout, - proxy = reqt.proxy, - } - request_p(autht, respt, tmp) +function authorizep(reqt, respt) + local auth = "Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password)) + reqt.headers["authorization"] = auth + return requestp(reqt) end -local function should_redirect(reqt, respt, tmp) - return (reqt.redirect ~= false) and - (respt.code == 301 or respt.code == 302) and - (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") - and (not tmp.nredirects or tmp.nredirects < 5) -end - -local function redirect(reqt, respt, tmp) - tmp.nredirects = (tmp.nredirects or 0) + 1 +function redirectp(reqt, respt) + -- we create a new table to get rid of anything we don't + -- absolutely need, including authentication info local redirt = { method = reqt.method, -- the RFC says the redirect URL has to be absolute, but some @@ -251,69 +225,38 @@ local function redirect(reqt, respt, tmp) source = reqt.source, sink = reqt.sink, headers = reqt.headers, - timeout = reqt.timeout, - proxy = reqt.proxy + proxy = reqt.proxy, + nredirects = (reqt.nredirects or 0) + 1 } - request_p(redirt, respt, tmp) + respt = requestp(redirt) -- we pass the location header as a clue we redirected if respt.headers then respt.headers.location = redirt.url end -end - -local function skip_continue(reqt, respt, tmp) - if respt.code == 100 then - receive_status(reqt, respt, tmp) - end -end - --- execute a request of through an exception -function request_p(reqt, respt, tmp) - parse_url(reqt, respt, tmp) - adjust_headers(reqt, respt, tmp) - open(reqt, respt, tmp) - send_request(reqt, respt, tmp) - receive_status(reqt, respt, tmp) - skip_continue(reqt, respt, tmp) - receive_headers(reqt, respt, tmp) - if should_redirect(reqt, respt, tmp) then - tmp.sock:close() - redirect(reqt, respt, tmp) - elseif should_authorize(reqt, respt, tmp) then - tmp.sock:close() - authorize(reqt, respt, tmp) - elseif should_receive_body(reqt, respt, tmp) then - receive_body(reqt, respt, tmp) - end -end - -function request(reqt) - local respt, tmp = {}, {} - local s, e = pcall(request_p, reqt, respt, tmp) - if not s then respt.error = e end - if tmp.sock then tmp.sock:close() end return respt end -function get(u) - local t = {} - respt = request { - url = u, - sink = ltn12.sink.table(t) - } - return (table.getn(t) > 0 or nil) and table.concat(t), respt.headers, - respt.code, respt.error -end +request = socket.protect(requestp) -function post(u, body) +get = socket.protect(function(u) local t = {} - respt = request { - url = u, - method = "POST", + local respt = requestp { + url = u, + sink = ltn12.sink.table(t) + } + return (table.getn(t) > 0 or nil) and table.concat(t), respt.headers, + respt.code +end) + +post = socket.protect(function(u, body) + local t = {} + local respt = requestp { + url = u, + method = "POST", source = ltn12.source.string(body), sink = ltn12.sink.table(t), - headers = { ["content-length"] = string.len(body) } + headers = { ["content-length"] = string.len(body) } } - return (table.getn(t) > 0 or nil) and table.concat(t), - respt.headers, respt.code, respt.error -end + return (table.getn(t) > 0 or nil) and table.concat(t), + respt.headers, respt.code +end) return http diff --git a/src/inet.c b/src/inet.c index 3a57441..62c67f1 100644 --- a/src/inet.c +++ b/src/inet.c @@ -10,7 +10,6 @@ #include #include -#include "luasocket.h" #include "inet.h" /*=========================================================================*\ diff --git a/src/ltn12.lua b/src/ltn12.lua index 41855f0..6228247 100644 --- a/src/ltn12.lua +++ b/src/ltn12.lua @@ -8,9 +8,8 @@ ----------------------------------------------------------------------------- -- Setup namespace ----------------------------------------------------------------------------- -local ltn12 = {} -setmetatable(ltn12, { __index = _G }) -setfenv(1, ltn12) +_LOADED["ltn12"] = getfenv(1) + filter = {} source = {} sink = {} @@ -19,10 +18,6 @@ pump = {} -- 2048 seems to be better in windows... BLOCKSIZE = 2048 -local function shift(a, b, c) - return b, c -end - ----------------------------------------------------------------------------- -- Filter stuff ----------------------------------------------------------------------------- @@ -53,7 +48,9 @@ local function chain2(f1, f2) end end) return function(chunk) - return shift(coroutine.resume(co, chunk)) + local ret, a, b = coroutine.resume(co, chunk) + if ret then return a, b + else return nil, a end end end @@ -149,7 +146,9 @@ function source.chain(src, f) end end) return function() - return shift(coroutine.resume(co)) + local ret, a, b = coroutine.resume(co) + if ret then return a, b + else return nil, a end end end @@ -166,7 +165,9 @@ function source.cat(...) end end) return function() - return shift(coroutine.resume(co)) + local ret, a, b = coroutine.resume(co) + if ret then return a, b + else return nil, a end end end diff --git a/src/luasocket.c b/src/luasocket.c index ca3a52c..2b0a1fa 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -26,7 +26,7 @@ #include "luasocket.h" #include "auxiliar.h" -#include "base.h" +#include "except.h" #include "timeout.h" #include "buffer.h" #include "inet.h" @@ -35,11 +35,18 @@ #include "select.h" /*-------------------------------------------------------------------------*\ -* Modules +* Internal function prototypes +\*-------------------------------------------------------------------------*/ +static int global_skip(lua_State *L); +static int global_unload(lua_State *L); +static int base_open(lua_State *L); + +/*-------------------------------------------------------------------------*\ +* Modules and functions \*-------------------------------------------------------------------------*/ static const luaL_reg mod[] = { {"auxiliar", aux_open}, - {"base", base_open}, + {"except", except_open}, {"timeout", tm_open}, {"buffer", buf_open}, {"inet", inet_open}, @@ -49,11 +56,69 @@ static const luaL_reg mod[] = { {NULL, NULL} }; +static luaL_reg func[] = { + {"skip", global_skip}, + {"__unload", global_unload}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Skip a few arguments +\*-------------------------------------------------------------------------*/ +static int global_skip(lua_State *L) { + int amount = luaL_checkint(L, 1); + int ret = lua_gettop(L) - amount - 1; + return ret >= 0 ? ret : 0; +} + +/*-------------------------------------------------------------------------*\ +* Unloads the library +\*-------------------------------------------------------------------------*/ +static int global_unload(lua_State *L) { + sock_close(); + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Setup basic stuff. +\*-------------------------------------------------------------------------*/ +static int base_open(lua_State *L) { + if (sock_open()) { + /* whoever is loading the library replaced the global environment + * with the namespace table */ + lua_pushvalue(L, LUA_GLOBALSINDEX); + /* make sure library is still "requirable" if initialized staticaly */ + lua_pushstring(L, "_LOADEDLIB"); + lua_gettable(L, -2); + lua_pushstring(L, LUASOCKET_LIBNAME); + lua_pushcfunction(L, (lua_CFunction) luaopen_socket); + lua_settable(L, -3); + lua_pop(L, 1); +#ifdef LUASOCKET_DEBUG + lua_pushstring(L, "DEBUG"); + lua_pushboolean(L, 1); + lua_rawset(L, -3); +#endif + /* make version string available to scripts */ + lua_pushstring(L, "VERSION"); + lua_pushstring(L, LUASOCKET_VERSION); + lua_rawset(L, -3); + /* export other functions */ + luaL_openlib(L, NULL, func, 0); + return 1; + } else { + lua_pushstring(L, "unable to initialize library"); + lua_error(L); + return 0; + } +} + /*-------------------------------------------------------------------------*\ * Initializes all library modules. \*-------------------------------------------------------------------------*/ LUASOCKET_API int luaopen_socket(lua_State *L) { int i; + base_open(L); for (i = 0; mod[i].name; i++) mod[i].func(L); return 1; } diff --git a/src/luasocket.h b/src/luasocket.h index 716b7ff..6d30605 100644 --- a/src/luasocket.h +++ b/src/luasocket.h @@ -25,6 +25,7 @@ /*-------------------------------------------------------------------------*\ * Initializes the library. \*-------------------------------------------------------------------------*/ +#define LUASOCKET_LIBNAME "socket" LUASOCKET_API int luaopen_socket(lua_State *L); #endif /* LUASOCKET_H */ diff --git a/src/mime.c b/src/mime.c index f42528c..5750714 100644 --- a/src/mime.c +++ b/src/mime.c @@ -76,7 +76,17 @@ static UC b64unbase[256]; \*-------------------------------------------------------------------------*/ MIME_API int luaopen_mime(lua_State *L) { - lua_newtable(L); + /* whoever is loading the library replaced the global environment + * with the namespace table */ + lua_pushvalue(L, LUA_GLOBALSINDEX); + /* make sure library is still "requirable" if initialized staticaly */ + lua_pushstring(L, "_LOADEDLIB"); + lua_gettable(L, -2); + lua_pushstring(L, MIME_LIBNAME); + lua_pushcfunction(L, (lua_CFunction) luaopen_mime); + lua_settable(L, -3); + lua_pop(L, 1); + /* export functions */ luaL_openlib(L, NULL, func, 0); /* initialize lookup tables */ qpsetup(qpclass, qpunbase); diff --git a/src/mime.h b/src/mime.h index 6febedf..b82d61a 100644 --- a/src/mime.h +++ b/src/mime.h @@ -19,6 +19,7 @@ #define MIME_API extern #endif +#define MIME_LIBNAME "mime" MIME_API int luaopen_mime(lua_State *L); #endif /* MIME_H */ diff --git a/src/mime.lua b/src/mime.lua index ecf310d..000404f 100644 --- a/src/mime.lua +++ b/src/mime.lua @@ -5,24 +5,16 @@ -- RCS ID: $Id$ ----------------------------------------------------------------------------- ------------------------------------------------------------------------------ --- Load MIME from dynamic library --- Comment these lines if you are loading static ------------------------------------------------------------------------------ -local open = assert(loadlib("mime", "luaopen_mime")) -local mime = assert(open()) - ----------------------------------------------------------------------------- -- Load other required modules ----------------------------------------------------------------------------- +local mime = requirelib("mime", "luaopen_mime", getfenv(1)) local ltn12 = require("ltn12") ----------------------------------------------------------------------------- -- Setup namespace ----------------------------------------------------------------------------- --- make all module globals fall into mime namespace -setmetatable(mime, { __index = _G }) -setfenv(1, mime) +_LOADED["mime"] = mime -- encode, decode and wrap algorithm tables encodet = {} @@ -48,7 +40,7 @@ end encodet['quoted-printable'] = function(mode) return ltn12.filter.cycle(qp, "", - (mode == "binary") and "=0D=0A" or "\13\10") + (mode == "binary") and "=0D=0A" or "\r\n") end -- define the decoding filters diff --git a/src/select.c b/src/select.c index 1ebd82c..13f9d6e 100644 --- a/src/select.c +++ b/src/select.c @@ -9,26 +9,21 @@ #include #include -#include "luasocket.h" #include "socket.h" -#include "auxiliar.h" #include "select.h" /*=========================================================================*\ * Internal function prototypes. \*=========================================================================*/ -static int meth_set(lua_State *L); -static int meth_isset(lua_State *L); -static int c_select(lua_State *L); +static int getfd(lua_State *L); +static int dirty(lua_State *L); +static int collect_fd(lua_State *L, int tab, int max_fd, int itab, fd_set *set); +static int check_dirty(lua_State *L, int tab, int dtab, fd_set *set); +static void return_fd(lua_State *L, fd_set *set, int max_fd, + int itab, int tab, int start); +static void make_assoc(lua_State *L, int tab); static int global_select(lua_State *L); -/* fd_set object methods */ -static luaL_reg set[] = { - {"set", meth_set}, - {"isset", meth_isset}, - {NULL, NULL} -}; - /* functions in library namespace */ static luaL_reg func[] = { {"select", global_select}, @@ -36,22 +31,13 @@ static luaL_reg func[] = { }; /*=========================================================================*\ -* Internal function prototypes. +* Exported functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ -int select_open(lua_State *L) -{ - /* get select auxiliar lua function from lua code and register - * pass it as an upvalue to global_select */ -#ifdef LUASOCKET_COMPILED -#include "select.lch" -#else - lua_dofile(L, "select.lua"); -#endif - luaL_openlib(L, NULL, func, 1); - aux_newclass(L, "select{fd_set}", set); +int select_open(lua_State *L) { + luaL_openlib(L, NULL, func, 0); return 0; } @@ -61,64 +47,149 @@ int select_open(lua_State *L) /*-------------------------------------------------------------------------*\ * Waits for a set of sockets until a condition is met or timeout. \*-------------------------------------------------------------------------*/ -static int global_select(lua_State *L) -{ - fd_set *read_fd_set, *write_fd_set; - /* make sure we have enough arguments (nil is the default) */ +static int global_select(lua_State *L) { + int timeout, rtab, wtab, itab, max_fd, ret, ndirty; + fd_set rset, wset; + FD_ZERO(&rset); FD_ZERO(&wset); lua_settop(L, 3); - /* check timeout */ - if (!lua_isnil(L, 3) && !lua_isnumber(L, 3)) - luaL_argerror(L, 3, "number or nil expected"); - /* select auxiliar lua function to be called comes first */ - lua_pushvalue(L, lua_upvalueindex(1)); - lua_insert(L, 1); - /* pass fd_set objects */ - read_fd_set = (fd_set *) lua_newuserdata(L, sizeof(fd_set)); - FD_ZERO(read_fd_set); - aux_setclass(L, "select{fd_set}", -1); - write_fd_set = (fd_set *) lua_newuserdata(L, sizeof(fd_set)); - FD_ZERO(write_fd_set); - aux_setclass(L, "select{fd_set}", -1); - /* pass select auxiliar C function */ - lua_pushcfunction(L, c_select); - /* call select auxiliar lua function */ - lua_call(L, 6, 3); - return 3; -} - -/*=========================================================================*\ -* Lua methods -\*=========================================================================*/ -static int meth_set(lua_State *L) -{ - fd_set *set = (fd_set *) aux_checkclass(L, "select{fd_set}", 1); - t_sock fd = (t_sock) lua_tonumber(L, 2); - if (fd >= 0) FD_SET(fd, set); - return 0; -} - -static int meth_isset(lua_State *L) -{ - fd_set *set = (fd_set *) aux_checkclass(L, "select{fd_set}", 1); - t_sock fd = (t_sock) lua_tonumber(L, 2); - if (fd >= 0 && FD_ISSET(fd, set)) lua_pushnumber(L, 1); - else lua_pushnil(L); - return 1; + timeout = lua_isnil(L, 3) ? -1 : (int)(luaL_checknumber(L, 3) * 1000); + lua_newtable(L); itab = lua_gettop(L); + lua_newtable(L); rtab = lua_gettop(L); + lua_newtable(L); wtab = lua_gettop(L); + max_fd = collect_fd(L, 1, -1, itab, &rset); + ndirty = check_dirty(L, 1, rtab, &rset); + timeout = ndirty > 0? 0: timeout; + max_fd = collect_fd(L, 2, max_fd, itab, &wset); + ret = sock_select(max_fd+1, &rset, &wset, NULL, timeout); + if (ret > 0 || (ret == 0 && ndirty > 0)) { + return_fd(L, &rset, max_fd+1, itab, rtab, ndirty); + return_fd(L, &wset, max_fd+1, itab, wtab, 0); + make_assoc(L, rtab); + make_assoc(L, wtab); + return 2; + } else if (ret == 0) { + lua_pushstring(L, "timeout"); + return 3; + } else { + lua_pushnil(L); + lua_pushnil(L); + lua_pushstring(L, "error"); + return 3; + } } /*=========================================================================*\ * Internal functions \*=========================================================================*/ -static int c_select(lua_State *L) -{ - int max_fd = (int) lua_tonumber(L, 1); - fd_set *read_fd_set = (fd_set *) aux_checkclass(L, "select{fd_set}", 2); - fd_set *write_fd_set = (fd_set *) aux_checkclass(L, "select{fd_set}", 3); - int timeout = lua_isnil(L, 4) ? -1 : (int)(lua_tonumber(L, 4) * 1000); - struct timeval tv; - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; - lua_pushnumber(L, select(max_fd, read_fd_set, write_fd_set, NULL, - timeout < 0 ? NULL : &tv)); - return 1; +static int getfd(lua_State *L) { + int fd = -1; + lua_pushstring(L, "getfd"); + lua_gettable(L, -2); + if (!lua_isnil(L, -1)) { + lua_pushvalue(L, -2); + lua_call(L, 1, 1); + if (lua_isnumber(L, -1)) + fd = lua_tonumber(L, -1); + } + lua_pop(L, 1); + return fd; } + +static int dirty(lua_State *L) { + int is = 0; + lua_pushstring(L, "dirty"); + lua_gettable(L, -2); + if (!lua_isnil(L, -1)) { + lua_pushvalue(L, -2); + lua_call(L, 1, 1); + is = lua_toboolean(L, -1); + } + lua_pop(L, 1); + return is; +} + +static int collect_fd(lua_State *L, int tab, int max_fd, + int itab, fd_set *set) { + int i = 1; + if (lua_isnil(L, tab)) + return max_fd; + while (1) { + int fd; + lua_pushnumber(L, i); + lua_gettable(L, tab); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + break; + } + fd = getfd(L); + if (fd > 0) { + FD_SET(fd, set); + if (max_fd < fd) max_fd = fd; + lua_pushnumber(L, fd); + lua_pushvalue(L, -2); + lua_settable(L, itab); + } + lua_pop(L, 1); + i = i + 1; + } + return max_fd; +} + +static int check_dirty(lua_State *L, int tab, int dtab, fd_set *set) { + int ndirty = 0, i = 1; + if (lua_isnil(L, tab)) + return 0; + while (1) { + int fd; + lua_pushnumber(L, i); + lua_gettable(L, tab); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + break; + } + fd = getfd(L); + if (fd > 0 && dirty(L)) { + lua_pushnumber(L, ++ndirty); + lua_pushvalue(L, -2); + lua_settable(L, dtab); + FD_CLR(fd, set); + } + lua_pop(L, 1); + i = i + 1; + } + return ndirty; +} + +static void return_fd(lua_State *L, fd_set *set, int max_fd, + int itab, int tab, int start) { + int fd; + for (fd = 0; fd < max_fd; fd++) { + if (FD_ISSET(fd, set)) { + lua_pushnumber(L, ++start); + lua_pushnumber(L, fd); + lua_gettable(L, itab); + lua_settable(L, tab); + } + } +} + +static void make_assoc(lua_State *L, int tab) { + int i = 1, atab; + lua_newtable(L); atab = lua_gettop(L); + while (1) { + lua_pushnumber(L, i); + lua_gettable(L, tab); + if (!lua_isnil(L, -1)) { + lua_pushnumber(L, i); + lua_pushvalue(L, -2); + lua_settable(L, atab); + lua_pushnumber(L, i); + lua_settable(L, atab); + } else { + lua_pop(L, 1); + break; + } + i = i+1; + } +} + diff --git a/src/smtp.lua b/src/smtp.lua index 7ae99a5..dc80c35 100644 --- a/src/smtp.lua +++ b/src/smtp.lua @@ -7,15 +7,9 @@ ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- --- Load SMTP from dynamic library --- Comment these lines if you are loading static ------------------------------------------------------------------------------ -local open = assert(loadlib("smtp", "luaopen_smtp")) -local smtp = assert(open()) - ------------------------------------------------------------------------------ --- Load other required modules +-- Load required modules ----------------------------------------------------------------------------- +local smtp = requirelib("smtp") local socket = require("socket") local ltn12 = require("ltn12") local tp = require("tp") @@ -23,10 +17,10 @@ local tp = require("tp") ----------------------------------------------------------------------------- -- Setup namespace ----------------------------------------------------------------------------- --- make all module globals fall into smtp namespace -setmetatable(smtp, { __index = _G }) -setfenv(1, smtp) +_LOADED["smtp"] = smtp +-- timeout for connection +TIMEOUT = 60 -- default server used to send e-mails SERVER = "localhost" -- default port @@ -94,9 +88,7 @@ function metat.__index:send(mailt) end function open(server, port) - print(server or SERVER, port or PORT) - local tp, error = tp.connect(server or SERVER, port or PORT) - if not tp then return nil, error end + local tp = socket.try(tp.connect(server or SERVER, port or PORT, TIMEOUT)) return setmetatable({tp = tp}, metat) end @@ -121,7 +113,10 @@ local function send_multipart(mesgt) coroutine.yield('content-type: multipart/mixed; boundary="' .. bd .. '"\r\n\r\n') -- send preamble - if mesgt.body.preamble then coroutine.yield(mesgt.body.preamble) end + if mesgt.body.preamble then + coroutine.yield(mesgt.body.preamble) + coroutine.yield("\r\n") + end -- send each part separated by a boundary for i, m in ipairs(mesgt.body) do coroutine.yield("\r\n--" .. bd .. "\r\n") @@ -130,7 +125,10 @@ local function send_multipart(mesgt) -- send last boundary coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") -- send epilogue - if mesgt.body.epilogue then coroutine.yield(mesgt.body.epilogue) end + if mesgt.body.epilogue then + coroutine.yield(mesgt.body.epilogue) + coroutine.yield("\r\n") + end end -- yield message body from a source @@ -183,12 +181,12 @@ end -- set defaul headers local function adjust_headers(mesgt) local lower = {} - for i,v in (mesgt or lower) do + for i,v in (mesgt.headers or lower) do lower[string.lower(i)] = v end lower["date"] = lower["date"] or os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or ZONE) - lower["x-mailer"] = lower["x-mailer"] or socket.version + lower["x-mailer"] = lower["x-mailer"] or socket.VERSION -- this can't be overriden lower["mime-version"] = "1.0" mesgt.headers = lower @@ -198,18 +196,22 @@ function message(mesgt) adjust_headers(mesgt) -- create and return message source local co = coroutine.create(function() send_message(mesgt) end) - return function() return socket.skip(1, coroutine.resume(co)) end + return function() + local ret, a, b = coroutine.resume(co) + if ret then return a, b + else return nil, a end + end end --------------------------------------------------------------------------- -- High level SMTP API ----------------------------------------------------------------------------- send = socket.protect(function(mailt) - local smtp = socket.try(open(mailt.server, mailt.port)) - smtp:greet(mailt.domain) - smtp:send(mailt) - smtp:quit() - return smtp:close() + local con = open(mailt.server, mailt.port) + con:greet(mailt.domain) + con:send(mailt) + con:quit() + return con:close() end) return smtp diff --git a/src/socket.lua b/src/socket.lua index 418cd1b..9aa6437 100644 --- a/src/socket.lua +++ b/src/socket.lua @@ -7,8 +7,8 @@ ----------------------------------------------------------------------------- -- Load LuaSocket from dynamic library ----------------------------------------------------------------------------- -local open = assert(loadlib("luasocket", "luaopen_socket")) -local socket = assert(open()) +local socket = requirelib("luasocket", "luaopen_socket", getfenv(1)) +_LOADED["socket"] = socket ----------------------------------------------------------------------------- -- Auxiliar functions @@ -116,18 +116,21 @@ socket.sourcet["by-length"] = function(sock, length) end socket.sourcet["until-closed"] = function(sock) + local done return setmetatable({ getfd = function() return sock:getfd() end, dirty = function() return sock:dirty() end }, { - __call = ltn12.source.simplify(function() + __call = function() + if done then return nil end local chunk, err, partial = sock:receive(socket.BLOCKSIZE) if not err then return chunk elseif err == "closed" then sock:close() - return partial, ltn12.source.empty() + done = 1 + return partial else return nil, err end - end) + end }) end diff --git a/src/tcp.c b/src/tcp.c index 90cfcde..845e0a3 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -9,13 +9,10 @@ #include #include -#include "luasocket.h" - #include "auxiliar.h" #include "socket.h" #include "inet.h" #include "options.h" -#include "base.h" #include "tcp.h" /*=========================================================================*\ @@ -41,7 +38,7 @@ static int meth_dirty(lua_State *L); /* tcp object methods */ static luaL_reg tcp[] = { {"__gc", meth_close}, - {"__tostring", base_meth_tostring}, + {"__tostring", aux_tostring}, {"accept", meth_accept}, {"bind", meth_bind}, {"close", meth_close}, diff --git a/src/timeout.c b/src/timeout.c index bd6c3b4..4f9a315 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -26,6 +26,14 @@ #endif #endif +/* min and max macros */ +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? x : y) +#endif +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? x : y) +#endif + /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ diff --git a/src/tp.lua b/src/tp.lua index 3e9dba6..56dd8bc 100644 --- a/src/tp.lua +++ b/src/tp.lua @@ -2,24 +2,28 @@ -- Unified SMTP/FTP subsystem -- LuaSocket toolkit. -- Author: Diego Nehab --- Conforming to: RFC 2616, LTN7 -- RCS ID: $Id$ ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- --- Load other required modules +-- Load required modules ----------------------------------------------------------------------------- local socket = require("socket") +local ltn12 = require("ltn12") ----------------------------------------------------------------------------- -- Setup namespace ----------------------------------------------------------------------------- -tp = {} -setmetatable(tp, { __index = _G }) -setfenv(1, tp) +_LOADED["tp"] = getfenv(1) +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- TIMEOUT = 60 +----------------------------------------------------------------------------- +-- Implementation +----------------------------------------------------------------------------- -- gets server reply (works for SMTP and FTP) local function get_reply(control) local code, current, sep @@ -37,7 +41,6 @@ local function get_reply(control) -- reply ends with same code until code == current and sep == " " end -print(reply) return code, reply end @@ -46,6 +49,7 @@ local metat = { __index = {} } function metat.__index:check(ok) local code, reply = get_reply(self.control) +print(reply) if not code then return nil, reply end if type(ok) ~= "function" then if type(ok) == "table" then @@ -103,11 +107,11 @@ function metat.__index:close() end -- connect with server and return control object -function connect(host, port) - local control, err = socket.connect(host, port) - if not control then return nil, err end - control:settimeout(TIMEOUT) +connect = socket.protect(function(host, port, timeout) + local control = socket.try(socket.tcp()) + socket.try(control:settimeout(timeout or TIMEOUT)) + socket.try(control:connect(host, port)) return setmetatable({control = control}, metat) -end +end) return tp diff --git a/src/udp.c b/src/udp.c index 4770a2e..51d6402 100644 --- a/src/udp.c +++ b/src/udp.c @@ -9,13 +9,10 @@ #include #include -#include "luasocket.h" - #include "auxiliar.h" #include "socket.h" #include "inet.h" #include "options.h" -#include "base.h" #include "udp.h" /*=========================================================================*\ @@ -51,7 +48,7 @@ static luaL_reg udp[] = { {"close", meth_close}, {"setoption", meth_setoption}, {"__gc", meth_close}, - {"__tostring", base_meth_tostring}, + {"__tostring", aux_tostring}, {"getfd", meth_getfd}, {"setfd", meth_setfd}, {"dirty", meth_dirty}, diff --git a/src/url.lua b/src/url.lua index 2441268..aac2a47 100644 --- a/src/url.lua +++ b/src/url.lua @@ -9,9 +9,7 @@ ----------------------------------------------------------------------------- -- Setup namespace ----------------------------------------------------------------------------- -local url = {} -setmetatable(url, { __index = _G }) -setfenv(1, url) +_LOADED["url"] = getfenv(1) ----------------------------------------------------------------------------- -- Encodes a string into its escaped hexadecimal representation diff --git a/test/httptest.lua b/test/httptest.lua index a70aa87..61dc60a 100644 --- a/test/httptest.lua +++ b/test/httptest.lua @@ -4,14 +4,18 @@ -- to "/luasocket-test-cgi" and "/luasocket-test-cgi/" -- needs "AllowOverride AuthConfig" on /home/c/diego/tec/luasocket/test/auth -require("http") +socket = require("socket") +http = require("http") +mime = require("mime") +url = require("url") +ltn12 = require("ltn12") dofile("testsupport.lua") local host, proxy, request, response, index_file local ignore, expect, index, prefix, cgiprefix, index_crlf -socket.http.TIMEOUT = 10 +http.TIMEOUT = 10 local t = socket.time() @@ -56,7 +60,7 @@ local check_request = function(request, expect, ignore) end request.source = request.source or (request.body and ltn12.source.string(request.body)) - local response = socket.http.request(request) + local response = http.request(request) if t and table.getn(t) > 0 then response.body = table.concat(t) end check_result(response, expect, ignore) end @@ -64,16 +68,16 @@ end ------------------------------------------------------------------------ io.write("testing request uri correctness: ") local forth = cgiprefix .. "/request-uri?" .. "this+is+the+query+string" -local back, h, c, e = socket.http.get("http://" .. host .. forth) +local back, h, c, e = http.get("http://" .. host .. forth) if not back then fail(e) end -back = socket.url.parse(back) +back = url.parse(back) if similar(back.query, "this+is+the+query+string") then print("ok") else fail(back.query) end ------------------------------------------------------------------------ io.write("testing query string correctness: ") forth = "this+is+the+query+string" -back = socket.http.get("http://" .. host .. cgiprefix .. +back = http.get("http://" .. host .. cgiprefix .. "/query-string?" .. forth) if similar(back, forth) then print("ok") else fail("failed!") end @@ -149,7 +153,7 @@ check_request(request, expect, ignore) ------------------------------------------------------------------------ io.write("testing simple post function: ") -back = socket.http.post("http://" .. host .. cgiprefix .. "/cat", index) +back = http.post("http://" .. host .. cgiprefix .. "/cat", index) assert(back == index) ------------------------------------------------------------------------ @@ -277,30 +281,6 @@ ignore = { } check_request(request, expect, ignore) ------------------------------------------------------------------------- -io.write("testing host not found: ") -request = { - url = "http://wronghost/does/not/exist" -} -local c, e = socket.connect("wronghost", 80) -expect = { - error = e -} -ignore = {} -check_request(request, expect, ignore) - ------------------------------------------------------------------------- -io.write("testing invalid url: ") -request = { - url = host .. prefix -} -local c, e = socket.connect("", 80) -expect = { - error = e -} -ignore = {} -check_request(request, expect, ignore) - ------------------------------------------------------------------------ io.write("testing document not found: ") request = { @@ -396,37 +376,43 @@ ignore = { } check_request(request, expect, ignore) ------------------------------------------------------------------------- -io.write("testing wrong scheme: ") -request = { - url = "wrong://" .. host .. cgiprefix .. "/cat", - method = "GET" -} -expect = { - error = "unknown scheme 'wrong'" -} -ignore = { -} -check_request(request, expect, ignore) - ------------------------------------------------------------------------ local body io.write("testing simple get function: ") -body = socket.http.get("http://" .. host .. prefix .. "/index.html") +body = http.get("http://" .. host .. prefix .. "/index.html") assert(body == index) print("ok") ------------------------------------------------------------------------ io.write("testing HEAD method: ") -socket.http.TIMEOUT = 1 -response = socket.http.request { +http.TIMEOUT = 1 +response = http.request { method = "HEAD", url = "http://www.cs.princeton.edu/~diego/" } assert(response and response.headers) print("ok") +------------------------------------------------------------------------ +io.write("testing host not found: ") +local c, e = socket.connect("wronghost", 80) +local r, re = http.request{url = "http://wronghost/does/not/exist"} +assert(r == nil and e == re) +r, re = http.get("http://wronghost/does/not/exist") +assert(r == nil and e == re) +print("ok") + +------------------------------------------------------------------------ +io.write("testing invalid url: ") +local c, e = socket.connect("", 80) +local r, re = http.request{url = host .. prefix} +assert(r == nil and e == re) +r, re = http.get(host .. prefix) +assert(r == nil and e == re) +print("ok") + ------------------------------------------------------------------------ print("passed all tests") +os.remove("err") print(string.format("done in %.2fs", socket.time() - t)) diff --git a/test/testclnt.lua b/test/testclnt.lua index 1825007..14d3484 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -69,7 +69,7 @@ function check_timeout(tm, sl, elapsed, err, opp, mode, alldone) end end -if not socket.debug then +if not socket.DEBUG then fail("Please define LUASOCKET_DEBUG and recompile LuaSocket") end diff --git a/test/testmesg.lua b/test/testmesg.lua index 15b0eb7..449f4c2 100644 --- a/test/testmesg.lua +++ b/test/testmesg.lua @@ -1,52 +1,57 @@ -require("smtp") -require("mime") +-- load the smtp support and its friends +local smtp = require("smtp") +local mime = require("mime") +local ltn12 = require("ltn12") -mesgt = { - headers = { - to = "D Burgess ", - subject = "Looking good! (please check headers)" +-- creates a source to send a message with two parts. The first part is +-- plain text, the second part is a PNG image, encoded as base64. +source = smtp.message{ + headers = { + -- Remember that headers are *ignored* by smtp.send. + from = "Sicrano ", + to = "Fulano ", + subject = "Here is a message with attachments" + }, + body = { + preamble = "If your client doesn't understand attachments, \r\n" .. + "it will still display the preamble and the epilogue.\r\n", + "Preamble might show up even in a MIME enabled client.", + -- first part: No headers means plain text, us-ascii. + -- The mime.eol low-level filter normalizes end-of-line markers. + [1] = { + body = mime.eol(0, [[ + Lines in a message body should always end with CRLF. + The smtp module will *NOT* perform translation. It will + perform necessary stuffing, though. + ]]) }, - body = { - preamble = "Some attatched stuff", - [1] = { - body = mime.eol(0, "Testing stuffing.\n.\nGot you.\n.Hehehe.\n") - }, - [2] = { - headers = { - ["content-type"] = 'application/octet-stream; name="testmesg.lua"', - ["content-disposition"] = 'attachment; filename="testmesg.lua"', - ["content-transfer-encoding"] = "BASE64" - }, - body = ltn12.source.chain( - ltn12.source.file(io.open("testmesg.lua", "rb")), - ltn12.filter.chain( - mime.encode("base64"), - mime.wrap() - ) - ) - }, - [3] = { - headers = { - ["content-type"] = 'text/plain; name="testmesg.lua"', - ["content-disposition"] = 'attachment; filename="testmesg.lua"', - ["content-transfer-encoding"] = "QUOTED-PRINTABLE" - }, - body = ltn12.source.chain( - ltn12.source.file(io.open("testmesg.lua", "rb")), - ltn12.filter.chain( - mime.normalize(), - mime.encode("quoted-printable"), - mime.wrap("quoted-printable") - ) - ) - }, - epilogue = "Done attaching stuff", - } + -- second part: Headers describe content the to be an image, + -- sent under the base64 transfer content encoding. + -- Notice that nothing happens until the message is sent. Small + -- chunks are loaded into memory and translation happens on the fly. + [2] = { + headers = { + ["content-type"] = 'image/png; name="image.png"', + ["content-disposition"] = 'attachment; filename="image.png"', + ["content-description"] = 'a beautiful image', + ["content-transfer-encoding"] = "BASE64" + }, + body = ltn12.source.chain( + ltn12.source.file(io.open("image.png", "rb")), + ltn12.filter.chain( + mime.encode("base64"), + mime.wrap() + ) + ) + }, + epilogue = "This might also show up, but after the attachments" + } } -print(socket.smtp.send { +-- finally send it +r, e = smtp.send{ rcpt = " ", from = " ", - source = socket.smtp.message(mesgt), + source = source, server = "mail.cs.princeton.edu" -}) +} diff --git a/test/urltest.lua b/test/urltest.lua index 8c2ee88..92f2fae 100644 --- a/test/urltest.lua +++ b/test/urltest.lua @@ -1,4 +1,5 @@ -require"url" +socket = require("socket") +socket.url = require("url") dofile("testsupport.lua") local check_build_url = function(parsed)