diff --git a/TODO b/TODO index bc307d5..69cfc6b 100644 --- a/TODO +++ b/TODO @@ -2,6 +2,15 @@ ajeitar os README.* ajeitar select. upvalue nao tem nada a ver... make sure filter.chain fails gracefully. ajeitar o manual sobre select, mais liberal agora +falar sobre o novo esquema de namespace +tirar socket.url socket.ftp etc do manual. agora os namespaces estao +liberados. +ajeitar as referencias a RFCS e LTNS em todos os arquivos. +proxy no ftp +ajeitar < e-mail > no smtp? +ajeitar referencias a LTN12 nos manuais + +make sure sockets are closed when exceptions are raised falar sobre encodet/wrapt/decodet no manual sobre mime @@ -14,8 +23,6 @@ expose encode/decode tables to provide extensibility for mime module use coroutines instead of fancy filters check garbage collection in test*.lua -pop3??? - add socket.TIMEOUT to be default timeout? diff --git a/doc/ftp.html b/doc/ftp.html index 6776a17..a0c9268 100644 --- a/doc/ftp.html +++ b/doc/ftp.html @@ -35,9 +35,9 @@

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.

@@ -49,175 +49,191 @@ URLs MUST conform to
-[ftp://][<user>[:<password>]@]<host>[:<port>][/<path>][type=a|i|d] +[ftp://][<user>[:<password>]@]<host>[:<port>][/<path>][type=a|i]
+

+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)
-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],
}

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

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

-socket.ftp.get_cb{
-  url = string,
-  type = string,
-  content_cb = receive-callback,
-  user = string,
-  password = string
-} -

- -

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

+-- a function that returns a directory listing +function ls(u) + local t = {} + local p = url.parse(u) + p.command = "nlst" + p.sink = ltn12.sink.table(t) + local r, e = ftp.get(p) + return r and table.concat(t), e +end +

-socket.ftp.put(url, content)
-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],
}

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

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

-socket.ftp.put_cb{
-  url = string,
-  type = string,
-  content_cb = send-callback,
-  user = string,
-  password = string
-} -

- -

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

- -
--- 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 @@ + + + + +LuaSocket: Network support for the Lua language + + + + + + + +
+
+
+ + + +
+LuaSocket +
Network support for the Lua language +
+

+home · +download · +introduction · +reference +

+
+
+
+ + + +

LTN12

+ +

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. +

+ + + +

Filters

+ + + +

+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. +

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

+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. +

+ +
+-- load the ltn12 module
+local ltn12 = require("ltn12")
+
+-- the base64 mime filter factory
+encodet['base64'] = function()
+    return ltn12.filter.cycle(b64, "")
+end
+
+ + + +

Pumps

+ + + +

+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. +

+ + + +

Sinks

+ + + +

+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. +

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

+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. +

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

+ + + +

+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. +

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

+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. +

+ + + + + + + diff --git a/doc/mime.html b/doc/mime.html new file mode 100644 index 0000000..d2fcc3c --- /dev/null +++ b/doc/mime.html @@ -0,0 +1,428 @@ + + + + +LuaSocket: Network support for the Lua language + + + + + + + +
+
+
+ + + +
+LuaSocket +
Network support for the Lua language +
+

+home · +download · +introduction · +reference +

+
+
+
+ + + +

MIME

+ +

+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. +

+ + + +

High-level filters

+ + + +

+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")
+mime.decode("quoted-printable") +

+ +

+Returns a filter that decodes data from a given transfer content +encoding. +

+ +

+The function returns the created filter. +

+ + + +

+mime.encode("base64")
+mime.encode("quoted-printable" [, mode]) +

+ +

+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. +

+ +
+base64 = ltn12.filter.chain(
+  mime.encode("base64"),
+  mime.wrap("base64")
+)
+
+ +

+Note: Text data has to be converted to canonic form +before being encoded. +

+ +
+base64 = ltn12.filter.chain(
+  mime.normalize(),
+  mime.encode("base64"),
+  mime.wrap("base64")
+)
+
+ + + +

+mime.wrap("text" [, length])
+mime.wrap("base64")
+mime.wrap("quoted-printable") +

+ +

+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: +

+ +
+qp = ltn12.filter.chain(
+  mime.normalize(),
+  mime.encode("quoted-printable"),
+  mime.wrap("quoted-printable")
+)
+
+ +

+Note: To break into lines with a different end-of-line convention, apply +a normalization filter after the line break filter. +

+ + + +

Low-level filters

+ + + +

+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. +

+ +
+print((mime.b64("diego:password")))
+--> ZGllZ286cGFzc3dvcmQ=
+
+ + + +

+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. +

+ +
+-- translates the end-of-line marker to UNIX
+unix = mime.eol(0, dos, "\n") 
+
+ + + +

+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. +

+ +
+print((mime.qp("maçã")))
+--> ma=E7=E3=
+
+ + + +

+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. +

+ +
+print((mime.unb64("ZGllZ286cGFzc3dvcmQ=")))
+--> diego:password
+
+ + + +

+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. +

+ +
+print((mime.qp("ma=E7=E3=")))
+--> maçã
+
+ + + +

+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. +

+ + + + + + + + diff --git a/doc/reference.css b/doc/reference.css index 607ac4f..7c8148d 100644 --- a/doc/reference.css +++ b/doc/reference.css @@ -10,6 +10,9 @@ tt { h1, h2, h3, h4 { margin-left: 0em; } + +h3 { padding-top: 1em; } + p { margin-left: 1em; } p.name { diff --git a/doc/reference.html b/doc/reference.html index ba519c0..ebcfb5b 100644 --- a/doc/reference.html +++ b/doc/reference.html @@ -36,7 +36,7 @@

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
-bind, -connect, -debug, +DEBUG, dns, -ftp, -http, protect, select, sink, source, sleep, -smtp, time, tcp, try, udp, -url, -version. +VERSION.
@@ -179,7 +157,7 @@
-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
absolute, build, build_path, -quote, +escape, parse, parse_path, -unquote. +unescape.
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 @@

SMTP

-

-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{
+

    +
  • DOMAIN: domain used to greet the server; +
  • PORT: default port used for the connection; +
  • SERVER: default server used for the connection; +
  • TIMEOUT: default timeout for all I/O operations; +
  • ZONE: default time zone. +
+ + + +

+smtp.send{
  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],
}

-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: +

    +
  • server: Server to connect to. Defaults to "localhost"; +
  • port: Port to connect to. Defaults to 25; +
  • domain: Domain name used to greet the server; Defaults to the +local machine host name; +
  • step: LTN12 pump step function used to pass data from the +source to the server. Defaults to the LTN12 pump.step function. +

@@ -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:

  • If someone is to receive the message, the e-mail address has @@ -171,36 +213,147 @@ and

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

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

      @@ -267,16 +265,21 @@ of bytes from the socket.

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


    + + + +

    UDP

    +

    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)