aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDiego Nehab <diego@tecgraf.puc-rio.br>2004-05-25 05:27:44 +0000
committerDiego Nehab <diego@tecgraf.puc-rio.br>2004-05-25 05:27:44 +0000
commit888496aa821cd09d925046250ea98b1512293fd5 (patch)
tree217e2b532762a64b58c4fffcb2cba08ba2243462
parent4fc164b8eaa0453050a0a859321c327bb2c4f776 (diff)
downloadluasocket-888496aa821cd09d925046250ea98b1512293fd5.tar.gz
luasocket-888496aa821cd09d925046250ea98b1512293fd5.tar.bz2
luasocket-888496aa821cd09d925046250ea98b1512293fd5.zip
FTP low-level working.
SMTP connection oriented working. ltn12 improved.
-rw-r--r--TODO6
-rw-r--r--ltn012.wiki391
-rw-r--r--src/auxiliar.c38
-rw-r--r--src/ftp.lua557
-rw-r--r--src/http.lua14
-rw-r--r--src/ltn12.lua46
-rw-r--r--src/luasocket.c29
-rw-r--r--src/mime.c50
-rw-r--r--src/select.c25
-rw-r--r--src/smtp.lua90
-rw-r--r--src/tcp.c3
-rw-r--r--src/tp.lua76
-rw-r--r--src/udp.c2
-rw-r--r--test/ltn12test.lua8
-rw-r--r--test/testmesg.lua3
15 files changed, 690 insertions, 648 deletions
diff --git a/TODO b/TODO
index 1e30a78..ca5b445 100644
--- a/TODO
+++ b/TODO
@@ -18,13 +18,17 @@
18* Padronizar os retornos de funccao 18* Padronizar os retornos de funccao
19* Separar as classes em arquivos 19* Separar as classes em arquivos
20* Retorno de sendto em datagram sockets pode ser refused 20* Retorno de sendto em datagram sockets pode ser refused
21* select sets are now associative
22
23colocar pump.all, pump.step e pump.simplify.
24mudar ltn12.html e usar o exemplo source.cat que está muito melhor.
21 25
22break smtp.send into c = smtp.open, c:send() c:close() 26break smtp.send into c = smtp.open, c:send() c:close()
23 27
24falar sobre encodet/wrapt/decodet no manual sobre mime 28falar sobre encodet/wrapt/decodet no manual sobre mime
25 29
26 30
27RECEIVE MUDOU!!! COLOCAR NO MANUAL. 31RECEIVE MUDOU!!! (partial stuff) COLOCAR NO MANUAL.
28HTTP.lua mudou bastante também. 32HTTP.lua mudou bastante também.
29 33
30fazer com que a socket.source e socket.sink sejam "selectable". 34fazer com que a socket.source e socket.sink sejam "selectable".
diff --git a/ltn012.wiki b/ltn012.wiki
new file mode 100644
index 0000000..57561c5
--- /dev/null
+++ b/ltn012.wiki
@@ -0,0 +1,391 @@
1===Filters, sources and sinks: design, motivation and examples===
2==or Functional programming for the rest of us==
3by Diego Nehab
4
5{{{
6
7}}}
8
9===Abstract===
10Certain operations can be implemented in the form of filters. A filter is a function that processes data received in consecutive function calls, returning partial results chunk by chunk. Examples of operations that can be implemented as filters include the end-of-line normalization for text, Base64 and Quoted-Printable transfer content encodings, the breaking of text into lines, SMTP byte stuffing, and there are many others. Filters become even more powerful when we allow them to be chained together to create composite filters. Filters can be seen as middle nodes in a chain of data transformations. Sources an sinks are the corresponding end points of these chains. A source is a function that produces data, chunk by chunk, and a sink is a function that takes data, chunk by chunk. In this technical note, we define an elegant interface for filters, sources, sinks and chaining. We evolve our interface progressively, until we reach a high degree of generality. We discuss difficulties that arise during the implementation of this interface and we provide solutions and examples.
11
12===Introduction===
13
14Applications sometimes have too much information to process to fit in memory and are thus forced to process data in smaller parts. Even when there is enough memory, processing all the data atomically may take long enough to frustrate a user that wants to interact with the application. Furthermore, complex transformations can often be defined as series of simpler operations. Several different complex transformations might share the same simpler operations, so that an uniform interface to combine them is desirable. The following concepts constitute our solution to these problems.
15
16''Filters'' are functions that accept successive chunks of input, and produce successive chunks of output. Furthermore, the result of concatenating all the output data is the same as the result of applying the filter over the concatenation of the input data. As a consequence, boundaries are irrelevant: filters have to handle input data split arbitrarily by the user.
17
18A ''chain'' is a function that combines the effect of two (or more) other functions, but whose interface is indistinguishable from the interface of one of its components. Thus, a chained filter can be used wherever an atomic filter can be used. However, its effect on data is the combined effect of its component filters. Note that, as a consequence, chains can be chained themselves to create arbitrarily complex operations that can be used just like atomic operations.
19
20Filters can be seen as internal nodes in a network through which data flows, potentially being transformed along its way. Chains connect these nodes together. To complete the picture, we need ''sources'' and ''sinks'' as initial and final nodes of the network, respectively. Less abstractly, a source is a function that produces new data every time it is called. On the other hand, sinks are functions that give a final destination to the data they receive. Naturally, sources and sinks can be chained with filters.
21
22Finally, filters, chains, sources, and sinks are all passive entities: they need to be repeatedly called in order for something to happen. ''Pumps'' provide the driving force that pushes data through the network, from a source to a sink.
23
24 Hopefully, these concepts will become clear with examples. In the following sections, we start with simplified interfaces, which we improve several times until we can find no obvious shortcommings. The evolution we present is not contrived: it follows the steps we followed ourselves as we consolidated our understanding of these concepts.
25
26== A concrete example ==
27
28Some data transformations are easier to implement as filters than others. Examples of operations that can be implemented as filters include the end-of-line normalization for text, the Base64 and Quoted-Printable transfer content encodings, the breaking of text into lines, SMTP byte stuffing, and many others. Let's use the end-of-line normalization as an example to define our initial filter interface. We later discuss why the implementation might not be trivial.
29
30Assume we are given text in an unknown end-of-line convention (including possibly mixed conventions) out of the commonly found Unix (LF), Mac OS (CR), and DOS (CRLF) conventions. We would like to be able to write code like the following:
31 {{{
32input = source.chain(source.file(io.stdin), normalize("\r\n"))
33output = sink.file(io.stdout)
34pump(input, output)
35}}}
36
37This program should read data from the standard input stream and normalize the end-of-line markers to the canonic CRLF marker defined by the MIME standard, finally sending the results to the standard output stream. For that, we use a ''file source'' to produce data from standard input, and chain it with a filter that normalizes the data. The pump then repeatedly gets data from the source, and moves it to the ''file sink'' that sends it to standard output.
38
39To make the discussion even more concrete, we start by discussing the implementation of the normalization filter. The {{normalize}} ''factory'' is a function that creates such a filter. Our initial filter interface is as follows: the filter receives a chunk of input data, and returns a chunk of processed data. When there is no more input data, the user notifies the filter by invoking it with a {{nil}} chunk. The filter then returns the final chunk of processed data.
40
41Although the interface is extremely simple, the implementation doesn't seem so obvious. Any filter respecting this interface needs to keep some kind of context between calls. This is because chunks can be broken between the CR and LF characters marking the end of a line. This need for context storage is what motivates the use of factories: each time the factory is called, it returns a filter with its own context so that we can have several independent filters being used at the same time. For the normalization filter, we know that the obvious solution (i.e. concatenating all the input into the context before producing any output) is not good enough, so we will have to find another way.
42
43We will break the implementation in two parts: a low-level filter, and a factory of high-level filters. The low-level filter will be implemented in C and will not carry any context between function calls. The high-level filter factory, implemented in Lua, will create and return a high-level filter that keeps whatever context the low-level filter needs, but isolates the user from its internal details. That way, we take advantage of C's efficiency to perform the dirty work, and take advantage of Lua's simplicity for the bookkeeping.
44
45==The Lua part of the implementation==
46
47Below is the implementation of the factory of high-level end-of-line normalization filters:
48 {{{
49function filter.cycle(low, ctx, extra)
50 return function(chunk)
51 local ret
52 ret, ctx = low(ctx, chunk, extra)
53 return ret
54 end
55end
56
57function normalize(marker)
58 return cycle(eol, 0, marker)
59end
60}}}
61
62The {{normalize}} factory simply calls a more generic factory, the {{cycle}} factory. This factory receives a low-level filter, an initial context and some extra value and returns the corresponding high-level filter. Each time the high level filer is called with a new chunk, it calls the low-level filter passing the previous context, the new chunk and the extra argument. The low-level filter produces the chunk of processed data and a new context. Finally, the high-level filter updates its internal context and returns the processed chunk of data to the user. It is the low-level filter that does all the work. Notice that this implementation takes advantage of the Lua 5.0 lexical scoping rules to store the context locally, between function calls.
63
64Moving to the low-level filter, we notice there is no perfect solution to the end-of-line marker normalization problem itself. The difficulty comes from an inherent ambiguity on the definition of empty lines within mixed input. However, the following solution works well for any consistent input, as well as for non-empty lines in mixed input. It also does a reasonable job with empty lines and serves as a good example of how to implement a low-level filter.
65
66Here is what we do: CR and LF are considered candidates for line break. We issue ''one'' end-of-line line marker if one of the candidates is seen alone, or followed by a ''different'' candidate. That is, CR&nbsp;CR and LF&nbsp;LF issue two end of line markers each, but CR&nbsp;LF and LF&nbsp;CR issue only one marker. This idea takes care of Mac OS, Mac OS X, VMS and Unix, DOS and MIME, as well as probably other more obscure conventions.
67
68==The C part of the implementation==
69
70The low-level filter is divided into two simple functions. The inner function actually does the conversion. It takes each input character in turn, deciding what to output and how to modify the context. The context tells if the last character seen was a candidate and, if so, which candidate it was.
71 {{{
72#define candidate(c) (c == CR || c == LF)
73static int process(int c, int last, const char *marker, luaL_Buffer *buffer) {
74 if (candidate(c)) {
75 if (candidate(last)) {
76 if (c == last) luaL_addstring(buffer, marker);
77 return 0;
78 } else {
79 luaL_addstring(buffer, marker);
80 return c;
81 }
82 } else {
83 luaL_putchar(buffer, c);
84 return 0;
85 }
86}
87}}}
88
89The inner function makes use of Lua's auxiliary library's buffer interface for its efficiency and ease of use. The outer function simply interfaces with Lua. It receives the context and the input chunk (as well as an optional end-of-line marker), and returns the transformed output and the new context.
90 {{{
91static int eol(lua_State *L) {
92 int ctx = luaL_checkint(L, 1);
93 size_t isize = 0;
94 const char *input = luaL_optlstring(L, 2, NULL, &isize);
95 const char *last = input + isize;
96 const char *marker = luaL_optstring(L, 3, CRLF);
97 luaL_Buffer buffer;
98 luaL_buffinit(L, &amp;buffer);
99 if (!input) {
100 lua_pushnil(L);
101 lua_pushnumber(L, 0);
102 return 2;
103 }
104 while (input &lt; last)
105 ctx = process(*input++, ctx, marker, &amp;buffer);
106 luaL_pushresult(&amp;buffer);
107 lua_pushnumber(L, ctx);
108 return 2;
109}
110}}}
111
112Notice that if the input chunk is {{nil}}, the operation is considered to be finished. In that case, the loop will not execute a single time and the context is reset to the initial state. This allows the filter to be reused indefinitely. It is a good idea to write filters like this, when possible.
113
114Besides the end-of-line normalization filter shown above, many other filters can be implemented with the same ideas. Examples include Base64 and Quoted-Printable transfer content encodings, the breaking of text into lines, SMTP byte stuffing etc. The challenging part is to decide what will be the context. For line breaking, for instance, it could be the number of bytes left in the current line. For Base64 encoding, it could be the bytes that remain in the division of the input into 3-byte atoms.
115
116===Chaining===
117
118Filters become more powerful when the concept of chaining is introduced. Suppose you have a filter for Quoted-Printable encoding and you want to encode some text. According to the standard, the text has to be normalized into its canonic form prior to encoding. A nice interface that simplifies this task is a factory that creates a composite filter that passes data through multiple filters, but that can be used wherever a primitive filter is used.
119 {{{
120local function chain2(f1, f2)
121 return function(chunk)
122 local ret = f2(f1(chunk))
123 if chunk then return ret
124 else return ret .. f2() end
125 end
126end
127
128function filter.chain(...)
129 local f = arg[1]
130 for i = 2, table.getn(arg) do
131 f = chain2(f, arg[i])
132 end
133 return f
134end
135
136local chain = filter.chain(normalize("\r\n"), encode("quoted-printable"))
137while 1 do
138 local chunk = io.read(2048)
139 io.write(chain(chunk))
140 if not chunk then break end
141end
142}}}
143
144The chaining factory is very simple. All it does is return a function that passes data through all filters and returns the result to the user. It uses the simpler auxiliar function that knows how to chain two filters together. In the auxiliar function, special care must be taken if the chunk is final. This is because the final chunk notification has to be pushed through both filters in turn. Thanks to the chain factory, it is easy to perform the Quoted-Printable conversion, as the above example shows.
145
146===Sources, sinks, and pumps===
147
148As we noted in the introduction, the filters we introduced so far act as the internal nodes in a network of transformations. Information flows from node to node (or rather from one filter to the next) and is transformed on its way out. Chaining filters together is the way we found to connect nodes in the network. But what about the end nodes? In the beginning of the network, we need a node that provides the data, a source. In the end of the network, we need a node that takes in the data, a sink.
149
150==Sources==
151
152We start with two simple sources. The first is the {{empty}} source: It simply returns no data, possibly returning an error message. The second is the {{file}} source, which produces the contents of a file in a chunk by chunk fashion, closing the file handle when done.
153 {{{
154function source.empty(err)
155 return function()
156 return nil, err
157 end
158end
159
160function source.file(handle, io_err)
161 if handle then
162 return function()
163 local chunk = handle:read(2048)
164 if not chunk then handle:close() end
165 return chunk
166 end
167 else return source.empty(io_err or "unable to open file") end
168end
169}}}
170
171A source returns the next chunk of data each time it is called. When there is no more data, it just returns {{nil}}. If there is an error, the source can inform the caller by returning {{nil}} followed by an error message. Adrian Sietsma noticed that, although not on purpose, the interface for sources is compatible with the idea of iterators in Lua 5.0. That is, a data source can be nicely used in conjunction with {{for}} loops. Using our file source as an iterator, we can rewrite our first example:
172 {{{
173local process = normalize("\r\n")
174for chunk in source.file(io.stdin) do
175 io.write(process(chunk))
176end
177io.write(process(nil))
178}}}
179
180Notice that the last call to the filter obtains the last chunk of processed data. The loop terminates when the source returns {{nil}} and therefore we need that final call outside of the loop.
181
182==Mantaining state between calls==
183
184It is often the case that a source needs to change its behaviour after some event. One simple example would be a file source that wants to make sure it returns {{nil}} regardless of how many times it is called after the end of file, avoiding attempts to read past the end of the file. Another example would be a source that returns the contents of several files, as if they were concatenated, moving from one file to the next until the end of the last file is reached.
185
186One way to implement this kind of source is to have the factory declare extra state variables that the source can use via lexical scoping. Our file source could set the file handle itself to {{nil}} when it detects the end-of-file. Then, every time the source is called, it could check if the handle is still valid and act accordingly:
187 {{{
188function source.file(handle, io_err)
189 if handle then
190 return function()
191 if not handle then return nil end
192 local chunk = handle:read(2048)
193 if not chunk then
194 handle:close()
195 handle = nil
196 end
197 return chunk
198 end
199 else return source.empty(io_err or "unable to open file") end
200end
201}}}
202
203Another way to implement this behavior involves a change in the source interface to makes it more flexible. Let's allow a source to return a second value, besides the next chunk of data. If the returned chunk is {{nil}}, the extra return value tells us what happened. A second {{nil}} means that there is just no more data and the source is empty. Any other value is considered to be an error message. On the other hand, if the chunk was ''not'' {{nil}}, the second return value tells us whether the source wants to be replaced. If it is {{nil}}, we should proceed using the same source. Otherwise it has to be another source, which we have to use from then on, to get the remaining data.
204
205This extra freedom is good for someone writing a source function, but it is a pain for those that have to use it. Fortunately, given one of these ''fancy'' sources, we can transform it into a simple source that never needs to be replaced, using the following factory.
206 {{{
207function source.simplify(src)
208 return function()
209 local chunk, err_or_new = src()
210 src = err_or_new or src
211 if not chunk then return nil, err_or_new
212 else return chunk end
213 end
214end
215}}}
216
217The simplification factory allows us to write fancy sources and use them as if they were simple. Therefore, our next functions will only produce simple sources, and functions that take sources will assume they are simple.
218
219Going back to our file source, the extended interface allows for a more elegant implementation. The new source just asks to be replaced by an empty source as soon as there is no more data. There is no repeated checking of the handle. To make things simpler to the user, the factory itself simplifies the the fancy file source before returning it to the user:
220 {{{
221function source.file(handle, io_err)
222 if handle then
223 return source.simplify(function()
224 local chunk = handle:read(2048)
225 if not chunk then
226 handle:close()
227 return "", source.empty()
228 end
229 return chunk
230 end)
231 else return source.empty(io_err or "unable to open file") end
232end
233}}}
234
235We can make these ideas even more powerful if we use a new feature of Lua 5.0: coroutines. Coroutines suffer from a great lack of advertisement, and I am going to play my part here. Just like lexical scoping, coroutines taste odd at first, but once you get used with the concept, it can save your day. I have to admit that using coroutines to implement our file source would be overkill, so let's implement a concatenated source factory instead.
236 {{{
237function source.cat(...)
238 local co = coroutine.create(function()
239 local i = 1
240 while i <= table.getn(arg) do
241 local chunk, err = arg[i]()
242 if chunk then coroutine.yield(chunk)
243 elseif err then return nil, err
244 else i = i + 1 end
245 end
246 end)
247 return function()
248 return shift(coroutine.resume(co))
249 end
250end
251}}}
252
253The factory creates two functions. The first is an auxiliar that does all the work, in the form of a coroutine. It reads a chunk from one of the sources. If the chunk is {{nil}}, it moves to the next source, otherwise it just yields returning the chunk. When it is resumed, it continues from where it stopped and tries to read the next chunk. The second function is the source itself, and just resumes the execution of the auxiliar coroutine, returning to the user whatever chunks it returns (skipping the first result that tells us if the coroutine terminated). Imagine writing the same function without coroutines and you will notice the simplicity of this implementation. We will use coroutines again when we make the filter interface more powerful.
254
255==Chaining Sources==
256
257What does it mean to chain a source with a filter? The most useful interpretation is that the combined source-filter is a new source that produces data and passes it through the filter before returning it. Here is a factory that does it:
258 {{{
259function source.chain(src, f)
260 return source.simplify(function()
261 local chunk, err = src()
262 if not chunk then return f(nil), source.empty(err)
263 else return f(chunk) end
264 end)
265end
266}}}
267
268Our motivating example in the introduction chains a source with a filter. The idea of chaining a source with a filter is useful when one thinks about functions that might get their input data from a source. By chaining a simple source with one or more filters, the same function can be provided with filtered data even though it is unaware of the filtering that is happening behind its back.
269
270==Sinks==
271
272Just as we defined an interface for an initial source of data, we can also define an interface for a final destination of data. We call any function respecting that interface a ''sink''. Below are two simple factories that return sinks. The table factory creates a sink that stores all obtained data into a table. The data can later be efficiently concatenated into a single string with the {{table.concat}} library function. As another example, we introduce the {{null}} sink: A sink that simply discards the data it receives.
273 {{{
274function sink.table(t)
275 t = t or {}
276 local f = function(chunk, err)
277 if chunk then table.insert(t, chunk) end
278 return 1
279 end
280 return f, t
281end
282
283local function null()
284 return 1
285end
286
287function sink.null()
288 return null
289end
290}}}
291
292Sinks receive consecutive chunks of data, until the end of data is notified with a {{nil}} chunk. An error is notified by an extra argument giving an error message after the {{nil}} chunk. If a sink detects an error itself and wishes not to be called again, it should return {{nil}}, optionally followed by an error message. A return value that is not {{nil}} means the source will accept more data. Finally, just as sources can choose to be replaced, so can sinks, following the same interface. Once again, it is easy to implement a {{sink.simplify}} factory that transforms a fancy sink into a simple sink.
293
294As an example, let's create a source that reads from the standard input, then chain it with a filter that normalizes the end-of-line convention and let's use a sink to place all data into a table, printing the result in the end.
295 {{{
296local load = source.chain(source.file(io.stdin), normalize("\r\n"))
297local store, t = sink.table()
298while 1 do
299 local chunk = load()
300 store(chunk)
301 if not chunk then break end
302end
303print(table.concat(t))
304}}}
305
306Again, just as we created a factory that produces a chained source-filter from a source and a filter, it is easy to create a factory that produces a new sink given a sink and a filter. The new sink passes all data it receives through the filter before handing it in to the original sink. Here is the implementation:
307 {{{
308function sink.chain(f, snk)
309 return function(chunk, err)
310 local r, e = snk(f(chunk))
311 if not r then return nil, e end
312 if not chunk then return snk(nil, err) end
313 return 1
314 end
315end
316}}}
317
318==Pumps==
319
320There is a while loop that has been around for too long in our examples. It's always there because everything that we designed so far is passive. Sources, sinks, filters: None of them will do anything on their own. The operation of pumping all data a source can provide into a sink is so common that we will provide a couple helper functions to do that for us.
321 {{{
322function pump.step(src, snk)
323 local chunk, src_err = src()
324 local ret, snk_err = snk(chunk, src_err)
325 return chunk and ret and not src_err and not snk_err, src_err or snk_err
326end
327
328function pump.all(src, snk, step)
329 step = step or pump.step
330 while true do
331 local ret, err = step(src, snk)
332 if not ret then return not err, err end
333 end
334end
335}}}
336
337The {{pump.step}} function moves one chunk of data from the source to the sink. The {{pump.all}} function takes an optional {{step}} function and uses it to pump all the data from the source to the sink. We can now use everything we have to write a program that reads a binary file from disk and stores it in another file, after encoding it to the Base64 transfer content encoding:
338 {{{
339local load = source.chain(
340 source.file(io.open("input.bin", "rb")),
341 encode("base64")
342)
343local store = sink.chain(
344 wrap(76),
345 sink.file(io.open("output.b64", "w")),
346)
347pump.all(load, store)
348}}}
349
350The way we split the filters here is not intuitive, on purpose. Alternatively, we could have chained the Base64 encode filter and the line-wrap filter together, and then chain the resulting filter with either the file source or the file sink. It doesn't really matter.
351
352===One last important change===
353
354Turns out we still have a problem. When David Burgess was writing his gzip filter, he noticed that the decompression filter can explode a small input chunk into a huge amount of data. Although we wished we could ignore this problem, we soon agreed we couldn't. The only solution is to allow filters to return partial results, and that is what we chose to do. After invoking the filter to pass input data, the user now has to loop invoking the filter to find out if it has more output data to return. Note that these extra calls can't pass more data to the filter.
355
356More specifically, after passing a chunk of input data to a filter and collecting the first chunk of output data, the user invokes the filter repeatedly, passing the empty string, to get extra output chunks. When the filter itself returns an empty string, the user knows there is no more output data, and can proceed to pass the next input chunk. In the end, after the user passes a {{nil}} notifying the filter that there is no more input data, the filter might still have produced too much output data to return in a single chunk. The user has to loop again, this time passing {{nil}} each time, until the filter itself returns {{nil}} to notify the user it is finaly done.
357
358Most filters won't need this extra freedom. Fortunately, the new filter interface is easy to implement. In fact, the end-of-line translation filter we created in the introduction already conforms to it. On the other hand, the chaining function becomes much more complicated. If it wasn't for coroutines, I wouldn't be happy to implement it. Let me know if you can find a simpler implementation that does not use coroutines!
359 {{{
360local function chain2(f1, f2)
361 local co = coroutine.create(function(chunk)
362 while true do
363 local filtered1 = f1(chunk)
364 local filtered2 = f2(filtered1)
365 local done2 = filtered1 and ""
366 while true do
367 if filtered2 == "" or filtered2 == nil then break end
368 coroutine.yield(filtered2)
369 filtered2 = f2(done2)
370 end
371 if filtered1 == "" then chunk = coroutine.yield(filtered1)
372 elseif filtered1 == nil then return nil
373 else chunk = chunk and "" end
374 end
375 end)
376 return function(chunk)
377 local _, res = coroutine.resume(co, chunk)
378 return res
379 end
380end
381}}}
382
383Chaining sources also becomes more complicated, but a similar solution is possible with coroutines. Chaining sinks is just as simple as it has always been. Interstingly, these modifications do not have a measurable negative impact in the the performance of filters that didn't need the added flexibility. They do severely improve the efficiency of filters like the gzip filter, though, and that is why we are keeping them.
384
385===Final considerations===
386
387These ideas were created during the development of {{LuaSocket}}[http://www.tecgraf.puc-rio.br/luasocket] 2.0, and are available as the LTN12 module. As a result, {{LuaSocket}}[http://www.tecgraf.puc-rio.br/luasocket] implementation was greatly simplified and became much more powerful. The MIME module is especially integrated to LTN12 and provides many other filters. We felt these concepts deserved to be made public even to those that don't care about {{LuaSocket}}[http://www.tecgraf.puc-rio.br/luasocket], hence the LTN.
388
389One extra application that deserves mentioning makes use of an identity filter. Suppose you want to provide some feedback to the user while a file is being downloaded into a sink. Chaining the sink with an identity filter (a filter that simply returns the received data unaltered), you can update a progress counter on the fly. The original sink doesn't have to be modified. Another interesting idea is that of a T sink: A sink that sends data to two other sinks. In summary, there appears to be enough room for many other interesting ideas.
390
391In this technical note we introduced filters, sources, sinks, and pumps. These are useful tools for data processing in general. Sources provide a simple abstraction for data acquisition. Sinks provide an abstraction for final data destinations. Filters define an interface for data transformations. The chaining of filters, sources and sinks provides an elegant way to create arbitrarily complex data transformation from simpler transformations. Pumps just put the machinery to work.
diff --git a/src/auxiliar.c b/src/auxiliar.c
index fe21d08..b1f9203 100644
--- a/src/auxiliar.c
+++ b/src/auxiliar.c
@@ -14,27 +14,6 @@
14* Exported functions 14* Exported functions
15\*=========================================================================*/ 15\*=========================================================================*/
16/*-------------------------------------------------------------------------*\ 16/*-------------------------------------------------------------------------*\
17* Prints the value of a class in a nice way
18\*-------------------------------------------------------------------------*/
19int aux_meth_tostring(lua_State *L)
20{
21 char buf[32];
22 if (!lua_getmetatable(L, 1)) goto error;
23 lua_pushstring(L, "__index");
24 lua_gettable(L, -2);
25 if (!lua_istable(L, -1)) goto error;
26 lua_pushstring(L, "class");
27 lua_gettable(L, -2);
28 if (!lua_isstring(L, -1)) goto error;
29 sprintf(buf, "%p", lua_touserdata(L, 1));
30 lua_pushfstring(L, "socket: %s: %s", lua_tostring(L, -1), buf);
31 return 1;
32error:
33 lua_pushnil(L);
34 return 1;
35}
36
37/*-------------------------------------------------------------------------*\
38* Initializes the module 17* Initializes the module
39\*-------------------------------------------------------------------------*/ 18\*-------------------------------------------------------------------------*/
40int aux_open(lua_State *L) 19int aux_open(lua_State *L)
@@ -48,23 +27,20 @@ int aux_open(lua_State *L)
48void aux_newclass(lua_State *L, const char *classname, luaL_reg *func) 27void aux_newclass(lua_State *L, const char *classname, luaL_reg *func)
49{ 28{
50 luaL_newmetatable(L, classname); /* mt */ 29 luaL_newmetatable(L, classname); /* mt */
51 /* set __tostring metamethod */
52 lua_pushstring(L, "__tostring");
53 lua_pushcfunction(L, aux_meth_tostring);
54 lua_rawset(L, -3);
55 /* create __index table to place methods */ 30 /* create __index table to place methods */
56 lua_pushstring(L, "__index"); /* mt,"__index" */ 31 lua_pushstring(L, "__index"); /* mt,"__index" */
57 lua_newtable(L); /* mt,"__index",it */ 32 lua_newtable(L); /* mt,"__index",it */
58 luaL_openlib(L, NULL, func, 0);
59 /* put class name into class metatable */ 33 /* put class name into class metatable */
60 lua_pushstring(L, "class"); /* mt,"__index",it,"class" */ 34 lua_pushstring(L, "class"); /* mt,"__index",it,"class" */
61 lua_pushstring(L, classname); /* mt,"__index",it,"class",classname */ 35 lua_pushstring(L, classname); /* mt,"__index",it,"class",classname */
62 lua_rawset(L, -3); /* mt,"__index",it */ 36 lua_rawset(L, -3); /* mt,"__index",it */
63 /* get __gc method from class and use it for garbage collection */ 37 /* pass all methods that start with _ to the metatable, and all others
64 lua_pushstring(L, "__gc"); /* mt,"__index",it,"__gc" */ 38 * to the index table */
65 lua_pushstring(L, "__gc"); /* mt,"__index",it,"__gc","__gc" */ 39 for (; func->name; func++) { /* mt,"__index",it */
66 lua_rawget(L, -3); /* mt,"__index",it,"__gc",fn */ 40 lua_pushstring(L, func->name);
67 lua_rawset(L, -5); /* mt,"__index",it */ 41 lua_pushcfunction(L, func->func);
42 lua_rawset(L, func->name[0] == '_' ? -5: -3);
43 }
68 lua_rawset(L, -3); /* mt */ 44 lua_rawset(L, -3); /* mt */
69 lua_pop(L, 1); 45 lua_pop(L, 1);
70} 46}
diff --git a/src/ftp.lua b/src/ftp.lua
index 18dab6d..6074623 100644
--- a/src/ftp.lua
+++ b/src/ftp.lua
@@ -30,474 +30,153 @@ EMAIL = "anonymous@anonymous.org"
30BLOCKSIZE = 2048 30BLOCKSIZE = 2048
31 31
32----------------------------------------------------------------------------- 32-----------------------------------------------------------------------------
33-- Gets ip and port for data connection from PASV answer 33-- Low level FTP API
34-- Input
35-- pasv: PASV command answer
36-- Returns
37-- ip: string containing ip for data connection
38-- port: port for data connection
39----------------------------------------------------------------------------- 34-----------------------------------------------------------------------------
40local function get_pasv(pasv) 35local metat = { __index = {} }
41 local a, b, c, d, p1, p2, _
42 local ip, port
43 _,_, a, b, c, d, p1, p2 =
44 string.find(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)")
45 if not (a and b and c and d and p1 and p2) then return nil, nil end
46 ip = string.format("%d.%d.%d.%d", a, b, c, d)
47 port = tonumber(p1)*256 + tonumber(p2)
48 return ip, port
49end
50
51-----------------------------------------------------------------------------
52-- Check server greeting
53-- Input
54-- control: control connection with server
55-- Returns
56-- code: nil if error
57-- answer: server answer or error message
58-----------------------------------------------------------------------------
59local function greet(control)
60 local code, answer = check_answer(control, {120, 220})
61 if code == 120 then -- please try again, somewhat busy now...
62 return check_answer(control, {220})
63 end
64 return code, answer
65end
66
67-----------------------------------------------------------------------------
68-- Log in on server
69-- Input
70-- control: control connection with server
71-- user: user name
72-- password: user password if any
73-- Returns
74-- code: nil if error
75-- answer: server answer or error message
76-----------------------------------------------------------------------------
77local function login(control, user, password)
78 local code, answer = command(control, "user", user, {230, 331})
79 if code == 331 and password then -- need pass and we have pass
80 return command(control, "pass", password, {230, 202})
81 end
82 return code, answer
83end
84
85-----------------------------------------------------------------------------
86-- Change to target directory
87-- Input
88-- control: socket for control connection with server
89-- path: directory to change to
90-- Returns
91-- code: nil if error
92-- answer: server answer or error message
93-----------------------------------------------------------------------------
94local function cwd(control, path)
95end
96
97-----------------------------------------------------------------------------
98-- Change to target directory
99-- Input
100-- control: socket for control connection with server
101-- Returns
102-- server: server socket bound to local address, nil if error
103-- answer: error message if any
104-----------------------------------------------------------------------------
105local function port(control)
106 local code, answer
107 local server, ctl_ip
108 ctl_ip, answer = control:getsockname()
109 server, answer = socket.bind(ctl_ip, 0)
110 server:settimeout(TIMEOUT)
111 local ip, p, ph, pl
112 ip, p = server:getsockname()
113 pl = math.mod(p, 256)
114 ph = (p - pl)/256
115 local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",")
116 code, answer = command(control, "port", arg, {200})
117 if not code then
118 server:close()
119 return nil, answer
120 else return server end
121end
122 36
123----------------------------------------------------------------------------- 37function open(server, port)
124-- Closes control connection with server 38 local tp = socket.try(socket.tp.connect(server, port or PORT))
125-- Input 39 return setmetatable({tp = tp}, metat)
126-- control: control connection with server
127-- Returns
128-- code: nil if error
129-- answer: server answer or error message
130-----------------------------------------------------------------------------
131local function logout(control)
132 local code, answer = command(control, "quit", nil, {221})
133 if code then control:close() end
134 return code, answer
135end 40end
136 41
137----------------------------------------------------------------------------- 42local function port(portt)
138-- Receives data and send it to a callback 43 return portt.server:accept()
139-- Input
140-- data: data connection
141-- callback: callback to return file contents
142-- Returns
143-- nil if successfull, or an error message in case of error
144-----------------------------------------------------------------------------
145local function receive_indirect(data, callback)
146 local chunk, err, res
147 while not err do
148 chunk, err = try_receive(data, BLOCKSIZE)
149 if err == "closed" then err = "done" end
150 res = callback(chunk, err)
151 if not res then break end
152 end
153end 44end
154 45
155----------------------------------------------------------------------------- 46local function pasv(pasvt)
156-- Retrieves file or directory listing 47 return socket.connect(pasvt.ip, pasvt.port)
157-- Input
158-- control: control connection with server
159-- server: server socket bound to local address
160-- name: file name
161-- is_directory: is file a directory name?
162-- content_cb: callback to receive file contents
163-- Returns
164-- err: error message in case of error, nil otherwise
165-----------------------------------------------------------------------------
166local function retrieve(control, server, name, is_directory, content_cb)
167 local code, answer
168 local data
169 -- ask server for file or directory listing accordingly
170 if is_directory then
171 code, answer = cwd(control, name)
172 if not code then return answer end
173 code, answer = command(control, "nlst", nil, {150, 125})
174 else
175 code, answer = command(control, "retr", name, {150, 125})
176 end
177 if not code then return nil, answer end
178 data, answer = server:accept()
179 server:close()
180 if not data then
181 control:close()
182 return answer
183 end
184 answer = receive_indirect(data, content_cb)
185 if answer then
186 control:close()
187 return answer
188 end
189 data:close()
190 -- make sure file transfered ok
191 return check_answer(control, {226, 250})
192end 48end
193 49
194----------------------------------------------------------------------------- 50function metat.__index:login(user, password)
195-- Stores a file 51 socket.try(self.tp:command("USER", user))
196-- Input 52 local code, reply = socket.try(self.tp:check{"2..", 331})
197-- control: control connection with server 53 if code == 331 then
198-- server: server socket bound to local address 54 socket.try(password, reply)
199-- file: file name under current directory 55 socket.try(self.tp:command("PASS", password))
200-- send_cb: callback to produce the file contents 56 socket.try(self.tp:check("2.."))
201-- Returns 57 end
202-- code: return code, nil if error 58 return 1
203-- answer: server answer or error message 59end
204----------------------------------------------------------------------------- 60
205local function store(control, server, file, send_cb) 61function metat.__index:pasv()
206 local data, err 62 socket.try(self.tp:command("PASV"))
207 local code, answer = command(control, "stor", file, {150, 125}) 63 local code, reply = socket.try(self.tp:check("2.."))
208 if not code then 64 local _, _, a, b, c, d, p1, p2 =
209 control:close() 65 string.find(reply, "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)")
210 return nil, answer 66 socket.try(a and b and c and d and p1 and p2, reply)
211 end 67 self.pasvt = {
212 -- start data connection 68 ip = string.format("%d.%d.%d.%d", a, b, c, d),
213 data, answer = server:accept() 69 port = p1*256 + p2
214 server:close() 70 }
215 if not data then 71 if self.portt then
216 control:close() 72 self.portt.server:close()
217 return nil, answer 73 self.portt = nil
218 end 74 end
219 -- send whole file 75 return self.pasvt.ip, self.pasvt.port
220 err = send_indirect(data, send_cb, send_cb())
221 if err then
222 control:close()
223 return nil, err
224 end
225 -- close connection to inform that file transmission is complete
226 data:close()
227 -- check if file was received correctly
228 return check_answer(control, {226, 250})
229end
230
231-----------------------------------------------------------------------------
232-- Change transfer type
233-- Input
234-- control: control connection with server
235-- params: "type=i" for binary or "type=a" for ascii
236-- Returns
237-- err: error message if any
238-----------------------------------------------------------------------------
239local function change_type(control, params)
240 local type, _
241 _, _, type = string.find(params or "", "type=(.)")
242 if type == "a" or type == "i" then
243 local code, err = command(control, "type", type, {200})
244 if not code then return err end
245 end
246end 76end
247 77
248----------------------------------------------------------------------------- 78function metat.__index:port(ip, port)
249-- Starts a control connection, checks the greeting and log on 79 self.pasvt = nil
250-- Input 80 local server
251-- parsed: parsed URL components 81 if not ip then
252-- Returns 82 ip, port = socket.try(self.tp:getcontrol():getsockname())
253-- control: control connection with server, or nil if error 83 server = socket.try(socket.bind(ip, 0))
254-- err: error message if any 84 ip, port = socket.try(server:getsockname())
255----------------------------------------------------------------------------- 85 socket.try(server:settimeout(TIMEOUT))
256local function open(parsed) 86 end
257 local control, err = socket.tp.connect(parsed.host, parsed.port) 87 local pl = math.mod(port, 256)
258 if not control then return nil, err end 88 local ph = (port - pl)/256
89 local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",")
90 socket.try(self.tp:command("port", arg))
91 socket.try(self.tp:check("2.."))
92 self.portt = server and {ip = ip, port = port, server = server}
93 return 1
94end
95
96function metat.__index:send(sendt)
97 local data
98 socket.try(self.pasvt or self.portt, "need port or pasv first")
99 if self.pasvt then data = socket.try(pasv(self.pasvt)) end
100 socket.try(self.tp:command(sendt.command, sendt.argument))
101 if self.portt then data = socket.try(port(self.portt)) end
102 local step = sendt.step or ltn12.pump.step
259 local code, reply 103 local code, reply
260 -- greet 104 local checkstep = function(src, snk)
261 code, reply = control:check({120, 220}) 105 local readyt = socket.select(readt, nil, 0)
262 if code == 120 then -- busy, try again 106 if readyt[tp] then
263 code, reply = control:check(220) 107 code, reply = self.tp:check{"2..", "1.."}
108 if not code then
109 data:close()
110 return nil, reply
111 end
112 end
113 local ret, err = step(src, snk)
114 if err then data:close() end
115 return ret, err
264 end 116 end
265 -- authenticate 117 local sink = socket.sink("close-when-empty", data)
266 code, reply = control:command("user", user) 118 socket.try(ltn12.pump.all(sendt.source, sink, checkstep))
267 code, reply = control:check({230, 331}) 119 if not code then code = socket.try(self.tp:check{"1..", "2.."}) end
268 if code == 331 and password then -- need pass and we have pass 120 if string.find(code, "1..") then socket.try(self.tp:check("2..")) end
269 control:command("pass", password) 121 return 1
270 code, reply = control:check({230, 202}) 122end
123
124function metat.__index:receive(recvt)
125 local data
126 socket.try(self.pasvt or self.portt, "need port or pasv first")
127 if self.pasvt then data = socket.try(pasv(self.pasvt)) end
128 socket.try(self.tp:command(recvt.command, recvt.argument))
129 if self.portt then data = socket.try(port(self.portt)) end
130 local source = socket.source("until-closed", data)
131 local step = recvt.step or ltn12.pump.step
132 local checkstep = function(src, snk)
133 local ret, err = step(src, snk)
134 if err then data:close() end
135 return ret, err
271 end 136 end
272 -- change directory 137 socket.try(ltn12.pump.all(source, recvt.sink, checkstep))
273 local segment = parse_path(parsed) 138 local code = socket.try(self.tp:check{"1..", "2.."})
274 for i, v in ipairs(segment) do 139 if string.find(code, "1..") then socket.try(self.tp:check("2..")) end
275 code, reply = control:command("cwd") 140 return 1
276 code, reply = control:check(250)
277 end
278 -- change type
279 local type = string.sub(params or "", 7, 7)
280 if type == "a" or type == "i" then
281 code, reply = control:command("type", type)
282 code, reply = control:check(200)
283 end
284end 141end
285 142
286 return change_dir(control, segment) or 143function metat.__index:cwd(dir)
287 change_type(control, parsed.params) or 144 socket.try(self.tp:command("CWD", dir))
288 download(control, request, segment) or 145 socket.try(self.tp:check(250))
289 close(control) 146 return 1
290end 147end
291 148
149function metat.__index:type(type)
150 socket.try(self.tp:command("TYPE", type))
151 socket.try(self.tp:check(200))
152 return 1
292end 153end
293 154
294----------------------------------------------------------------------------- 155function metat.__index:greet()
295-- Stores a file in current directory 156 local code = socket.try(self.tp:check{"1..", "2.."})
296-- Input 157 if string.find(code, "1..") then socket.try(self.tp:check("2..")) end
297-- control: control connection with server 158 return 1
298-- request: a table with the fields:
299-- content_cb: send callback to send file contents
300-- segment: parsed URL path segments
301-- Returns
302-- err: error message if any
303-----------------------------------------------------------------------------
304local function upload(control, request, segment)
305 local code, name, content_cb
306 -- get remote file name
307 name = segment[table.getn(segment)]
308 if not name then
309 control:close()
310 return "Invalid file path"
311 end
312 content_cb = request.content_cb
313 -- setup passive connection
314 local server, answer = port(control)
315 if not server then return answer end
316 -- ask server to receive file
317 code, answer = store(control, server, name, content_cb)
318 if not code then return answer end
319end 159end
320 160
321----------------------------------------------------------------------------- 161function metat.__index:quit()
322-- Download a file from current directory 162 socket.try(self.tp:command("QUIT"))
323-- Input 163 socket.try(self.tp:check("2.."))
324-- control: control connection with server 164 return 1
325-- request: a table with the fields:
326-- content_cb: receive callback to receive file contents
327-- segment: parsed URL path segments
328-- Returns
329-- err: error message if any
330-----------------------------------------------------------------------------
331local function download(control, request, segment)
332 local code, name, is_directory, content_cb
333 is_directory = segment.is_directory
334 content_cb = request.content_cb
335 -- get remote file name
336 name = segment[table.getn(segment)]
337 if not name and not is_directory then
338 control:close()
339 return "Invalid file path"
340 end
341 -- setup passive connection
342 local server, answer = port(control)
343 if not server then return answer end
344 -- ask server to send file or directory listing
345 code, answer = retrieve(control, server, name,
346 is_directory, content_cb)
347 if not code then return answer end
348end 165end
349 166
350----------------------------------------------------------------------------- 167function metat.__index:close()
351-- Parses the FTP URL setting default values 168 socket.try(self.tp:close())
352-- Input 169 return 1
353-- request: a table with the fields:
354-- url: the target URL
355-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
356-- user: account user name
357-- password: account password
358-- Returns
359-- parsed: a table with parsed components
360-----------------------------------------------------------------------------
361local function parse_url(request)
362 local parsed = socket.url.parse(request.url, {
363 user = "anonymous",
364 port = 21,
365 path = "/",
366 password = EMAIL,
367 scheme = "ftp"
368 })
369 -- explicit login information overrides that given by URL
370 parsed.user = request.user or parsed.user
371 parsed.password = request.password or parsed.password
372 -- explicit representation type overrides that given by URL
373 if request.type then parsed.params = "type=" .. request.type end
374 return parsed
375end 170end
376 171
377----------------------------------------------------------------------------- 172-----------------------------------------------------------------------------
378-- Parses the FTP URL path setting default values 173-- High level FTP API
379-- Input
380-- parsed: a table with the parsed URL components
381-- Returns
382-- dirs: a table with parsed directory components
383----------------------------------------------------------------------------- 174-----------------------------------------------------------------------------
384local function parse_path(parsed_url)
385 local segment = socket.url.parse_path(parsed_url.path)
386 segment.is_directory = segment.is_directory or
387 (parsed_url.params == "type=d")
388 return segment
389end
390 175
391----------------------------------------------------------------------------- 176function put(putt)
392-- Builds a request table from a URL or request table
393-- Input
394-- url_or_request: target url or request table (a table with the fields:
395-- url: the target URL
396-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
397-- user: account user name
398-- password: account password)
399-- Returns
400-- request: request table
401-----------------------------------------------------------------------------
402local function build_request(data)
403 local request = {}
404 if type(data) == "table" then for i, v in data do request[i] = v end
405 else request.url = data end
406 return request
407end 177end
408 178
409----------------------------------------------------------------------------- 179function get(gett)
410-- Downloads a file from a FTP server
411-- Input
412-- request: a table with the fields:
413-- url: the target URL
414-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
415-- user: account user name
416-- password: account password
417-- content_cb: receive callback to receive file contents
418-- Returns
419-- err: error message if any
420-----------------------------------------------------------------------------
421function get_cb(request)
422 local parsed = parse_url(request)
423 if parsed.scheme ~= "ftp" then
424 return string.format("unknown scheme '%s'", parsed.scheme)
425 end
426 local control, err = open(parsed)
427 if not control then return err end
428 local segment = parse_path(parsed)
429 return change_dir(control, segment) or
430 change_type(control, parsed.params) or
431 download(control, request, segment) or
432 close(control)
433end
434
435-----------------------------------------------------------------------------
436-- Uploads a file to a FTP server
437-- Input
438-- request: a table with the fields:
439-- url: the target URL
440-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
441-- user: account user name
442-- password: account password
443-- content_cb: send callback to send file contents
444-- Returns
445-- err: error message if any
446-----------------------------------------------------------------------------
447function put_cb(request)
448 local parsed = parse_url(request)
449 if parsed.scheme ~= "ftp" then
450 return string.format("unknown scheme '%s'", parsed.scheme)
451 end
452 local control, err = open(parsed)
453 if not control then return err end
454 local segment = parse_path(parsed)
455 err = change_dir(control, segment) or
456 change_type(control, parsed.params) or
457 upload(control, request, segment) or
458 close(control)
459 if err then return nil, err
460 else return 1 end
461end
462
463-----------------------------------------------------------------------------
464-- Uploads a file to a FTP server
465-- Input
466-- url_or_request: target url or request table (a table with the fields:
467-- url: the target URL
468-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
469-- user: account user name
470-- password: account password)
471-- content: file contents
472-- content: file contents
473-- Returns
474-- err: error message if any
475-----------------------------------------------------------------------------
476function put(url_or_request, content)
477 local request = build_request(url_or_request)
478 request.content = request.content or content
479 request.content_cb = socket.callback.send_string(request.content)
480 return put_cb(request)
481end
482
483-----------------------------------------------------------------------------
484-- Retrieve a file from a ftp server
485-- Input
486-- url_or_request: target url or request table (a table with the fields:
487-- url: the target URL
488-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory
489-- user: account user name
490-- password: account password)
491-- Returns
492-- data: file contents as a string
493-- err: error message in case of error, nil otherwise
494-----------------------------------------------------------------------------
495function get(url_or_request)
496 local concat = socket.concat.create()
497 local request = build_request(url_or_request)
498 request.content_cb = socket.callback.receive_concat(concat)
499 local err = get_cb(request)
500 return concat:getresult(), err
501end 180end
502 181
503return socket.ftp 182return ftp
diff --git a/src/http.lua b/src/http.lua
index da18aaf..f787b9d 100644
--- a/src/http.lua
+++ b/src/http.lua
@@ -68,7 +68,7 @@ end
68 68
69local function receive_body(reqt, respt, tmp) 69local function receive_body(reqt, respt, tmp)
70 local sink = reqt.sink or ltn12.sink.null() 70 local sink = reqt.sink or ltn12.sink.null()
71 local pump = reqt.pump or ltn12.pump 71 local step = reqt.step or ltn12.pump.step
72 local source 72 local source
73 local te = respt.headers["transfer-encoding"] 73 local te = respt.headers["transfer-encoding"]
74 if te and te ~= "identity" then 74 if te and te ~= "identity" then
@@ -80,9 +80,9 @@ local function receive_body(reqt, respt, tmp)
80 source = socket.source("by-length", tmp.sock, length) 80 source = socket.source("by-length", tmp.sock, length)
81 else 81 else
82 -- get it all until connection closes 82 -- get it all until connection closes
83 source = socket.source("until-closed", tmp.sock) 83 source = socket.source(tmp.sock)
84 end 84 end
85 socket.try(pump(source, sink)) 85 socket.try(ltn12.pump.all(source, sink, step))
86end 86end
87 87
88local function send_headers(sock, headers) 88local function send_headers(sock, headers)
@@ -125,7 +125,7 @@ end
125local function send_request(reqt, respt, tmp) 125local function send_request(reqt, respt, tmp)
126 local uri = request_uri(reqt, respt, tmp) 126 local uri = request_uri(reqt, respt, tmp)
127 local headers = tmp.headers 127 local headers = tmp.headers
128 local pump = reqt.pump or ltn12.pump 128 local step = reqt.step or ltn12.pump.step
129 -- send request line 129 -- send request line
130 socket.try(tmp.sock:send((reqt.method or "GET") 130 socket.try(tmp.sock:send((reqt.method or "GET")
131 .. " " .. uri .. " HTTP/1.1\r\n")) 131 .. " " .. uri .. " HTTP/1.1\r\n"))
@@ -136,9 +136,11 @@ local function send_request(reqt, respt, tmp)
136 -- send request message body, if any 136 -- send request message body, if any
137 if not reqt.source then return end 137 if not reqt.source then return end
138 if headers["content-length"] then 138 if headers["content-length"] then
139 socket.try(pump(reqt.source, socket.sink(tmp.sock))) 139 socket.try(ltn12.pump.all(reqt.source,
140 socket.sink(tmp.sock), step))
140 else 141 else
141 socket.try(pump(reqt.source, socket.sink("http-chunked", tmp.sock))) 142 socket.try(ltn12.pump.all(reqt.source,
143 socket.sink("http-chunked", tmp.sock), step))
142 end 144 end
143end 145end
144 146
diff --git a/src/ltn12.lua b/src/ltn12.lua
index dac932b..56e6043 100644
--- a/src/ltn12.lua
+++ b/src/ltn12.lua
@@ -8,6 +8,7 @@ setfenv(1, ltn12)
8filter = {} 8filter = {}
9source = {} 9source = {}
10sink = {} 10sink = {}
11pump = {}
11 12
12-- 2048 seems to be better in windows... 13-- 2048 seems to be better in windows...
13BLOCKSIZE = 2048 14BLOCKSIZE = 2048
@@ -22,7 +23,6 @@ end
22 23
23-- returns a high level filter that cycles a cycles a low-level filter 24-- returns a high level filter that cycles a cycles a low-level filter
24function filter.cycle(low, ctx, extra) 25function filter.cycle(low, ctx, extra)
25 if type(low) ~= 'function' then error('invalid low-level filter', 2) end
26 return function(chunk) 26 return function(chunk)
27 local ret 27 local ret
28 ret, ctx = low(ctx, chunk, extra) 28 ret, ctx = low(ctx, chunk, extra)
@@ -32,8 +32,6 @@ end
32 32
33-- chains two filters together 33-- chains two filters together
34local function chain2(f1, f2) 34local function chain2(f1, f2)
35 if type(f1) ~= 'function' then error('invalid filter', 2) end
36 if type(f2) ~= 'function' then error('invalid filter', 2) end
37 local co = coroutine.create(function(chunk) 35 local co = coroutine.create(function(chunk)
38 while true do 36 while true do
39 local filtered1 = f1(chunk) 37 local filtered1 = f1(chunk)
@@ -58,7 +56,6 @@ end
58function filter.chain(...) 56function filter.chain(...)
59 local f = arg[1] 57 local f = arg[1]
60 for i = 2, table.getn(arg) do 58 for i = 2, table.getn(arg) do
61 if type(arg[i]) ~= 'function' then error('invalid filter', 2) end
62 f = chain2(f, arg[i]) 59 f = chain2(f, arg[i])
63 end 60 end
64 return f 61 return f
@@ -93,7 +90,6 @@ end
93 90
94-- turns a fancy source into a simple source 91-- turns a fancy source into a simple source
95function source.simplify(src) 92function source.simplify(src)
96 if type(src) ~= 'function' then error('invalid source', 2) end
97 return function() 93 return function()
98 local chunk, err_or_new = src() 94 local chunk, err_or_new = src()
99 src = err_or_new or src 95 src = err_or_new or src
@@ -117,7 +113,6 @@ end
117 113
118-- creates rewindable source 114-- creates rewindable source
119function source.rewind(src) 115function source.rewind(src)
120 if type(src) ~= 'function' then error('invalid source', 2) end
121 local t = {} 116 local t = {}
122 return function(chunk) 117 return function(chunk)
123 if not chunk then 118 if not chunk then
@@ -132,8 +127,6 @@ end
132 127
133-- chains a source with a filter 128-- chains a source with a filter
134function source.chain(src, f) 129function source.chain(src, f)
135 if type(src) ~= 'function' then error('invalid source', 2) end
136 if type(f) ~= 'function' then error('invalid filter', 2) end
137 local co = coroutine.create(function() 130 local co = coroutine.create(function()
138 while true do 131 while true do
139 local chunk, err = src() 132 local chunk, err = src()
@@ -152,20 +145,21 @@ function source.chain(src, f)
152 end 145 end
153end 146end
154 147
155-- creates a source that produces contents of several files one after the 148-- creates a source that produces contents of several sources, one after the
156-- other, as if they were concatenated 149-- other, as if they were concatenated
157function source.cat(...) 150function source.cat(...)
158 local co = coroutine.create(function() 151 local co = coroutine.create(function()
159 local i = 1 152 local i = 1
160 while i <= table.getn(arg) do 153 while i <= table.getn(arg) do
161 local chunk = arg[i]:read(2048) 154 local chunk, err = arg[i]()
162 if chunk then coroutine.yield(chunk) 155 if chunk then coroutine.yield(chunk)
163 else i = i + 1 end 156 elseif err then return nil, err
157 else i = i + 1 end
164 end 158 end
165 end) 159 end)
166 return source.simplify(function() 160 return function()
167 return shift(coroutine.resume(co)) 161 return shift(coroutine.resume(co))
168 end) 162 end
169end 163end
170 164
171-- creates a sink that stores into a table 165-- creates a sink that stores into a table
@@ -180,7 +174,6 @@ end
180 174
181-- turns a fancy sink into a simple sink 175-- turns a fancy sink into a simple sink
182function sink.simplify(snk) 176function sink.simplify(snk)
183 if type(snk) ~= 'function' then error('invalid sink', 2) end
184 return function(chunk, err) 177 return function(chunk, err)
185 local ret, err_or_new = snk(chunk, err) 178 local ret, err_or_new = snk(chunk, err)
186 if not ret then return nil, err_or_new end 179 if not ret then return nil, err_or_new end
@@ -219,8 +212,6 @@ end
219 212
220-- chains a sink with a filter 213-- chains a sink with a filter
221function sink.chain(f, snk) 214function sink.chain(f, snk)
222 if type(snk) ~= 'function' then error('invalid sink', 2) end
223 if type(f) ~= 'function' then error('invalid filter', 2) end
224 return function(chunk, err) 215 return function(chunk, err)
225 local filtered = f(chunk) 216 local filtered = f(chunk)
226 local done = chunk and "" 217 local done = chunk and ""
@@ -233,15 +224,18 @@ function sink.chain(f, snk)
233 end 224 end
234end 225end
235 226
236-- pumps all data from a source to a sink 227-- pumps one chunk from the source to the sink
237function pump(src, snk) 228function pump.step(src, snk)
238 if type(src) ~= 'function' then error('invalid source', 2) end 229 local chunk, src_err = src()
239 if type(snk) ~= 'function' then error('invalid sink', 2) end 230 local ret, snk_err = snk(chunk, src_err)
231 return chunk and ret and not src_err and not snk_err, src_err or snk_err
232end
233
234-- pumps all data from a source to a sink, using a step function
235function pump.all(src, snk, step)
236 step = step or pump.step
240 while true do 237 while true do
241 local chunk, src_err = src() 238 local ret, err = step(src, snk)
242 local ret, snk_err = snk(chunk, src_err) 239 if not ret then return not err, err end
243 if not chunk or not ret then
244 return not src_err and not snk_err, src_err or snk_err
245 end
246 end 240 end
247end 241end
diff --git a/src/luasocket.c b/src/luasocket.c
index eadb758..fe4c96c 100644
--- a/src/luasocket.c
+++ b/src/luasocket.c
@@ -25,6 +25,7 @@
25\*=========================================================================*/ 25\*=========================================================================*/
26#include "luasocket.h" 26#include "luasocket.h"
27 27
28#include "base.h"
28#include "auxiliar.h" 29#include "auxiliar.h"
29#include "timeout.h" 30#include "timeout.h"
30#include "buffer.h" 31#include "buffer.h"
@@ -39,34 +40,8 @@
39/*=========================================================================*\ 40/*=========================================================================*\
40* Declarations 41* Declarations
41\*=========================================================================*/ 42\*=========================================================================*/
42static int base_open(lua_State *L);
43static int mod_open(lua_State *L, const luaL_reg *mod); 43static int mod_open(lua_State *L, const luaL_reg *mod);
44 44
45/*-------------------------------------------------------------------------*\
46* Setup basic stuff.
47\*-------------------------------------------------------------------------*/
48static int base_open(lua_State *L)
49{
50 /* create namespace table */
51 lua_pushstring(L, LUASOCKET_LIBNAME);
52 lua_newtable(L);
53#ifdef LUASOCKET_DEBUG
54 lua_pushstring(L, "debug");
55 lua_pushnumber(L, 1);
56 lua_rawset(L, -3);
57#endif
58 /* make version string available so scripts */
59 lua_pushstring(L, "version");
60 lua_pushstring(L, LUASOCKET_VERSION);
61 lua_rawset(L, -3);
62 /* store namespace as global */
63 lua_settable(L, LUA_GLOBALSINDEX);
64 /* make sure modules know what is our namespace */
65 lua_pushstring(L, "LUASOCKET_LIBNAME");
66 lua_pushstring(L, LUASOCKET_LIBNAME);
67 lua_settable(L, LUA_GLOBALSINDEX);
68 return 0;
69}
70 45
71static int mod_open(lua_State *L, const luaL_reg *mod) 46static int mod_open(lua_State *L, const luaL_reg *mod)
72{ 47{
@@ -79,6 +54,7 @@ static int mod_open(lua_State *L, const luaL_reg *mod)
79#include "tp.lch" 54#include "tp.lch"
80#include "smtp.lch" 55#include "smtp.lch"
81#include "http.lch" 56#include "http.lch"
57#include "ftp.lch"
82#else 58#else
83 lua_dofile(L, "ltn12.lua"); 59 lua_dofile(L, "ltn12.lua");
84 lua_dofile(L, "auxiliar.lua"); 60 lua_dofile(L, "auxiliar.lua");
@@ -87,6 +63,7 @@ static int mod_open(lua_State *L, const luaL_reg *mod)
87 lua_dofile(L, "tp.lua"); 63 lua_dofile(L, "tp.lua");
88 lua_dofile(L, "smtp.lua"); 64 lua_dofile(L, "smtp.lua");
89 lua_dofile(L, "http.lua"); 65 lua_dofile(L, "http.lua");
66 lua_dofile(L, "ftp.lua");
90#endif 67#endif
91 return 0; 68 return 0;
92} 69}
diff --git a/src/mime.c b/src/mime.c
index 7bfa6aa..fe24f9b 100644
--- a/src/mime.c
+++ b/src/mime.c
@@ -14,14 +14,9 @@
14/*=========================================================================*\ 14/*=========================================================================*\
15* Don't want to trust escape character constants 15* Don't want to trust escape character constants
16\*=========================================================================*/ 16\*=========================================================================*/
17#define CR 0x0D
18#define LF 0x0A
19#define HT 0x09
20#define SP 0x20
21
22typedef unsigned char UC; 17typedef unsigned char UC;
23static const char CRLF[] = {CR, LF, 0}; 18static const char CRLF[] = "\r\n";
24static const char EQCRLF[] = {'=', CR, LF, 0}; 19static const char EQCRLF[] = "=\r\n";
25 20
26/*=========================================================================*\ 21/*=========================================================================*\
27* Internal function prototypes. 22* Internal function prototypes.
@@ -121,9 +116,9 @@ static int mime_global_wrp(lua_State *L)
121 luaL_buffinit(L, &buffer); 116 luaL_buffinit(L, &buffer);
122 while (input < last) { 117 while (input < last) {
123 switch (*input) { 118 switch (*input) {
124 case CR: 119 case '\r':
125 break; 120 break;
126 case LF: 121 case '\n':
127 luaL_addstring(&buffer, CRLF); 122 luaL_addstring(&buffer, CRLF);
128 left = length; 123 left = length;
129 break; 124 break;
@@ -327,11 +322,10 @@ static int mime_global_unb64(lua_State *L)
327* all (except CRLF in text) can be =XX 322* all (except CRLF in text) can be =XX
328* CLRL in not text must be =XX=XX 323* CLRL in not text must be =XX=XX
329* 33 through 60 inclusive can be plain 324* 33 through 60 inclusive can be plain
330* 62 through 120 inclusive can be plain 325* 62 through 126 inclusive can be plain
331* 9 and 32 can be plain, unless in the end of a line, where must be =XX 326* 9 and 32 can be plain, unless in the end of a line, where must be =XX
332* encoded lines must be no longer than 76 not counting CRLF 327* encoded lines must be no longer than 76 not counting CRLF
333* soft line-break are =CRLF 328* soft line-break are =CRLF
334* !"#$@[\]^`{|}~ should be =XX for EBCDIC compatibility
335* To encode one byte, we need to see the next two. 329* To encode one byte, we need to see the next two.
336* Worst case is when we see a space, and wonder if a CRLF is comming 330* Worst case is when we see a space, and wonder if a CRLF is comming
337\*-------------------------------------------------------------------------*/ 331\*-------------------------------------------------------------------------*/
@@ -344,16 +338,10 @@ static void qpsetup(UC *qpclass, UC *qpunbase)
344 int i; 338 int i;
345 for (i = 0; i < 256; i++) qpclass[i] = QP_QUOTED; 339 for (i = 0; i < 256; i++) qpclass[i] = QP_QUOTED;
346 for (i = 33; i <= 60; i++) qpclass[i] = QP_PLAIN; 340 for (i = 33; i <= 60; i++) qpclass[i] = QP_PLAIN;
347 for (i = 62; i <= 120; i++) qpclass[i] = QP_PLAIN; 341 for (i = 62; i <= 126; i++) qpclass[i] = QP_PLAIN;
348 qpclass[HT] = QP_IF_LAST; qpclass[SP] = QP_IF_LAST; 342 qpclass['\t'] = QP_IF_LAST;
349 qpclass['!'] = QP_QUOTED; qpclass['"'] = QP_QUOTED; 343 qpclass[' '] = QP_IF_LAST;
350 qpclass['#'] = QP_QUOTED; qpclass['$'] = QP_QUOTED; 344 qpclass['\r'] = QP_CR;
351 qpclass['@'] = QP_QUOTED; qpclass['['] = QP_QUOTED;
352 qpclass['\\'] = QP_QUOTED; qpclass[']'] = QP_QUOTED;
353 qpclass['^'] = QP_QUOTED; qpclass['`'] = QP_QUOTED;
354 qpclass['{'] = QP_QUOTED; qpclass['|'] = QP_QUOTED;
355 qpclass['}'] = QP_QUOTED; qpclass['~'] = QP_QUOTED;
356 qpclass['}'] = QP_QUOTED; qpclass[CR] = QP_CR;
357 for (i = 0; i < 256; i++) qpunbase[i] = 255; 345 for (i = 0; i < 256; i++) qpunbase[i] = 255;
358 qpunbase['0'] = 0; qpunbase['1'] = 1; qpunbase['2'] = 2; 346 qpunbase['0'] = 0; qpunbase['1'] = 1; qpunbase['2'] = 2;
359 qpunbase['3'] = 3; qpunbase['4'] = 4; qpunbase['5'] = 5; 347 qpunbase['3'] = 3; qpunbase['4'] = 4; qpunbase['5'] = 5;
@@ -377,7 +365,7 @@ static void qpquote(UC c, luaL_Buffer *buffer)
377 365
378/*-------------------------------------------------------------------------*\ 366/*-------------------------------------------------------------------------*\
379* Accumulate characters until we are sure about how to deal with them. 367* Accumulate characters until we are sure about how to deal with them.
380* Once we are sure, output the to the buffer, in the correct form. 368* Once we are sure, output to the buffer, in the correct form.
381\*-------------------------------------------------------------------------*/ 369\*-------------------------------------------------------------------------*/
382static size_t qpencode(UC c, UC *input, size_t size, 370static size_t qpencode(UC c, UC *input, size_t size,
383 const char *marker, luaL_Buffer *buffer) 371 const char *marker, luaL_Buffer *buffer)
@@ -389,7 +377,7 @@ static size_t qpencode(UC c, UC *input, size_t size,
389 /* might be the CR of a CRLF sequence */ 377 /* might be the CR of a CRLF sequence */
390 case QP_CR: 378 case QP_CR:
391 if (size < 2) return size; 379 if (size < 2) return size;
392 if (input[1] == LF) { 380 if (input[1] == '\n') {
393 luaL_addstring(buffer, marker); 381 luaL_addstring(buffer, marker);
394 return 0; 382 return 0;
395 } else qpquote(input[0], buffer); 383 } else qpquote(input[0], buffer);
@@ -398,7 +386,7 @@ static size_t qpencode(UC c, UC *input, size_t size,
398 case QP_IF_LAST: 386 case QP_IF_LAST:
399 if (size < 3) return size; 387 if (size < 3) return size;
400 /* if it is the last, quote it and we are done */ 388 /* if it is the last, quote it and we are done */
401 if (input[1] == CR && input[2] == LF) { 389 if (input[1] == '\r' && input[2] == '\n') {
402 qpquote(input[0], buffer); 390 qpquote(input[0], buffer);
403 luaL_addstring(buffer, marker); 391 luaL_addstring(buffer, marker);
404 return 0; 392 return 0;
@@ -492,19 +480,19 @@ static size_t qpdecode(UC c, UC *input, size_t size,
492 case '=': 480 case '=':
493 if (size < 3) return size; 481 if (size < 3) return size;
494 /* eliminate soft line break */ 482 /* eliminate soft line break */
495 if (input[1] == CR && input[2] == LF) return 0; 483 if (input[1] == '\r' && input[2] == '\n') return 0;
496 /* decode quoted representation */ 484 /* decode quoted representation */
497 c = qpunbase[input[1]]; d = qpunbase[input[2]]; 485 c = qpunbase[input[1]]; d = qpunbase[input[2]];
498 /* if it is an invalid, do not decode */ 486 /* if it is an invalid, do not decode */
499 if (c > 15 || d > 15) luaL_addlstring(buffer, (char *)input, 3); 487 if (c > 15 || d > 15) luaL_addlstring(buffer, (char *)input, 3);
500 else luaL_putchar(buffer, (c << 4) + d); 488 else luaL_putchar(buffer, (c << 4) + d);
501 return 0; 489 return 0;
502 case CR: 490 case '\r':
503 if (size < 2) return size; 491 if (size < 2) return size;
504 if (input[1] == LF) luaL_addlstring(buffer, (char *)input, 2); 492 if (input[1] == '\n') luaL_addlstring(buffer, (char *)input, 2);
505 return 0; 493 return 0;
506 default: 494 default:
507 if (input[0] == HT || (input[0] > 31 && input[0] < 127)) 495 if (input[0] == '\t' || (input[0] > 31 && input[0] < 127))
508 luaL_putchar(buffer, input[0]); 496 luaL_putchar(buffer, input[0]);
509 return 0; 497 return 0;
510 } 498 }
@@ -582,9 +570,9 @@ static int mime_global_qpwrp(lua_State *L)
582 luaL_buffinit(L, &buffer); 570 luaL_buffinit(L, &buffer);
583 while (input < last) { 571 while (input < last) {
584 switch (*input) { 572 switch (*input) {
585 case CR: 573 case '\r':
586 break; 574 break;
587 case LF: 575 case '\n':
588 left = length; 576 left = length;
589 luaL_addstring(&buffer, CRLF); 577 luaL_addstring(&buffer, CRLF);
590 break; 578 break;
@@ -623,7 +611,7 @@ static int mime_global_qpwrp(lua_State *L)
623* c is the current character being processed 611* c is the current character being processed
624* last is the previous character 612* last is the previous character
625\*-------------------------------------------------------------------------*/ 613\*-------------------------------------------------------------------------*/
626#define eolcandidate(c) (c == CR || c == LF) 614#define eolcandidate(c) (c == '\r' || c == '\n')
627static int eolprocess(int c, int last, const char *marker, 615static int eolprocess(int c, int last, const char *marker,
628 luaL_Buffer *buffer) 616 luaL_Buffer *buffer)
629{ 617{
diff --git a/src/select.c b/src/select.c
index 8590b96..41bdaa4 100644
--- a/src/select.c
+++ b/src/select.c
@@ -21,7 +21,6 @@ static int meth_set(lua_State *L);
21static int meth_isset(lua_State *L); 21static int meth_isset(lua_State *L);
22static int c_select(lua_State *L); 22static int c_select(lua_State *L);
23static int global_select(lua_State *L); 23static int global_select(lua_State *L);
24static void check_obj_tab(lua_State *L, int tabidx);
25 24
26/* fd_set object methods */ 25/* fd_set object methods */
27static luaL_reg set[] = { 26static luaL_reg set[] = {
@@ -68,9 +67,6 @@ static int global_select(lua_State *L)
68 fd_set *read_fd_set, *write_fd_set; 67 fd_set *read_fd_set, *write_fd_set;
69 /* make sure we have enough arguments (nil is the default) */ 68 /* make sure we have enough arguments (nil is the default) */
70 lua_settop(L, 3); 69 lua_settop(L, 3);
71 /* check object tables */
72 check_obj_tab(L, 1);
73 check_obj_tab(L, 2);
74 /* check timeout */ 70 /* check timeout */
75 if (!lua_isnil(L, 3) && !lua_isnumber(L, 3)) 71 if (!lua_isnil(L, 3) && !lua_isnumber(L, 3))
76 luaL_argerror(L, 3, "number or nil expected"); 72 luaL_argerror(L, 3, "number or nil expected");
@@ -127,24 +123,3 @@ static int c_select(lua_State *L)
127 timeout < 0 ? NULL : &tv)); 123 timeout < 0 ? NULL : &tv));
128 return 1; 124 return 1;
129} 125}
130
131static void check_obj_tab(lua_State *L, int tabidx)
132{
133 if (tabidx < 0) tabidx = lua_gettop(L) + tabidx + 1;
134 if (lua_istable(L, tabidx)) {
135 lua_pushnil(L);
136 while (lua_next(L, tabidx) != 0) {
137 if (aux_getgroupudata(L, "select{able}", -1) == NULL) {
138 char msg[45];
139 if (lua_isnumber(L, -2))
140 sprintf(msg, "table entry #%g is invalid",
141 lua_tonumber(L, -2));
142 else
143 sprintf(msg, "invalid entry found in table");
144 luaL_argerror(L, tabidx, msg);
145 }
146 lua_pop(L, 1);
147 }
148 } else if (!lua_isnil(L, tabidx))
149 luaL_argerror(L, tabidx, "table or nil expected");
150}
diff --git a/src/smtp.lua b/src/smtp.lua
index ed8bd15..6ddeaae 100644
--- a/src/smtp.lua
+++ b/src/smtp.lua
@@ -20,6 +20,7 @@ DOMAIN = os.getenv("SERVER_NAME") or "localhost"
20-- default time zone (means we don't know) 20-- default time zone (means we don't know)
21ZONE = "-0000" 21ZONE = "-0000"
22 22
23
23local function shift(a, b, c) 24local function shift(a, b, c)
24 return b, c 25 return b, c
25end 26end
@@ -29,31 +30,66 @@ function stuff()
29 return ltn12.filter.cycle(dot, 2) 30 return ltn12.filter.cycle(dot, 2)
30end 31end
31 32
33---------------------------------------------------------------------------
34-- Low level SMTP API
35-----------------------------------------------------------------------------
36local metat = { __index = {} }
37
38function metat.__index:greet(domain)
39 socket.try(self.tp:check("2.."))
40 socket.try(self.tp:command("EHLO", domain or DOMAIN))
41 return socket.try(self.tp:check("2.."))
42end
43
44function metat.__index:mail(from)
45 socket.try(self.tp:command("MAIL", "FROM:" .. from))
46 return socket.try(self.tp:check("2.."))
47end
48
49function metat.__index:rcpt(to)
50 socket.try(self.tp:command("RCPT", "TO:" .. to))
51 return socket.try(self.tp:check("2.."))
52end
53
54function metat.__index:data(src)
55 socket.try(self.tp:command("DATA"))
56 socket.try(self.tp:check("3.."))
57 socket.try(self.tp:source(src))
58 socket.try(self.tp:send("\r\n.\r\n"))
59 return socket.try(self.tp:check("2.."))
60end
61
62function metat.__index:quit()
63 socket.try(self.tp:command("QUIT"))
64 return socket.try(self.tp:check("2.."))
65end
66
67function metat.__index:close()
68 return socket.try(self.tp:close())
69end
70
32-- send message or throw an exception 71-- send message or throw an exception
33local function send_p(control, mailt) 72function metat.__index:send(mailt)
34 socket.try(control:check("2..")) 73 self:mail(mailt.from)
35 socket.try(control:command("EHLO", mailt.domain or DOMAIN))
36 socket.try(control:check("2.."))
37 socket.try(control:command("MAIL", "FROM:" .. mailt.from))
38 socket.try(control:check("2.."))
39 if type(mailt.rcpt) == "table" then 74 if type(mailt.rcpt) == "table" then
40 for i,v in ipairs(mailt.rcpt) do 75 for i,v in ipairs(mailt.rcpt) do
41 socket.try(control:command("RCPT", "TO:" .. v)) 76 self:rcpt(v)
42 socket.try(control:check("2.."))
43 end 77 end
44 else 78 else
45 socket.try(control:command("RCPT", "TO:" .. mailt.rcpt)) 79 self:rcpt(mailt.rcpt)
46 socket.try(control:check("2.."))
47 end 80 end
48 socket.try(control:command("DATA")) 81 self:data(ltn12.source.chain(mailt.source, stuff()))
49 socket.try(control:check("3.."))
50 socket.try(control:source(ltn12.source.chain(mailt.source, stuff())))
51 socket.try(control:send("\r\n.\r\n"))
52 socket.try(control:check("2.."))
53 socket.try(control:command("QUIT"))
54 socket.try(control:check("2.."))
55end 82end
56 83
84function open(server, port)
85 local tp, error = socket.tp.connect(server or SERVER, port or PORT)
86 if not tp then return nil, error end
87 return setmetatable({tp = tp}, metat)
88end
89
90---------------------------------------------------------------------------
91-- Multipart message source
92-----------------------------------------------------------------------------
57-- returns a hopefully unique mime boundary 93-- returns a hopefully unique mime boundary
58local seqno = 0 94local seqno = 0
59local function newboundary() 95local function newboundary()
@@ -147,13 +183,17 @@ function message(mesgt)
147 return function() return shift(coroutine.resume(co)) end 183 return function() return shift(coroutine.resume(co)) end
148end 184end
149 185
150function send(mailt) 186---------------------------------------------------------------------------
151 local c, e = socket.tp.connect(mailt.server or SERVER, mailt.port or PORT) 187-- High level SMTP API
152 if not c then return nil, e end 188-----------------------------------------------------------------------------
153 local s, e = pcall(send_p, c, mailt) 189send = socket.protect(function(mailt)
154 c:close() 190 local server = mailt.server or SERVER
155 if s then return true 191 local port = mailt.port or PORT
156 else return nil, e end 192 local smtp = socket.try(open(server, port))
157end 193 smtp:greet(mailt.domain or DOMAIN)
194 smtp:send(mailt)
195 smtp:quit()
196 return smtp:close()
197end)
158 198
159return smtp 199return smtp
diff --git a/src/tcp.c b/src/tcp.c
index 46efac2..d0bc957 100644
--- a/src/tcp.c
+++ b/src/tcp.c
@@ -15,6 +15,7 @@
15#include "socket.h" 15#include "socket.h"
16#include "inet.h" 16#include "inet.h"
17#include "options.h" 17#include "options.h"
18#include "base.h"
18#include "tcp.h" 19#include "tcp.h"
19 20
20/*=========================================================================*\ 21/*=========================================================================*\
@@ -40,6 +41,7 @@ static int meth_dirty(lua_State *L);
40/* tcp object methods */ 41/* tcp object methods */
41static luaL_reg tcp[] = { 42static luaL_reg tcp[] = {
42 {"__gc", meth_close}, 43 {"__gc", meth_close},
44 {"__tostring", base_meth_tostring},
43 {"accept", meth_accept}, 45 {"accept", meth_accept},
44 {"bind", meth_bind}, 46 {"bind", meth_bind},
45 {"close", meth_close}, 47 {"close", meth_close},
@@ -58,7 +60,6 @@ static luaL_reg tcp[] = {
58 {"settimeout", meth_settimeout}, 60 {"settimeout", meth_settimeout},
59 {"shutdown", meth_shutdown}, 61 {"shutdown", meth_shutdown},
60 {NULL, NULL} 62 {NULL, NULL}
61
62}; 63};
63 64
64/* socket option handlers */ 65/* socket option handlers */
diff --git a/src/tp.lua b/src/tp.lua
index 9365255..e9e38a1 100644
--- a/src/tp.lua
+++ b/src/tp.lua
@@ -18,22 +18,19 @@ setfenv(1, socket.tp)
18 18
19TIMEOUT = 60 19TIMEOUT = 60
20 20
21-- gets server reply 21-- gets server reply (works for SMTP and FTP)
22local function get_reply(sock) 22local function get_reply(control)
23 local code, current, separator, _ 23 local code, current, separator, _
24 local line, err = sock:receive() 24 local line, err = control:receive()
25 local reply = line 25 local reply = line
26 if err then return nil, err end 26 if err then return nil, err end
27 _, _, code, separator = string.find(line, "^(%d%d%d)(.?)") 27 _, _, code, separator = string.find(line, "^(%d%d%d)(.?)")
28 if not code then return nil, "invalid server reply" end 28 if not code then return nil, "invalid server reply" end
29 if separator == "-" then -- reply is multiline 29 if separator == "-" then -- reply is multiline
30 repeat 30 repeat
31 line, err = sock:receive() 31 line, err = control:receive()
32 if err then return nil, err end 32 if err then return nil, err end
33 _,_, current, separator = string.find(line, "^(%d%d%d)(.)") 33 _,_, current, separator = string.find(line, "^(%d%d%d)(.?)")
34 if not current or not separator then
35 return nil, "invalid server reply"
36 end
37 reply = reply .. "\n" .. line 34 reply = reply .. "\n" .. line
38 -- reply ends with same code 35 -- reply ends with same code
39 until code == current and separator == " " 36 until code == current and separator == " "
@@ -42,60 +39,73 @@ local function get_reply(sock)
42end 39end
43 40
44-- metatable for sock object 41-- metatable for sock object
45local metatable = { __index = {} } 42local metat = { __index = {} }
46 43
47function metatable.__index:check(ok) 44function metat.__index:check(ok)
48 local code, reply = get_reply(self.sock) 45 local code, reply = get_reply(self.control)
49 if not code then return nil, reply end 46 if not code then return nil, reply end
50 if type(ok) ~= "function" then 47 if type(ok) ~= "function" then
51 if type(ok) == "table" then 48 if type(ok) == "table" then
52 for i, v in ipairs(ok) do 49 for i, v in ipairs(ok) do
53 if string.find(code, v) then return code, reply end 50 if string.find(code, v) then return tonumber(code), reply end
54 end 51 end
55 return nil, reply 52 return nil, reply
56 else 53 else
57 if string.find(code, ok) then return code, reply 54 if string.find(code, ok) then return tonumber(code), reply
58 else return nil, reply end 55 else return nil, reply end
59 end 56 end
60 else return ok(code, reply) end 57 else return ok(tonumber(code), reply) end
61end 58end
62 59
63function metatable.__index:command(cmd, arg) 60function metat.__index:command(cmd, arg)
64 if arg then return self.sock:send(cmd .. " " .. arg.. "\r\n") 61 if arg then return self.control:send(cmd .. " " .. arg.. "\r\n")
65 else return self.sock:send(cmd .. "\r\n") end 62 else return self.control:send(cmd .. "\r\n") end
66end 63end
67 64
68function metatable.__index:sink(snk, pat) 65function metat.__index:sink(snk, pat)
69 local chunk, err = sock:receive(pat) 66 local chunk, err = control:receive(pat)
70 return snk(chunk, err) 67 return snk(chunk, err)
71end 68end
72 69
73function metatable.__index:send(data) 70function metat.__index:send(data)
74 return self.sock:send(data) 71 return self.control:send(data)
72end
73
74function metat.__index:receive(pat)
75 return self.control:receive(pat)
76end
77
78function metat.__index:getfd()
79 return self.control:getfd()
80end
81
82function metat.__index:dirty()
83 return self.control:dirty()
75end 84end
76 85
77function metatable.__index:receive(pat) 86function metat.__index:getcontrol()
78 return self.sock:receive(pat) 87 return self.control
79end 88end
80 89
81function metatable.__index:source(src, instr) 90function metat.__index:source(src, instr)
82 while true do 91 while true do
83 local chunk, err = src() 92 local chunk, err = src()
84 if not chunk then return not err, err end 93 if not chunk then return not err, err end
85 local ret, err = self.sock:send(chunk) 94 local ret, err = self.control:send(chunk)
86 if not ret then return nil, err end 95 if not ret then return nil, err end
87 end 96 end
88end 97end
89 98
90-- closes the underlying sock 99-- closes the underlying control
91function metatable.__index:close() 100function metat.__index:close()
92 self.sock:close() 101 self.control:close()
102 return 1
93end 103end
94 104
95-- connect with server and return sock object 105-- connect with server and return control object
96function connect(host, port) 106function connect(host, port)
97 local sock, err = socket.connect(host, port) 107 local control, err = socket.connect(host, port)
98 if not sock then return nil, err end 108 if not control then return nil, err end
99 sock:settimeout(TIMEOUT) 109 control:settimeout(TIMEOUT)
100 return setmetatable({sock = sock}, metatable) 110 return setmetatable({control = control}, metat)
101end 111end
diff --git a/src/udp.c b/src/udp.c
index a0fdcc0..a2dff34 100644
--- a/src/udp.c
+++ b/src/udp.c
@@ -15,6 +15,7 @@
15#include "socket.h" 15#include "socket.h"
16#include "inet.h" 16#include "inet.h"
17#include "options.h" 17#include "options.h"
18#include "base.h"
18#include "udp.h" 19#include "udp.h"
19 20
20/*=========================================================================*\ 21/*=========================================================================*\
@@ -50,6 +51,7 @@ static luaL_reg udp[] = {
50 {"close", meth_close}, 51 {"close", meth_close},
51 {"setoption", meth_setoption}, 52 {"setoption", meth_setoption},
52 {"__gc", meth_close}, 53 {"__gc", meth_close},
54 {"__tostring", base_meth_tostring},
53 {"getfd", meth_getfd}, 55 {"getfd", meth_getfd},
54 {"setfd", meth_setfd}, 56 {"setfd", meth_setfd},
55 {"dirty", meth_dirty}, 57 {"dirty", meth_dirty},
diff --git a/test/ltn12test.lua b/test/ltn12test.lua
index 1c1f3f2..04749a5 100644
--- a/test/ltn12test.lua
+++ b/test/ltn12test.lua
@@ -1,3 +1,5 @@
1sink = ltn12.sink.file(io.open("lixo", "w")) 1a = ltn12.source.file(io.open("luasocket.lua"))
2source = ltn12.source.file(io.open("luasocket", "r")) 2b = ltn12.source.file(io.open("auxiliar.lua"))
3ltn12.pump(source, sink) 3c = ltn12.source.cat(a, b)
4d = ltn12.sink.file(io.stdout)
5socket.try(ltn12.pump.all(c, d))
diff --git a/test/testmesg.lua b/test/testmesg.lua
index 8b33133..796977e 100644
--- a/test/testmesg.lua
+++ b/test/testmesg.lua
@@ -49,5 +49,6 @@ print(socket.smtp.send {
49 rcpt = "<diego@cs.princeton.edu>", 49 rcpt = "<diego@cs.princeton.edu>",
50 from = "<diego@cs.princeton.edu>", 50 from = "<diego@cs.princeton.edu>",
51 source = socket.smtp.message(mesgt), 51 source = socket.smtp.message(mesgt),
52 server = "mail.cs.princeton.edu" 52 server = "mail.iis.com.br",
53 port = 7
53}) 54})