From d455d1707fbdd03592f78a5c5476e90822221359 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 2 Jun 2000 17:55:14 +0000 Subject: [PATCH 001/483] Initial revision --- src/luasocket.c | 1357 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1357 insertions(+) create mode 100644 src/luasocket.c diff --git a/src/luasocket.c b/src/luasocket.c new file mode 100644 index 0000000..027afa3 --- /dev/null +++ b/src/luasocket.c @@ -0,0 +1,1357 @@ +/*=========================================================================*\ +* TCP/IP bind for the Lua language +* Diego Nehab +* 26/11/1999 +* +* Module: LSOCK.C +* +* This module is part of an effort to make the most important features +* of the TCP/IP protocol available for Lua scripts. +* The main intent of the project was the distribution with the CGILua +* toolkit, in which is is used to implement the SMTP client functions. +* The Lua interface to TCP/IP follows the BSD TCP/IP API closely, +* trying to simplify all tasks involved in setting up a client connection +* and simple server connections. +* The provided IO routines, send and receive, follow the Lua style, being +* very similar to the read and write functions found in that language. +* The module implements both a BSD bind and a Winsock2 bind, and has +* been tested on several Unix flavors, as well as Windows 98 and NT. +\*=========================================================================*/ + +/*=========================================================================*\ +* Common include files +\*=========================================================================*/ +#include +#include +#include +#include +#include + +#include + +#define LUA_REENTRANT + +#include +#include +#include + +#include "lsock.h" + +/*=========================================================================*\ +* WinSock2 include files +\*=========================================================================*/ +#ifdef WIN32 +#include +#include + +/*=========================================================================*\ +* BSD include files +\*=========================================================================*/ +#else +/* close function */ +#include +/* fnctnl function and associated constants */ +#include +/* struct timeval and CLK_TCK */ +#include +/* times function and struct tms */ +#include +/* internet protocol definitions */ +#include +#include +/* struct sockaddr */ +#include +/* socket function */ +#include +/* gethostbyname and gethostbyaddr functions */ +#include +/* for some reason, bcopy and it's friends are not defined automatically +** on the IRIX plataforms... */ +#ifdef __sgi +#include +#endif +#endif + +/*=========================================================================*\ +* Datatype compatibilization and some simple changes +\*=========================================================================*/ +#ifndef WIN32 +#define closesocket close /* WinSock2 has a closesock function instead + ** of using the regular close function */ +#define SOCKET int /* it defines a SOCKET type instead of + ** using an integer file descriptor */ +#define INVALID_SOCKET (-1) /* and uses the this macro to represent and + ** invalid socket */ +#ifndef INADDR_NONE /* some unix flavours don't define this */ +#define INADDR_NONE (-1) +#endif +#ifndef CLK_TCK /* SunOS, for instance, does not define */ +#define CLK_TCK 60 /* CLK_TCK */ +#endif +#endif + +/*=========================================================================*\ +* Module definitions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* The send and receive function can return one of the following return +* codes. The values are mapped into Lua values by the function +* push_error. +\*-------------------------------------------------------------------------*/ +#define NET_DONE -1 /* operation completed successfully */ +#define NET_TIMEOUT 0 /* operation timed out */ +#define NET_CLOSED 1 /* the connection has been closed */ + +/*-------------------------------------------------------------------------*\ +* As far as a Lua script is concerned, there are two kind of objects +* representing a socket. A client socket is an object created by the +* function connect, and implementing the methods send, receive, timeout +* and close. A server socket is an object created by the function bind, +* and implementing the methods listen, accept and close. Lua tag values +* for these objects are created in the lua_socklibopen function, and +* passed as closure values (first argumnents to every library function, +# because we can't have any global variables. +\*-------------------------------------------------------------------------*/ +#define CLIENT_TAG 1 +#define SERVER_TAG 2 +#define FIRST_PAR 3 + +/*-------------------------------------------------------------------------*\ +* Both socket types are stored in the same structure to simplify +* implementation. The tag value used is different, though. The timeout +* fields are not used for the server socket object. +* There are two timeout values. The block timeout specifies the maximum +* time the any IO operation performed by luasock can be blocked waiting +* for completion. The return timeout specifies the maximum time a Lua script +* can be blocked waiting for an luasock IO operation to complete. +\*-------------------------------------------------------------------------*/ +typedef struct t_sock { + SOCKET sock; /* operating system socket object */ + int b; /* block timeout in ms */ + int r; /* return timeout in ms */ + int blocking; /* is this socket in blocking mode? */ +} t_sock; +typedef t_sock *p_sock; + +/*-------------------------------------------------------------------------*\ +* Macros and internal declarations +\*-------------------------------------------------------------------------*/ +/* internal function prototypes */ +#include "sock.h" + +/* return time since marked start in ms */ +#define time_since(start) (get_time()-start) + +/* 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 + +/*=========================================================================*\ +* Test support functions +\*=========================================================================*/ +#ifdef _DEBUG +/*-------------------------------------------------------------------------*\ +* Returns the time the system has been up, in secconds. +\*-------------------------------------------------------------------------*/ +static void net_time(lua_State *L); +static void net_time(lua_State *L) +{ + lua_pushnumber(L, get_time()/1000.0); +} + +/*-------------------------------------------------------------------------*\ +* Causes a Lua script to sleep for the specified number of secconds +\*-------------------------------------------------------------------------*/ +static void net_sleep(lua_State *L); +static void net_sleep(lua_State *L) +{ + int sec = (int) luaL_check_number(L, 1); +#ifdef WIN32 + Sleep(1000*sec); +#else + sleep(sec); +#endif +} + +#endif + +/*=========================================================================*\ +* Lua exported functions +* These functions can be accessed from a Lua script. +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a client socket and returns it to the Lua script. The timeout +* values are initialized as -1 so that the socket will block at any +* IO operation. +* Input +* host: host name or ip address to connect to +* port: port number on host +* Returns +* On success: client socket +* On error: nil, followed by an error message +\*-------------------------------------------------------------------------*/ +static void net_connect(lua_State *L) +{ + const char *hostname = luaL_check_string(L, FIRST_PAR); + unsigned short port = (unsigned short) luaL_check_number(L, FIRST_PAR+1); + struct sockaddr_in server; + p_sock sock = create_tcpsock(); + if (!sock) { + lua_pushnil(L); + lua_pushstring(L, sock_strerror()); + } + /* fills the sockaddr structure with the information needed to + ** connect our socket with the remote host */ + if (!fill_sockaddr(&server, hostname, port)) { + free(sock); + lua_pushnil(L); + lua_pushstring(L, host_strerror()); + return; + } + if (connect(sock->sock,(struct sockaddr *)&server,sizeof(server)) < 0) { + /* no connection? we close the socket to free the descriptor */ + closesocket(sock->sock); + lua_pushnil(L); + lua_pushstring(L, connect_strerror()); + return; + } + push_client(L, sock); + /* no need to push second return value, since it would be nil anyways */ +} + +/*-------------------------------------------------------------------------*\ +* Specifies the number of connections that can be queued on a server +* socket. +* Input +* sock: server socket created by the bind function +* Returns +* On success: nil +* On error: an error message +\*-------------------------------------------------------------------------*/ +static void net_listen(lua_State *L) +{ + p_sock sock = check_server(L, FIRST_PAR); + unsigned int backlog = (unsigned int) luaL_check_number(L, FIRST_PAR+1); + + if (listen(sock->sock, backlog) < 0) + lua_pushstring(L, "listen error"); + /* no need to push return value, since it would be nil anyways */ +} + +/*-------------------------------------------------------------------------*\ +* Returns a client socket attempting to connect to a server socket. +* The function blocks until a client shows up. +* Input +* sock: server socket created by the bind function +* Returns +* On success: client socket attempting connection +* On error: nil followed by an error message +\*-------------------------------------------------------------------------*/ +static void net_accept(lua_State *L) +{ + struct sockaddr_in client_addr; + p_sock server = check_server(L, FIRST_PAR); + int client_sock = -1; + unsigned int client_len = sizeof(client_addr); + p_sock client; + /* waits for a connection */ + client_sock = accept(server->sock, (struct sockaddr *) &client_addr, + &client_len); + /* we create and return a client socket object, passing the received + ** socket to Lua, as a client socket */ + client = create_sock(); + if (!client) { + lua_pushnil(L); + lua_pushstring(L, "out of memory"); + return; + } + client->sock = client_sock; + push_client(L, client); +} + +/*-------------------------------------------------------------------------*\ +* Associates an address to a server socket. +* Input +* host: host name or ip address to bind to +* port: port to bind to +* backlog: optional parameter specifying the number of connections +* to keep waiting before refuse a connection. the default value is 1. +* Returns +* On success: server socket bound to address +* On error: nil, followed by an error message +\*-------------------------------------------------------------------------*/ +static void net_bind(lua_State *L) +{ + const char *hostname = luaL_check_string(L, FIRST_PAR); + unsigned short port = (unsigned short) luaL_check_number(L, FIRST_PAR+1); + unsigned int backlog = (unsigned int) luaL_opt_number(L, FIRST_PAR+2, 1.0); + struct sockaddr_in server; + p_sock sock = create_tcpsock(); + if (!sock) { + lua_pushnil(L); + lua_pushstring(L, sock_strerror()); + } + /* fills the sockaddr structure with the information needed to + ** connect our socket with local address */ + if (!fill_sockaddr(&server, hostname, port)) { + free(sock); + lua_pushnil(L); + lua_pushstring(L, host_strerror()); + return; + } + if (bind(sock->sock,(struct sockaddr *)&server,sizeof(server)) < 0) { + lua_pushnil(L); + lua_pushstring(L, bind_strerror()); + return; + } + /* define the connection waiting queue length */ + if (listen(sock->sock, backlog) < 0) { + lua_pushnil(L); + lua_pushstring(L, "listen error"); + return; + } + /* pass the created socket to Lua, as a server socket */ + push_server(L, sock); + /* no need to push return value, since it would be nil anyways */ +} + +/*-------------------------------------------------------------------------*\ +* Sets timeout values for IO operations on a client socket +* Input +* sock: client socket created by the connect function +* time: time out value in seconds +* mode: optional timeout mode. "block" specifies the upper bound on +* the time any IO operation on sock can cause the program to block. +* "return" specifies the upper bound on the time elapsed before the +* function returns control to the script. "block" is the default. +* Returns +* no return value +\*-------------------------------------------------------------------------*/ +static void net_timeout(lua_State *L) +{ + p_sock sock = check_client(L, FIRST_PAR); + int ms = (int) (luaL_check_number(L, FIRST_PAR+1)*1000.0); + const char *mode = luaL_opt_string(L, FIRST_PAR+2, "b"); + switch (*mode) { + case 'b': + sock->b = ms; + break; + case 'r': + sock->r = ms; + break; + default: + luaL_arg_check(L, 0, FIRST_PAR+2, "invalid timeout mode"); + break; + } +} + +/*-------------------------------------------------------------------------*\ +* Send data through a socket +* Input: sock, a_1 [, a_2, a_3 ... a_n] +* sock: client socket created by the connect function +* a_i: strings to be sent. The strings will be sent on the order they +* appear as parameters +* Returns +* On success: nil, followed by the total number of bytes sent +* On error: NET_TIMEOUT if the connection timedout, or NET_CLOSED if +* the connection has been closed +\*-------------------------------------------------------------------------*/ +static void net_send(lua_State *L) +{ + p_sock sock = check_client(L, FIRST_PAR); + const char *data; + long size; + int start = get_time(); + long total = 0; + int arg = FIRST_PAR+1; + int err = NET_DONE; + int end; +#ifdef _DEBUG_BLOCK +printf("luasock: send start\n"); +#endif + while ((data = luaL_opt_lstr(L, arg++, NULL, &size)) && err == NET_DONE) + total += send_raw(sock, data, size, start, &err, &end); + push_error(L, err); + lua_pushnumber(L, (double) total); +#ifdef _DEBUG_BLOCK +printf("luasock: send end\n"); +#endif +#ifdef _DEBUG +/* pass the time elapsed during function execution to Lua, so that +** the test script can make sure we respected the timeouts */ +lua_pushnumber(L, (end-start)/1000.0); +#endif +} + +/*-------------------------------------------------------------------------*\ +* Receive data from a socket +* Input: sock [pat_1, pat_2 ... pat_n] +* sock: client socket created by the connect function +* pat_i: may be one of the following +* "*l": reads a text line, defined as a string of caracters terminates +* by a LF character, preceded or not by a CR character. This is +* the default pattern +* "*lu": reads a text line, terminanted by a CR character only. (Unix mode) +* number: reads 'number' characters from the socket +* Returns +* On success: one string for each pattern +* On error: all strings for which there was no error, followed by on +* nil value for each failed string, followed by an error code +\*-------------------------------------------------------------------------*/ +static void net_receive(lua_State *L) +{ + static const char *const modenames[] = {"*l", "*lu", NULL}; + p_sock sock = check_client(L, FIRST_PAR); + int err = NET_DONE, arg = FIRST_PAR+1, got = 0; + char *data = NULL; + int start = get_time(); + const char *mode = luaL_opt_string(L, arg++, "*l"); + int end; +#ifdef _DEBUG_BLOCK +printf("luasock: receive start\n"); +#endif + do { + /* if one pattern failed, we just skip all other patterns */ + if (err != NET_DONE) { + lua_pushnil(L); + continue; + } + /* get next pattern */ + switch (luaL_findstring(mode, modenames)) { + /* DOS line mode */ + case 0: + got = receive_dosline(L, sock, &data, start, &err, &end); + break; + /* Unix line mode */ + case 2: + got = receive_unixline(L, sock, &data, start, &err, &end); + break; + /* else it must be a number, raw mode */ + default: { + long size = (long) luaL_check_number(L, arg-1); + got = receive_raw(L, sock, size, &data, start, &err, &end); + break; + } + } + /* pass result */ + lua_pushlstring(L, data, got); + } while ((mode = luaL_opt_string(L, arg++, NULL))); + /* last return is an error code */ + push_error(L, err); +#ifdef _DEBUG_BLOCK +printf("luasock: receive end\n"); +#endif +#ifdef _DEBUG +/* pass the time elapsed during function execution to Lua, so that +** the test script can make sure we respected the timeouts */ +lua_pushnumber(L, (end-start)/1000.0); +#endif +} + +/*-------------------------------------------------------------------------*\ +* Closes a socket. +* Input +* sock: socket to be closed +\*-------------------------------------------------------------------------*/ +static void net_close(lua_State *L) +{ + p_sock sock = check_sock(L, FIRST_PAR); + closesocket(sock->sock); + /* set value to -1 so that we can later detect the use of a + ** closed socket */ + sock->sock = -1; +} + +/*-------------------------------------------------------------------------*\ +* Gettable fallback for the client socket. This function provides the +* alternative interface client:receive, client:send etc for the client +* socket methods. +\*-------------------------------------------------------------------------*/ +static void client_gettable(lua_State *L) +{ + static const char *const net_api[] = {"receive","send","timeout","close", + "connect", NULL}; + const char *idx = luaL_check_string(L, FIRST_PAR+1); + switch (luaL_findstring(idx, net_api)) { + case 0: + lua_pushnumber(L, get_tag(L, CLIENT_TAG)); + lua_pushnumber(L, get_tag(L, SERVER_TAG)); + lua_pushcclosure(L, net_receive, 2); + break; + case 1: + lua_pushnumber(L, get_tag(L, CLIENT_TAG)); + lua_pushnumber(L, get_tag(L, SERVER_TAG)); + lua_pushcclosure(L, net_send, 2); + break; + case 2: + lua_pushnumber(L, get_tag(L, CLIENT_TAG)); + lua_pushnumber(L, get_tag(L, SERVER_TAG)); + lua_pushcclosure(L, net_timeout, 2); + break; + case 3: + lua_pushnumber(L, get_tag(L, CLIENT_TAG)); + lua_pushnumber(L, get_tag(L, SERVER_TAG)); + lua_pushcclosure(L, net_close, 2); + break; + default: + lua_pushnil(L); + break; + } +} + +/*-------------------------------------------------------------------------*\ +* Gettable fallback for the server socket. This function provides the +* alternative interface server:listen, server:accept etc for the server +* socket methods. +\*-------------------------------------------------------------------------*/ +static void server_gettable(lua_State *L) +{ + static const char *const net_api[] = {"listen","accept","close", NULL}; + const char *idx = luaL_check_string(L, FIRST_PAR+1); + switch (luaL_findstring(idx, net_api)) { + case 0: + lua_pushnumber(L, get_tag(L, CLIENT_TAG)); + lua_pushnumber(L, get_tag(L, SERVER_TAG)); + lua_pushcclosure(L, net_listen, 2); + break; + case 1: + lua_pushnumber(L, get_tag(L, CLIENT_TAG)); + lua_pushnumber(L, get_tag(L, SERVER_TAG)); + lua_pushcclosure(L, net_accept, 2); + break; + case 2: + lua_pushnumber(L, get_tag(L, CLIENT_TAG)); + lua_pushnumber(L, get_tag(L, SERVER_TAG)); + lua_pushcclosure(L, net_close, 2); + break; + default: + lua_pushnil(L); + break; + } +} + +/*-------------------------------------------------------------------------*\ +* Garbage collection fallback for the socket objects. This function +* makes sure that all collected sockets are closed and that the memory +* used by the C structure t_sock is properly released. +\*-------------------------------------------------------------------------*/ +static void sock_gc(lua_State *L) +{ + p_sock sock = check_sock(L, FIRST_PAR); + if (sock->sock >= 0) + closesocket(sock->sock); + free(sock); +} + +/*=========================================================================*\ +* Internal functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Instals a handler to ignore sigpipe. That is, unless the signal had +* already been redefined. This function is not needed on the WinSock2, +* since it's sockets don't raise this signal. +\*-------------------------------------------------------------------------*/ +#ifndef WIN32 +static void handle_sigpipe(void); +static void handle_sigpipe(void) +{ + struct sigaction old, new; + bzero(&new, sizeof new); + new.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &new, &old); + /* test if the signal had been before, and restore it if so */ + if (old.sa_handler != SIG_DFL) { +#ifdef _DEBUG +/* this is a somewhat dangerous situation. we can only hope the +** installed signal handler understands that this signal can be +** raised by a socket operation */ +printf("SIGPIPE ALREADY REDEFINED!!!\n"); +#endif + sigaction(SIGPIPE, &old, NULL); + } +} +#endif + +/*-------------------------------------------------------------------------*\ +* Creates a t_sock structure with default values. +\*-------------------------------------------------------------------------*/ +static p_sock create_sock(void) +{ + p_sock sock = (p_sock) malloc(sizeof(t_sock)); + if (!sock) + return NULL; + sock->sock = -1; + sock->r = -1; + sock->b = -1; + sock->blocking = 1; + return sock; +} + +/*-------------------------------------------------------------------------*\ +* Creates a TCP/IP socket. +* Returns +* A pointer to a t_sock structure or NULL in case of error +\*-------------------------------------------------------------------------*/ +static p_sock create_tcpsock(void) +{ + p_sock sock = create_sock(); + if (!sock) + return NULL; + sock->sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock->sock < 0) { + free(sock); + sock = NULL; + } +#ifdef _DEBUG +/* this allow us to re-bind onto an address even if there is still +** a TIME_WAIT condition. debugging is much more confortable, because +** we don't get "address already in use" errors all the time we +** re-run the program before the OS is ready. in real life, though +** there could be data pending on the socket and this could lead to +** some weird errors. */ +{ + int val = 1; + setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, (char *) &val, + sizeof(val)); +} +#endif + return sock; +} + +/*-------------------------------------------------------------------------*\ +* Fills a sockaddr structure according to a given host name of ip +* address and a port number. +* Input +* address: pointer to sockaddr structure to be filled +* hostname: host name or ip address +* port: port number +* Returns +* 1 in case of success, 0 otherwise +\*-------------------------------------------------------------------------*/ +static int fill_sockaddr(struct sockaddr_in *address, const char *hostname, + unsigned short port) +{ + struct hostent *host = NULL; + unsigned long addr = inet_addr(hostname); + /* BSD says we could have used gethostbyname even if the hostname is + ** in ip address form, but WinSock2 says we can't. Therefore we + ** choose a method that works on both plataforms */ + if (addr == INADDR_NONE) + host = gethostbyname(hostname); + else + host = gethostbyaddr((char * ) &addr, sizeof(unsigned long), AF_INET); + if (!host) + return 0; + memset(address, 0, sizeof(struct sockaddr_in)); + address->sin_family = AF_INET; + address->sin_port = htons(port); + memcpy(&(address->sin_addr), host->h_addr, (unsigned) host->h_length); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Determine the time limit to be passed to the select function, given +* the time elapsed since the beginning of the operation. +* Input +* sock: socket structure being used in operation +* elapsed: time elapsed since operation started +* Returns +* time limit before function return in ms or -1 in case there is no +* time limit +\*-------------------------------------------------------------------------*/ +static int get_timeout(p_sock sock, int elapsed) +{ + /* no timeout */ + if (sock->b < 0 && sock->r < 0) + return -1; + /* there is no block timeout, we use the return timeout */ + if (sock->b < 0) + return max(0, sock->r - elapsed); + /* there is no return timeout, we use the block timeout */ + else if (sock->r < 0) + return sock->b; + /* both timeouts are specified */ + else + return min(sock->b, max(0, sock->r - elapsed)); +} + +/*-------------------------------------------------------------------------*\ +* Determines if we have a timeout condition or if we can proceed with +* an IO read operation. +* Input +* sock: socket structure being used in operation +* elapsed: time elapsed since operation started +* Returns +* 1 if we can proceed, 0 if a timeou has occured +\*-------------------------------------------------------------------------*/ +static int read_or_timeout(p_sock sock, int elapsed) +{ + fd_set set; /* file descriptor set */ + struct timeval to; /* timeout structure */ + int ms = get_timeout(sock, elapsed); + int err; + /* got timeout */ + if (ms == 0) + return 0; + FD_ZERO(&set); + FD_SET(sock->sock, &set); + /* we have a limit on the time we can wait */ + if (ms > 0) { + to.tv_sec = ms / 1000; + to.tv_usec = (ms % 1000) * 1000; + err = select(sock->sock+1, &set, NULL, NULL, &to); + set_nonblocking(sock); + /* we can wait forever */ + } else { + err = select(sock->sock+1, &set, NULL, NULL, NULL); + set_blocking(sock); + } + return (err > 0); +} + +/*-------------------------------------------------------------------------*\ +* Determines if we have a timeout condition or if we can proceed with +* an IO write operation. +* Input +* sock: socket structure being used in operation +* elapsed: time elapsed since operation started +* Returns +* 1 if we can proceed, 0 if a timeou has occured +\*-------------------------------------------------------------------------*/ +static int write_or_timeout(p_sock sock, int elapsed) +{ + fd_set set; /* file descriptor set */ + struct timeval to; /* timeout structure */ + int ms = get_timeout(sock, elapsed); + int err; + /* got timeout */ + if (ms == 0) + return 0; + FD_ZERO(&set); + FD_SET(sock->sock, &set); + /* we have a limit on the time we can wait */ + if (ms > 0) { + to.tv_sec = ms / 1000; + to.tv_usec = (ms % 1000) * 1000; + err = select(sock->sock+1, NULL, &set, NULL, &to); + set_nonblocking(sock); + /* we can wait forever */ + } else { + err = select(sock->sock+1, NULL, &set, NULL, NULL); + set_blocking(sock); + } + return (err > 0); +} + +/*-------------------------------------------------------------------------*\ +* Sends a raw block of data through a socket. The operations are all +* non-blocking and the function respects the timeout values in sock. +* Input +* sock: socket structure being used in operation +* data: buffer to be sent +* wanted: number of bytes in buffer +* start: time the operation started, in ms +* Output +* err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED +* Returns +* Number of bytes written +\*-------------------------------------------------------------------------*/ +static int send_raw(p_sock sock, const char *data, int wanted, + int start, int *err, int *end) +{ + int put = 0, total = 0; + *end = start; + while (wanted > 0) { + if(!write_or_timeout(sock, time_since(start))) { +#ifdef _DEBUG +*end = get_time(); +#endif + *err = NET_TIMEOUT; + return total; + } +#ifdef _DEBUG +/* the lua_pushlstring function can take a long time to pass a large block +** to Lua, therefore, we mark the time before passing the result. +** also, the call to write of read might take longer then the time we had +** left, so that the end of the operation is marked before the last call +** to the OS */ +*end = get_time(); +#endif + put = send(sock->sock, data, wanted, 0); + if (put <= 0) { +#ifdef WIN32 + /* on WinSock, a select over a socket on which there is a + ** non-blocking operation pending returns immediately, even + ** if the call would block. therefore, we have to do a busy + ** wait here. */ + if (WSAGetLastError() == WSAEWOULDBLOCK) + continue; +#endif + *err = NET_CLOSED; + return total; + } +#ifdef _DEBUG_BLOCK +printf("luasock: sent %d bytes, %dms elapsed\n", put, time_since(start)); +#endif + wanted -= put; + data += put; + total += put; + } + *err = NET_DONE; + return total; +} + +/*-------------------------------------------------------------------------*\ +* Reads a raw block of data from a socket. The operations are all +* non-blocking and the function respects the timeout values in sock. +* Input +* sock: socket structure being used in operation +* wanted: number of bytes to be read +* start: time the operation started, in ms +* Output +* data: pointer to an internal buffer containing the data read +* err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED +* Returns +* Number of bytes read +\*-------------------------------------------------------------------------*/ +static int receive_raw(lua_State *L, p_sock sock, int wanted, char **data, + int start, int *err, int *end) +{ + int got = 0; + char *buffer = NULL; + *end = start; + luaL_resetbuffer(L); + while (wanted > 0) { + buffer = luaL_openspace(L, wanted); + if(!read_or_timeout(sock, time_since(start))) { +#ifdef _DEBUG +*end = get_time(); +#endif + *data = luaL_buffer(L); + *err = NET_TIMEOUT; + return luaL_getsize(L); + } +#ifdef _DEBUG +*end = get_time(); +#endif + got = recv(sock->sock, buffer, wanted, 0); +#ifdef _DEBUG_BLOCK +printf("luasock: wanted %d, got %d, %dms elapsed\n", wanted, got, time_since(start)); +#endif + if (got <= 0) { + *data = luaL_buffer(L); + *err = NET_CLOSED; + return luaL_getsize(L); + } + wanted -= got; + luaL_addsize(L,got); + } + *data = luaL_buffer(L); + *err = NET_DONE; + return luaL_getsize(L); +} + +/*-------------------------------------------------------------------------*\ +* Reads a line terminated by a CR LF pair or just by a LF. The CR and LF +* are not returned by the function. All operations are non-blocking and the +* function respects the timeout values in sock. +* Input +* sock: socket structure being used in operation +* wanted: number of bytes in buffer +* start: time the operation started, in ms +* Output +* data: pointer to an internal buffer containing the data read +* err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED +* Returns +* Number of bytes read +\*-------------------------------------------------------------------------*/ +static long receive_dosline(lua_State *L, p_sock sock, char **data, + int start, int *err, int *end) +{ + char c = ' '; + long got = 0; + *end = start; + luaL_resetbuffer(L); + while (c != '\n') { + if (read_or_timeout(sock, time_since(start))) { +#ifdef _DEBUG +*end = get_time(); +#endif + got = recv(sock->sock, &c, 1, 0); + if (got <= 0) { + *err = NET_CLOSED; + *data = luaL_buffer(L); + return luaL_getsize(L); + } + luaL_addchar(L, c); + } else { + *err = NET_TIMEOUT; + *data = luaL_buffer(L); + return luaL_getsize(L); + } + } + *err = NET_DONE; + *data = luaL_buffer(L); + got = luaL_getsize(L); + if ((*data)[got-2] == '\r') return got-2; + else return got-1; +} + +/*-------------------------------------------------------------------------*\ +* Reads a line terminated by a LF character, which is not returned by +* the function. All operations are non-blocking and the function respects +* the timeout values in sock. +* Input +* sock: socket structure being used in operation +* wanted: number of bytes in buffer +* start: time the operation started, in ms +* Output +* data: pointer to an internal buffer containing the data read +* err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED +* Returns +* Number of bytes read +\*-------------------------------------------------------------------------*/ +static long receive_unixline(lua_State *L, p_sock sock, char **data, + int start, int *err, int *end) +{ + char c = ' '; + *end = start; + luaL_resetbuffer(L); + while (c != '\n') { + if (read_or_timeout(sock, time_since(start))) { +#ifdef _DEBUG +*end = get_time(); +#endif + if (recv(sock->sock, &c, 1, 0) <= 0) { + *err = NET_CLOSED; + *data = luaL_buffer(L); + return luaL_getsize(L); + } + luaL_addchar(L, c); + } else { + *err = NET_TIMEOUT; + *data = luaL_buffer(L); + return luaL_getsize(L); + } + } + *err = NET_DONE; + *data = luaL_buffer(L); + return luaL_getsize(L) - 1; +} + +/*-------------------------------------------------------------------------*\ +* Gets a tag from a closure parameter +* Input +* L: lua environment +* par: parameter number +\*-------------------------------------------------------------------------*/ +static int get_tag(lua_State *L, int par) +{ + return (int) lua_getnumber(L, lua_getparam(L, par)); +} + +/*-------------------------------------------------------------------------*\ +* Passes an error code to Lua. The NET_DONE error is translated to nil. +* Input +* err: error code to be passed to Lua +\*-------------------------------------------------------------------------*/ +static void push_error(lua_State *L, int err) +{ + switch (err) { + case NET_DONE: + lua_pushnil(L); + break; + case NET_TIMEOUT: + lua_pushstring(L, "timeout"); + break; + case NET_CLOSED: + lua_pushstring(L, "closed"); + break; + } +} + +/*-------------------------------------------------------------------------*\ +* Passes a client socket to Lua. +* Must be called from a closure receiving the socket tags as its +* parameters. +* Input +* L: lua environment +* sock: pointer to socket structure to be used +\*-------------------------------------------------------------------------*/ +static void push_client(lua_State *L, p_sock sock) +{ + lua_pushusertag(L, (void *) sock, get_tag(L, CLIENT_TAG)); +} + +/*-------------------------------------------------------------------------*\ +* Passes a server socket to Lua. +* Must be called from a closure receiving the socket tags as its +* parameters. +* Input +* L: lua environment +* sock: pointer to socket structure to be used +\*-------------------------------------------------------------------------*/ +static void push_server(lua_State *L, p_sock sock) +{ + lua_pushusertag(L, (void *) sock, get_tag(L, SERVER_TAG)); +} + +/*=========================================================================*\ +* WinSock2 specific functions. +\*=========================================================================*/ +#ifdef WIN32 +/*-------------------------------------------------------------------------*\ +* Initializes WinSock2 library. +* Returns +* 1 in case of success. 0 in case of error. +\*-------------------------------------------------------------------------*/ +static int wsock_open(void) +{ + WORD wVersionRequested;WSADATA wsaData;int err; + wVersionRequested = MAKEWORD( 2, 0 ); + err = WSAStartup( wVersionRequested, &wsaData ); + if ( err != 0 ) { + return 0; + } + if ( LOBYTE( wsaData.wVersion ) != 2 || + HIBYTE( wsaData.wVersion ) != 0 ) { + WSACleanup( ); + return 0; + } + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Gets time in ms, relative to system startup. +* Returns +* time in ms. +\*-------------------------------------------------------------------------*/ +static int get_time(void) +{ + return GetTickCount(); +} + +/*-------------------------------------------------------------------------*\ +* Put socket into blocking mode. +\*-------------------------------------------------------------------------*/ +static void set_blocking(p_sock sock) +{ + u_long argp = 0; + if (!sock->blocking) { + ioctlsocket(sock->sock, FIONBIO, &argp); + sock->blocking = 1; + } +} + +/*-------------------------------------------------------------------------*\ +* Put socket into non-blocking mode. +\*-------------------------------------------------------------------------*/ +static void set_nonblocking(p_sock sock) +{ + u_long argp = 1; + if (sock->blocking) { + ioctlsocket(sock->sock, FIONBIO, &argp); + sock->blocking = 0; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last host manipulation error. +\*-------------------------------------------------------------------------*/ +static char *host_strerror(void) +{ + switch (WSAGetLastError()) { + case HOST_NOT_FOUND: return "host not found"; + case NO_ADDRESS: return "unable to resolve host name"; + case NO_RECOVERY: return "name server error"; + case TRY_AGAIN: return "name server unavailable, try again later."; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last socket manipulation error. +\*-------------------------------------------------------------------------*/ +static char *sock_strerror(void) +{ + switch (WSAGetLastError()) { + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAEMFILE: return "descriptor table is full"; + case WSAENOBUFS: return "insufficient buffer space"; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last bind operation error. +\*-------------------------------------------------------------------------*/ +static char *bind_strerror(void) +{ + switch (WSAGetLastError()) { + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAEADDRINUSE: return "address already in use"; + case WSAEINVAL: return "socket already bound"; + case WSAENOBUFS: return "too many connections"; + case WSAEFAULT: return "invalid address"; + case WSAENOTSOCK: return "not a socket descriptor"; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last connect operationerror. +\*-------------------------------------------------------------------------*/ +static char *connect_strerror(void) +{ + switch (WSAGetLastError()) { + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAEADDRINUSE: return "address already in use"; + case WSAEADDRNOTAVAIL: return "address unavailable"; + case WSAECONNREFUSED: return "connection refused"; + case WSAENETUNREACH: return "network is unreachable"; + default: return "unknown error"; + } +} +#else + +/*=========================================================================*\ +* BSD specific functions. +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Gets time in ms, relative to system startup. +* Returns +* time in ms. +\*-------------------------------------------------------------------------*/ +static int get_time(void) +{ + struct tms t; + return (times(&t)*1000)/CLK_TCK; +} + +/*-------------------------------------------------------------------------*\ +* Put socket into blocking mode. +\*-------------------------------------------------------------------------*/ +static void set_blocking(p_sock sock) +{ + if (!sock->blocking) { + int flags = fcntl(sock->sock, F_GETFL, 0); + flags &= (~(O_NONBLOCK)); + fcntl(sock->sock, F_SETFL, flags); + sock->blocking = 1; + } +} + +/*-------------------------------------------------------------------------*\ +* Put socket into non-blocking mode. +\*-------------------------------------------------------------------------*/ +static void set_nonblocking(p_sock sock) +{ + if (sock->blocking) { + int flags = fcntl(sock->sock, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(sock->sock, F_SETFL, flags); + sock->blocking = 0; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last host manipulation error. +\*-------------------------------------------------------------------------*/ +static char *host_strerror(void) +{ + switch (h_errno) { + case HOST_NOT_FOUND: return "host not found"; + case NO_ADDRESS: return "unable to resolve host name"; + case NO_RECOVERY: return "name server error"; + case TRY_AGAIN: return "name server unavailable, try again later"; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last socket manipulation error. +\*-------------------------------------------------------------------------*/ +static char *sock_strerror(void) +{ + switch (errno) { + case EACCES: return "access denied"; + case EMFILE: return "descriptor table is full"; + case ENFILE: return "too many open files"; + case ENOBUFS: return "insuffucient buffer space"; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last bind command error. +\*-------------------------------------------------------------------------*/ +static char *bind_strerror(void) +{ + switch (errno) { + case EBADF: return "invalid descriptor"; + case EINVAL: return "socket already bound"; + case EACCES: return "access denied"; + case ENOTSOCK: return "not a socket descriptor"; + case EADDRINUSE: return "address already in use"; + case EADDRNOTAVAIL: return "address unavailable"; + case ENOMEM: return "out of memory"; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last connect error. +\*-------------------------------------------------------------------------*/ +static char *connect_strerror(void) +{ + switch (errno) { + case EBADF: return "invalid descriptor"; + case ENOTSOCK: return "not a socket descriptor"; + case EADDRNOTAVAIL: return "address not availabe"; + case ETIMEDOUT: return "connection timed out"; + case ECONNREFUSED: return "connection refused"; + case EACCES: return "access denied"; + case ENETUNREACH: return "network is unreachable"; + case EADDRINUSE: return "address already in use"; + default: return "unknown error"; + } +} + +#endif + +/*=========================================================================*\ +* Module exported functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Initializes the library interface with Lua and the socket library. +* Defines the symbols exported to Lua. +\*-------------------------------------------------------------------------*/ +void lua_socklibopen(lua_State *L) +{ + int client_tag, server_tag; + static struct luaL_reg funcs[] = { + {"connect", net_connect}, + {"bind", net_bind}, + {"listen", net_listen}, + {"accept", net_accept}, + {"close", net_close}, + {"send", net_send}, + {"receive", net_receive}, + {"timeout", net_timeout} + }; + int i; + +#ifdef WIN32 + wsock_open(); +#endif + /* declare new Lua tags for used userdata values */ + client_tag = lua_newtag(L); + server_tag = lua_newtag(L); + /* Lua exported functions */ + for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { + lua_pushnumber(L, client_tag); + lua_pushnumber(L, server_tag); + lua_pushcclosure(L, funcs[i].func, 2 ); + lua_setglobal(L, funcs[i].name ); + } + /* fallbacks */ + lua_pushnumber(L, client_tag); + lua_pushnumber(L, server_tag); + lua_pushcclosure(L, client_gettable, 2); + lua_settagmethod(L, client_tag, "gettable"); + + lua_pushnumber(L, client_tag); + lua_pushnumber(L, server_tag); + lua_pushcclosure(L, server_gettable, 2); + lua_settagmethod(L, server_tag, "gettable"); + + lua_pushnumber(L, client_tag); + lua_pushnumber(L, server_tag); + lua_pushcclosure(L, sock_gc, 2); + lua_settagmethod(L, client_tag, "gc"); + + lua_pushnumber(L, client_tag); + lua_pushnumber(L, server_tag); + lua_pushcclosure(L, sock_gc, 2); + lua_settagmethod(L, server_tag, "gc"); + + /* avoid stupid compiler warnings */ + (void) set_blocking; + +#ifndef WIN32 + /* avoid getting killed by a SIGPIPE signal */ + handle_sigpipe(); +#endif + +#ifdef _DEBUG +/* test support functions */ +lua_pushcfunction(L, net_sleep); lua_setglobal(L, "sleep"); +lua_pushcfunction(L, net_time); lua_setglobal(L, "time"); +#endif +} + +/*=========================================================================*\ +* Lua2c and c2lua stack auxiliary functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Checks if argument is a client socket, printing an error message in +* case of error +* Input +* numArg: argument position in lua2c stack +* Returns +* pointer to client socket, or doesn't return in case of error +\*-------------------------------------------------------------------------*/ +static p_sock check_client(lua_State *L, int numArg) +{ + p_sock sock; + lua_Object o = lua_getparam(L, numArg); + luaL_arg_check(L, lua_tag(L, o) == get_tag(L, CLIENT_TAG), + numArg, "client socket expected"); + sock = (p_sock) lua_getuserdata(L, o); + if (sock->sock < 0) + lua_error(L, "operation on closed socket"); + return sock; +} + +/*-------------------------------------------------------------------------*\ +* Checks if argument is a server socket, printing an error message in +* case of error +* Input +* numArg: argument position in lua2c stack +* Returns +* pointer to server socket, or doesn't return in case of error +\*-------------------------------------------------------------------------*/ +static p_sock check_server(lua_State *L, int numArg) +{ + p_sock sock; + lua_Object o = lua_getparam(L, numArg); + luaL_arg_check(L, lua_tag(L, o) == get_tag(L, SERVER_TAG), + numArg, "server socket expected"); + sock = (p_sock) lua_getuserdata(L, o); + if (sock->sock < 0) + lua_error(L, "operation on closed socket"); + return sock; +} + +/*-------------------------------------------------------------------------*\ +* Checks if argument is a socket, printing an error message in +* case of error +* Input +* numArg: argument position in lua2c stack +* Returns +* pointer to socket, or doesn't return in case of error +\*-------------------------------------------------------------------------*/ +static p_sock check_sock(lua_State *L, int numArg) +{ + lua_Object o = lua_getparam(L, numArg); + luaL_arg_check(L, (lua_tag(L, o) == get_tag(L, CLIENT_TAG)) || + (lua_tag(L, o) == get_tag(L, SERVER_TAG)), numArg, "socket expected"); + return lua_getuserdata(L, o); +} From df9a7e548f83b0694d7e4096e004858a43ab8a28 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 27 Dec 2000 19:19:22 +0000 Subject: [PATCH 002/483] Added new receive pattern "*a" Added new bind pattern "*" that binds to INADDR_ANY bind now also returns the ip and port bound to. --- src/luasocket.c | 588 +++++++++++++++++++++++++++++++----------------- 1 file changed, 386 insertions(+), 202 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index 027afa3..ab44490 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -3,7 +3,7 @@ * Diego Nehab * 26/11/1999 * -* Module: LSOCK.C +* Module: LUASOCKET.C * * This module is part of an effort to make the most important features * of the TCP/IP protocol available for Lua scripts. @@ -29,13 +29,11 @@ #include -#define LUA_REENTRANT - #include #include #include -#include "lsock.h" +#include "luasocket.h" /*=========================================================================*\ * WinSock2 include files @@ -108,22 +106,21 @@ * function connect, and implementing the methods send, receive, timeout * and close. A server socket is an object created by the function bind, * and implementing the methods listen, accept and close. Lua tag values -* for these objects are created in the lua_socklibopen function, and +* for these objects are created in the lua_socketlibopen function, and * passed as closure values (first argumnents to every library function, # because we can't have any global variables. \*-------------------------------------------------------------------------*/ -#define CLIENT_TAG 1 -#define SERVER_TAG 2 -#define FIRST_PAR 3 +#define CLIENT_TAG -2 +#define SERVER_TAG -1 /*-------------------------------------------------------------------------*\ * Both socket types are stored in the same structure to simplify * implementation. The tag value used is different, though. The timeout * fields are not used for the server socket object. * There are two timeout values. The block timeout specifies the maximum -* time the any IO operation performed by luasock can be blocked waiting +* time the any IO operation performed by luasocket can be blocked waiting * for completion. The return timeout specifies the maximum time a Lua script -* can be blocked waiting for an luasock IO operation to complete. +* can be blocked waiting for an luasocket IO operation to complete. \*-------------------------------------------------------------------------*/ typedef struct t_sock { SOCKET sock; /* operating system socket object */ @@ -136,9 +133,6 @@ typedef t_sock *p_sock; /*-------------------------------------------------------------------------*\ * Macros and internal declarations \*-------------------------------------------------------------------------*/ -/* internal function prototypes */ -#include "sock.h" - /* return time since marked start in ms */ #define time_since(start) (get_time()-start) @@ -150,6 +144,66 @@ typedef t_sock *p_sock; #define max(x, y) ((x) > (y) ? x : y) #endif +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +/* luasocket API functions */ +static int net_connect(lua_State *L); +static int net_bind(lua_State *L); +static int net_listen(lua_State *L); +static int net_accept(lua_State *L); +static int net_send(lua_State *L); +static int net_receive(lua_State *L); +static int net_timeout(lua_State *L); +static int net_close(lua_State *L); + +/* fallbacks */ +static int server_gettable(lua_State *L); +static int client_gettable(lua_State *L); +static int sock_gc(lua_State *L); + +/* argument checking routines */ +static p_sock check_client(lua_State *L, int numArg, int client_tag); +static p_sock check_server(lua_State *L, int numArg, int server_tag); +static p_sock check_sock(lua_State *L, int numArg, int server_tag, + int client_tag); +static void pop_tags(lua_State *L, int *client_tag, int *server_tag); +static void push_tags(lua_State *L, int client_tag, int server_tag); + +/* result handling routines */ +static char *host_strerror(void); +static char *bind_strerror(void); +static char *sock_strerror(void); +static char *connect_strerror(void); + +static void push_error(lua_State *L, int err); +static void push_client(lua_State *L, p_sock sock, int client_tag); +static void push_server(lua_State *L, p_sock sock, int server_tag); + +/* plataform specific functions */ +static int get_time(void); +static void set_blocking(p_sock sock); +static void set_nonblocking(p_sock sock); + +/* auxiliary functions */ +static p_sock create_sock(void); +static p_sock create_tcpsock(void); +static int fill_sockaddr(struct sockaddr_in *server, const char *hostname, + unsigned short port); +static int get_timeout(p_sock sock, int elapsed); +static int read_or_timeout(p_sock sock, int elapsed); +static int write_or_timeout(p_sock sock, int elapsed); +static int send_raw(p_sock sock, const char *data, int wanted, + int start, int *err, int *end); +static void receive_raw(lua_State *L, p_sock sock, int wanted, + int start, int *err, int *end); +static void receive_dosline(lua_State *L, p_sock sock, int start, + int *err, int *end); +static void receive_unixline(lua_State *L, p_sock sock, int start, + int *err, int *end); +static void receive_all(lua_State *L, p_sock sock, int start, + int *err, int *end); + /*=========================================================================*\ * Test support functions \*=========================================================================*/ @@ -157,17 +211,18 @@ typedef t_sock *p_sock; /*-------------------------------------------------------------------------*\ * Returns the time the system has been up, in secconds. \*-------------------------------------------------------------------------*/ -static void net_time(lua_State *L); -static void net_time(lua_State *L) +static int net_time(lua_State *L); +static int net_time(lua_State *L) { lua_pushnumber(L, get_time()/1000.0); + return 1; } /*-------------------------------------------------------------------------*\ * Causes a Lua script to sleep for the specified number of secconds \*-------------------------------------------------------------------------*/ -static void net_sleep(lua_State *L); -static void net_sleep(lua_State *L) +static int net_sleep(lua_State *L); +static int net_sleep(lua_State *L) { int sec = (int) luaL_check_number(L, 1); #ifdef WIN32 @@ -175,6 +230,7 @@ static void net_sleep(lua_State *L) #else sleep(sec); #endif + return 0; } #endif @@ -194,15 +250,19 @@ static void net_sleep(lua_State *L) * On success: client socket * On error: nil, followed by an error message \*-------------------------------------------------------------------------*/ -static void net_connect(lua_State *L) +static int net_connect(lua_State *L) { - const char *hostname = luaL_check_string(L, FIRST_PAR); - unsigned short port = (unsigned short) luaL_check_number(L, FIRST_PAR+1); + const char *hostname = luaL_check_string(L, 1); + unsigned short port = (unsigned short) luaL_check_number(L, 2); + int client_tag, server_tag; struct sockaddr_in server; - p_sock sock = create_tcpsock(); + p_sock sock; + pop_tags(L, &client_tag, &server_tag); + sock = create_tcpsock(); if (!sock) { lua_pushnil(L); lua_pushstring(L, sock_strerror()); + return 2; } /* fills the sockaddr structure with the information needed to ** connect our socket with the remote host */ @@ -210,17 +270,18 @@ static void net_connect(lua_State *L) free(sock); lua_pushnil(L); lua_pushstring(L, host_strerror()); - return; + return 2; } if (connect(sock->sock,(struct sockaddr *)&server,sizeof(server)) < 0) { /* no connection? we close the socket to free the descriptor */ closesocket(sock->sock); lua_pushnil(L); lua_pushstring(L, connect_strerror()); - return; + return 2; } - push_client(L, sock); - /* no need to push second return value, since it would be nil anyways */ + push_client(L, sock, client_tag); + lua_pushnil(L); + return 2; } /*-------------------------------------------------------------------------*\ @@ -232,14 +293,21 @@ static void net_connect(lua_State *L) * On success: nil * On error: an error message \*-------------------------------------------------------------------------*/ -static void net_listen(lua_State *L) +static int net_listen(lua_State *L) { - p_sock sock = check_server(L, FIRST_PAR); - unsigned int backlog = (unsigned int) luaL_check_number(L, FIRST_PAR+1); - - if (listen(sock->sock, backlog) < 0) + p_sock sock; + int client_tag, server_tag; + unsigned int backlog; + pop_tags(L, &client_tag, &server_tag); + sock = check_server(L, 1, server_tag); + backlog = (unsigned int) luaL_check_number(L, 2); + if (listen(sock->sock, backlog) < 0) { lua_pushstring(L, "listen error"); - /* no need to push return value, since it would be nil anyways */ + return 1; + } else { + lua_pushnil(L); + return 1; + } } /*-------------------------------------------------------------------------*\ @@ -251,13 +319,16 @@ static void net_listen(lua_State *L) * On success: client socket attempting connection * On error: nil followed by an error message \*-------------------------------------------------------------------------*/ -static void net_accept(lua_State *L) +static int net_accept(lua_State *L) { struct sockaddr_in client_addr; - p_sock server = check_server(L, FIRST_PAR); + int client_tag, server_tag; + p_sock server; int client_sock = -1; unsigned int client_len = sizeof(client_addr); p_sock client; + pop_tags(L, &client_tag, &server_tag); + server = check_server(L, 1, server_tag); /* waits for a connection */ client_sock = accept(server->sock, (struct sockaddr *) &client_addr, &client_len); @@ -267,10 +338,13 @@ static void net_accept(lua_State *L) if (!client) { lua_pushnil(L); lua_pushstring(L, "out of memory"); - return; + return 2; + } else { + client->sock = client_sock; + push_client(L, client, client_tag); + lua_pushnil(L); + return 2; } - client->sock = client_sock; - push_client(L, client); } /*-------------------------------------------------------------------------*\ @@ -281,42 +355,56 @@ static void net_accept(lua_State *L) * backlog: optional parameter specifying the number of connections * to keep waiting before refuse a connection. the default value is 1. * Returns -* On success: server socket bound to address +* On success: server socket bound to address, the ip address and port bound * On error: nil, followed by an error message \*-------------------------------------------------------------------------*/ -static void net_bind(lua_State *L) +static int net_bind(lua_State *L) { - const char *hostname = luaL_check_string(L, FIRST_PAR); - unsigned short port = (unsigned short) luaL_check_number(L, FIRST_PAR+1); - unsigned int backlog = (unsigned int) luaL_opt_number(L, FIRST_PAR+2, 1.0); + const char *hostname = luaL_check_string(L, 1); + unsigned short port = (unsigned short) luaL_check_number(L, 2); + unsigned int backlog = (unsigned int) luaL_opt_number(L, 3, 1.0); struct sockaddr_in server; + int server_size = sizeof(server); + int client_tag, server_tag; p_sock sock = create_tcpsock(); + pop_tags(L, &client_tag, &server_tag); if (!sock) { lua_pushnil(L); lua_pushstring(L, sock_strerror()); + return 2; } /* fills the sockaddr structure with the information needed to ** connect our socket with local address */ - if (!fill_sockaddr(&server, hostname, port)) { + else if (!fill_sockaddr(&server, hostname, port)) { free(sock); lua_pushnil(L); lua_pushstring(L, host_strerror()); - return; + return 2; } - if (bind(sock->sock,(struct sockaddr *)&server,sizeof(server)) < 0) { + else if (bind(sock->sock,(struct sockaddr *)&server, server_size) < 0) { lua_pushnil(L); lua_pushstring(L, bind_strerror()); - return; + return 2; } /* define the connection waiting queue length */ - if (listen(sock->sock, backlog) < 0) { + else if (listen(sock->sock, backlog) < 0) { lua_pushnil(L); lua_pushstring(L, "listen error"); - return; - } + return 2; + } /* pass the created socket to Lua, as a server socket */ - push_server(L, sock); - /* no need to push return value, since it would be nil anyways */ + else { + /* pass server */ + push_server(L, sock, server_tag); + /* get used address and port */ + getsockname(sock->sock, (struct sockaddr *)&server, &server_size); + /* pass ip number */ + lua_pushstring(L, inet_ntoa(server.sin_addr)); + /* pass port number */ + lua_pushnumber(L, ntohs(server.sin_port)); + lua_pushnil(L); + return 4; + } } /*-------------------------------------------------------------------------*\ @@ -331,11 +419,16 @@ static void net_bind(lua_State *L) * Returns * no return value \*-------------------------------------------------------------------------*/ -static void net_timeout(lua_State *L) +static int net_timeout(lua_State *L) { - p_sock sock = check_client(L, FIRST_PAR); - int ms = (int) (luaL_check_number(L, FIRST_PAR+1)*1000.0); - const char *mode = luaL_opt_string(L, FIRST_PAR+2, "b"); + int client_tag, server_tag; + p_sock sock; + int ms; + const char *mode; + pop_tags(L, &client_tag, &server_tag); + sock = check_client(L, 1, client_tag); + ms = (int) (luaL_check_number(L, 2)*1000.0); + mode = luaL_opt_string(L, 3, "b"); switch (*mode) { case 'b': sock->b = ms; @@ -344,9 +437,10 @@ static void net_timeout(lua_State *L) sock->r = ms; break; default: - luaL_arg_check(L, 0, FIRST_PAR+2, "invalid timeout mode"); + luaL_arg_check(L, 0, 3, "invalid timeout mode"); break; } + return 0; } /*-------------------------------------------------------------------------*\ @@ -358,33 +452,44 @@ static void net_timeout(lua_State *L) * Returns * On success: nil, followed by the total number of bytes sent * On error: NET_TIMEOUT if the connection timedout, or NET_CLOSED if -* the connection has been closed +* the connection has been closed, followed by the total number of +* bytes sent \*-------------------------------------------------------------------------*/ -static void net_send(lua_State *L) +static int net_send(lua_State *L) { - p_sock sock = check_client(L, FIRST_PAR); + p_sock sock; const char *data; - long size; + size_t size; int start = get_time(); long total = 0; - int arg = FIRST_PAR+1; + int arg; int err = NET_DONE; int end; + int top; + int client_tag, server_tag; + pop_tags(L, &client_tag, &server_tag); + top = lua_gettop(L); + sock = check_client(L, 1, client_tag); #ifdef _DEBUG_BLOCK -printf("luasock: send start\n"); +printf("luasocket: send start\n"); #endif - while ((data = luaL_opt_lstr(L, arg++, NULL, &size)) && err == NET_DONE) + for (arg = 2; arg <= top; arg++) { + data = luaL_opt_lstr(L, arg, NULL, &size); + if (!data || err != NET_DONE) + break; total += send_raw(sock, data, size, start, &err, &end); + } push_error(L, err); lua_pushnumber(L, (double) total); #ifdef _DEBUG_BLOCK -printf("luasock: send end\n"); +printf("luasocket: send end\n"); #endif #ifdef _DEBUG /* pass the time elapsed during function execution to Lua, so that ** the test script can make sure we respected the timeouts */ lua_pushnumber(L, (end-start)/1000.0); #endif + return lua_gettop(L) - top; } /*-------------------------------------------------------------------------*\ @@ -399,57 +504,73 @@ lua_pushnumber(L, (end-start)/1000.0); * number: reads 'number' characters from the socket * Returns * On success: one string for each pattern -* On error: all strings for which there was no error, followed by on +* On error: all strings for which there was no error, followed by one * nil value for each failed string, followed by an error code \*-------------------------------------------------------------------------*/ -static void net_receive(lua_State *L) +static int net_receive(lua_State *L) { - static const char *const modenames[] = {"*l", "*lu", NULL}; - p_sock sock = check_client(L, FIRST_PAR); - int err = NET_DONE, arg = FIRST_PAR+1, got = 0; - char *data = NULL; + static const char *const modenames[] = {"*l", "*lu", "*a", NULL}; + int err = NET_DONE, arg = 2; int start = get_time(); - const char *mode = luaL_opt_string(L, arg++, "*l"); int end; + int client_tag, server_tag; + int top; + p_sock sock; + const char *mode; + pop_tags(L, &client_tag, &server_tag); + sock = check_client(L, 1, client_tag); #ifdef _DEBUG_BLOCK -printf("luasock: receive start\n"); +printf("luasocket: receive start\n"); #endif - do { + /* push default pattern */ + top = lua_gettop(L); + if (top < 2) { + lua_pushstring(L, "*l"); + top++; + } + for (arg = 2; arg <= top; arg++) { /* if one pattern failed, we just skip all other patterns */ if (err != NET_DONE) { lua_pushnil(L); continue; } - /* get next pattern */ - switch (luaL_findstring(mode, modenames)) { - /* DOS line mode */ - case 0: - got = receive_dosline(L, sock, &data, start, &err, &end); - break; - /* Unix line mode */ - case 2: - got = receive_unixline(L, sock, &data, start, &err, &end); - break; - /* else it must be a number, raw mode */ - default: { - long size = (long) luaL_check_number(L, arg-1); - got = receive_raw(L, sock, size, &data, start, &err, &end); - break; + if (lua_isnumber(L, arg)) { + long size = (long) lua_tonumber(L, arg); + receive_raw(L, sock, size, start, &err, &end); + } else { + mode = luaL_opt_string(L, arg, NULL); + /* get next pattern */ + switch (luaL_findstring(mode, modenames)) { + /* DOS line mode */ + case 0: + receive_dosline(L, sock, start, &err, &end); + break; + /* Unix line mode */ + case 1: + receive_unixline(L, sock, start, &err, &end); + break; + /* 'Til closed mode */ + case 2: + receive_all(L, sock, start, &err, &end); + break; + /* else it must be a number, raw mode */ + default: + luaL_arg_check(L, 0, arg, "invalid receive pattern"); + break; } } - /* pass result */ - lua_pushlstring(L, data, got); - } while ((mode = luaL_opt_string(L, arg++, NULL))); + } /* last return is an error code */ push_error(L, err); #ifdef _DEBUG_BLOCK -printf("luasock: receive end\n"); +printf("luasocket: receive end\n"); #endif #ifdef _DEBUG /* pass the time elapsed during function execution to Lua, so that ** the test script can make sure we respected the timeouts */ lua_pushnumber(L, (end-start)/1000.0); #endif + return lua_gettop(L) - top; } /*-------------------------------------------------------------------------*\ @@ -457,13 +578,17 @@ lua_pushnumber(L, (end-start)/1000.0); * Input * sock: socket to be closed \*-------------------------------------------------------------------------*/ -static void net_close(lua_State *L) +static int net_close(lua_State *L) { - p_sock sock = check_sock(L, FIRST_PAR); + int client_tag, server_tag; + p_sock sock; + pop_tags(L, &client_tag, &server_tag); + sock = check_sock(L, 1, client_tag, server_tag); closesocket(sock->sock); /* set value to -1 so that we can later detect the use of a ** closed socket */ sock->sock = -1; + return 0; } /*-------------------------------------------------------------------------*\ @@ -471,36 +596,35 @@ static void net_close(lua_State *L) * alternative interface client:receive, client:send etc for the client * socket methods. \*-------------------------------------------------------------------------*/ -static void client_gettable(lua_State *L) +static int client_gettable(lua_State *L) { static const char *const net_api[] = {"receive","send","timeout","close", "connect", NULL}; - const char *idx = luaL_check_string(L, FIRST_PAR+1); + const char *idx = luaL_check_string(L, 2); + int server_tag, client_tag; + pop_tags(L, &client_tag, &server_tag); switch (luaL_findstring(idx, net_api)) { case 0: - lua_pushnumber(L, get_tag(L, CLIENT_TAG)); - lua_pushnumber(L, get_tag(L, SERVER_TAG)); + push_tags(L, client_tag, server_tag); lua_pushcclosure(L, net_receive, 2); break; case 1: - lua_pushnumber(L, get_tag(L, CLIENT_TAG)); - lua_pushnumber(L, get_tag(L, SERVER_TAG)); + push_tags(L, client_tag, server_tag); lua_pushcclosure(L, net_send, 2); break; case 2: - lua_pushnumber(L, get_tag(L, CLIENT_TAG)); - lua_pushnumber(L, get_tag(L, SERVER_TAG)); + push_tags(L, client_tag, server_tag); lua_pushcclosure(L, net_timeout, 2); break; case 3: - lua_pushnumber(L, get_tag(L, CLIENT_TAG)); - lua_pushnumber(L, get_tag(L, SERVER_TAG)); + push_tags(L, client_tag, server_tag); lua_pushcclosure(L, net_close, 2); break; default: lua_pushnil(L); break; } + return 1; } /*-------------------------------------------------------------------------*\ @@ -508,30 +632,30 @@ static void client_gettable(lua_State *L) * alternative interface server:listen, server:accept etc for the server * socket methods. \*-------------------------------------------------------------------------*/ -static void server_gettable(lua_State *L) +static int server_gettable(lua_State *L) { static const char *const net_api[] = {"listen","accept","close", NULL}; - const char *idx = luaL_check_string(L, FIRST_PAR+1); + const char *idx = luaL_check_string(L, 2); + int server_tag, client_tag; + pop_tags(L, &client_tag, &server_tag); switch (luaL_findstring(idx, net_api)) { case 0: - lua_pushnumber(L, get_tag(L, CLIENT_TAG)); - lua_pushnumber(L, get_tag(L, SERVER_TAG)); + push_tags(L, client_tag, server_tag); lua_pushcclosure(L, net_listen, 2); break; case 1: - lua_pushnumber(L, get_tag(L, CLIENT_TAG)); - lua_pushnumber(L, get_tag(L, SERVER_TAG)); + push_tags(L, client_tag, server_tag); lua_pushcclosure(L, net_accept, 2); break; case 2: - lua_pushnumber(L, get_tag(L, CLIENT_TAG)); - lua_pushnumber(L, get_tag(L, SERVER_TAG)); + push_tags(L, client_tag, server_tag); lua_pushcclosure(L, net_close, 2); break; default: lua_pushnil(L); break; } + return 1; } /*-------------------------------------------------------------------------*\ @@ -539,12 +663,16 @@ static void server_gettable(lua_State *L) * makes sure that all collected sockets are closed and that the memory * used by the C structure t_sock is properly released. \*-------------------------------------------------------------------------*/ -static void sock_gc(lua_State *L) +static int sock_gc(lua_State *L) { - p_sock sock = check_sock(L, FIRST_PAR); + int server_tag, client_tag; + p_sock sock; + pop_tags(L, &client_tag, &server_tag); + sock = check_sock(L, 1, client_tag, server_tag); if (sock->sock >= 0) closesocket(sock->sock); free(sock); + return 1; } /*=========================================================================*\ @@ -637,19 +765,24 @@ static int fill_sockaddr(struct sockaddr_in *address, const char *hostname, { struct hostent *host = NULL; unsigned long addr = inet_addr(hostname); - /* BSD says we could have used gethostbyname even if the hostname is - ** in ip address form, but WinSock2 says we can't. Therefore we - ** choose a method that works on both plataforms */ - if (addr == INADDR_NONE) - host = gethostbyname(hostname); - else - host = gethostbyaddr((char * ) &addr, sizeof(unsigned long), AF_INET); - if (!host) - return 0; memset(address, 0, sizeof(struct sockaddr_in)); + if (strcmp(hostname, "*")) { + /* BSD says we could have used gethostbyname even if the hostname is + ** in ip address form, but WinSock2 says we can't. Therefore we + ** choose a method that works on both plataforms */ + if (addr == INADDR_NONE) + host = gethostbyname(hostname); + else + host = gethostbyaddr((char * ) &addr, sizeof(unsigned long), + AF_INET); + if (!host) + return 0; + memcpy(&(address->sin_addr), host->h_addr, (unsigned) host->h_length); + } else { + address->sin_addr.s_addr = htonl(INADDR_ANY); + } address->sin_family = AF_INET; address->sin_port = htons(port); - memcpy(&(address->sin_addr), host->h_addr, (unsigned) host->h_length); return 1; } @@ -776,7 +909,7 @@ static int send_raw(p_sock sock, const char *data, int wanted, #ifdef _DEBUG /* the lua_pushlstring function can take a long time to pass a large block ** to Lua, therefore, we mark the time before passing the result. -** also, the call to write of read might take longer then the time we had +** also, the call to write or read might take longer then the time we had ** left, so that the end of the operation is marked before the last call ** to the OS */ *end = get_time(); @@ -795,7 +928,7 @@ static int send_raw(p_sock sock, const char *data, int wanted, return total; } #ifdef _DEBUG_BLOCK -printf("luasock: sent %d bytes, %dms elapsed\n", put, time_since(start)); +printf("luasocket: sent %d bytes, %dms elapsed\n", put, time_since(start)); #endif wanted -= put; data += put; @@ -813,46 +946,84 @@ printf("luasock: sent %d bytes, %dms elapsed\n", put, time_since(start)); * wanted: number of bytes to be read * start: time the operation started, in ms * Output -* data: pointer to an internal buffer containing the data read * err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED * Returns * Number of bytes read \*-------------------------------------------------------------------------*/ -static int receive_raw(lua_State *L, p_sock sock, int wanted, char **data, - int start, int *err, int *end) +#define MIN(x,y) ((x)<(y)?(x):(y)) +static void receive_raw(lua_State *L, p_sock sock, int wanted, int start, + int *err, int *end) { int got = 0; char *buffer = NULL; + luaL_Buffer b; *end = start; - luaL_resetbuffer(L); + luaL_buffinit(L, &b); while (wanted > 0) { - buffer = luaL_openspace(L, wanted); if(!read_or_timeout(sock, time_since(start))) { #ifdef _DEBUG *end = get_time(); #endif - *data = luaL_buffer(L); *err = NET_TIMEOUT; - return luaL_getsize(L); + luaL_pushresult(&b); + return; } #ifdef _DEBUG *end = get_time(); #endif - got = recv(sock->sock, buffer, wanted, 0); + buffer = luaL_prepbuffer(&b); + got = recv(sock->sock, buffer, MIN(wanted, LUAL_BUFFERSIZE), 0); #ifdef _DEBUG_BLOCK -printf("luasock: wanted %d, got %d, %dms elapsed\n", wanted, got, time_since(start)); +printf("luasocket: wanted %d, got %d, %dms elapsed\n", wanted, got, time_since(start)); #endif if (got <= 0) { - *data = luaL_buffer(L); *err = NET_CLOSED; - return luaL_getsize(L); + luaL_pushresult(&b); + return; } wanted -= got; - luaL_addsize(L,got); + luaL_addsize(&b, got); } - *data = luaL_buffer(L); *err = NET_DONE; - return luaL_getsize(L); + luaL_pushresult(&b); +} + +/*-------------------------------------------------------------------------*\ +* Reads everything until the connection is closed +* Input +* sock: socket structure being used in operation +* start: time the operation started, in ms +* Output +* err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED +* Result +* a string is pushed into the Lua stack with the line just read +\*-------------------------------------------------------------------------*/ +static void receive_all(lua_State *L, p_sock sock, int start, + int *err, int *end) +{ + int got; + char *buffer; + luaL_Buffer b; + *end = start; + luaL_buffinit(L, &b); + for ( ;; ) { + buffer = luaL_prepbuffer(&b); + if (read_or_timeout(sock, time_since(start))) { +#ifdef _DEBUG +*end = get_time(); +#endif + got = recv(sock->sock, buffer, LUAL_BUFFERSIZE, 0); + if (got <= 0) { + *err = NET_DONE; + break; + } + luaL_addsize(&b, got); + } else { + *err = NET_TIMEOUT; + break; + } + } + luaL_pushresult(&b); } /*-------------------------------------------------------------------------*\ @@ -866,17 +1037,18 @@ printf("luasock: wanted %d, got %d, %dms elapsed\n", wanted, got, time_since(sta * Output * data: pointer to an internal buffer containing the data read * err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED -* Returns -* Number of bytes read +* Result +* a string is pushed into the Lua stack with the line just read \*-------------------------------------------------------------------------*/ -static long receive_dosline(lua_State *L, p_sock sock, char **data, - int start, int *err, int *end) +static void receive_dosline(lua_State *L, p_sock sock, int start, + int *err, int *end) { char c = ' '; long got = 0; + luaL_Buffer b; *end = start; - luaL_resetbuffer(L); - while (c != '\n') { + luaL_buffinit(L, &b); + for ( ;; ) { if (read_or_timeout(sock, time_since(start))) { #ifdef _DEBUG *end = get_time(); @@ -884,21 +1056,20 @@ static long receive_dosline(lua_State *L, p_sock sock, char **data, got = recv(sock->sock, &c, 1, 0); if (got <= 0) { *err = NET_CLOSED; - *data = luaL_buffer(L); - return luaL_getsize(L); + break; + } + if (c != '\n') { + if (c != '\r') luaL_putchar(&b, c); + } else { + *err = NET_DONE; + break; } - luaL_addchar(L, c); } else { *err = NET_TIMEOUT; - *data = luaL_buffer(L); - return luaL_getsize(L); + break; } } - *err = NET_DONE; - *data = luaL_buffer(L); - got = luaL_getsize(L); - if ((*data)[got-2] == '\r') return got-2; - else return got-1; + luaL_pushresult(&b); } /*-------------------------------------------------------------------------*\ @@ -915,43 +1086,50 @@ static long receive_dosline(lua_State *L, p_sock sock, char **data, * Returns * Number of bytes read \*-------------------------------------------------------------------------*/ -static long receive_unixline(lua_State *L, p_sock sock, char **data, - int start, int *err, int *end) +static void receive_unixline(lua_State *L, p_sock sock, int start, + int *err, int *end) { char c = ' '; + long got = 0; + long size = 0; + luaL_Buffer b; *end = start; - luaL_resetbuffer(L); - while (c != '\n') { + luaL_buffinit(L, &b); + for ( ;; ) { if (read_or_timeout(sock, time_since(start))) { #ifdef _DEBUG *end = get_time(); #endif - if (recv(sock->sock, &c, 1, 0) <= 0) { + got = recv(sock->sock, &c, 1, 0); + if (got <= 0) { *err = NET_CLOSED; - *data = luaL_buffer(L); - return luaL_getsize(L); + break; + } + if (c != '\n') { + luaL_putchar(&b, c); + size++; + } else { + *err = NET_DONE; + break; } - luaL_addchar(L, c); } else { *err = NET_TIMEOUT; - *data = luaL_buffer(L); - return luaL_getsize(L); + break; } } - *err = NET_DONE; - *data = luaL_buffer(L); - return luaL_getsize(L) - 1; + luaL_pushresult(&b); } /*-------------------------------------------------------------------------*\ -* Gets a tag from a closure parameter +* Pops tags from closures * Input * L: lua environment -* par: parameter number \*-------------------------------------------------------------------------*/ -static int get_tag(lua_State *L, int par) +static void pop_tags(lua_State *L, int *client_tag, int *server_tag) { - return (int) lua_getnumber(L, lua_getparam(L, par)); + *client_tag = (int) lua_tonumber(L, CLIENT_TAG); + *server_tag = (int) lua_tonumber(L, SERVER_TAG); + lua_pop(L, 2); } /*-------------------------------------------------------------------------*\ @@ -974,6 +1152,17 @@ static void push_error(lua_State *L, int err) } } +/*-------------------------------------------------------------------------*\ +* Passes socket tags to lua in correct order +* Input: +* client_tag, server_tag +\*-------------------------------------------------------------------------*/ +static void push_tags(lua_State *L, int client_tag, int server_tag) +{ + lua_pushnumber(L, client_tag); + lua_pushnumber(L, server_tag); +} + /*-------------------------------------------------------------------------*\ * Passes a client socket to Lua. * Must be called from a closure receiving the socket tags as its @@ -982,9 +1171,9 @@ static void push_error(lua_State *L, int err) * L: lua environment * sock: pointer to socket structure to be used \*-------------------------------------------------------------------------*/ -static void push_client(lua_State *L, p_sock sock) +static void push_client(lua_State *L, p_sock sock, int client_tag) { - lua_pushusertag(L, (void *) sock, get_tag(L, CLIENT_TAG)); + lua_pushusertag(L, (void *) sock, client_tag); } /*-------------------------------------------------------------------------*\ @@ -995,9 +1184,9 @@ static void push_client(lua_State *L, p_sock sock) * L: lua environment * sock: pointer to socket structure to be used \*-------------------------------------------------------------------------*/ -static void push_server(lua_State *L, p_sock sock) +static void push_server(lua_State *L, p_sock sock, int server_tag) { - lua_pushusertag(L, (void *) sock, get_tag(L, SERVER_TAG)); + lua_pushusertag(L, (void *) sock, server_tag); } /*=========================================================================*\ @@ -1233,7 +1422,7 @@ static char *connect_strerror(void) * Initializes the library interface with Lua and the socket library. * Defines the symbols exported to Lua. \*-------------------------------------------------------------------------*/ -void lua_socklibopen(lua_State *L) +void lua_socketlibopen(lua_State *L) { int client_tag, server_tag; static struct luaL_reg funcs[] = { @@ -1256,29 +1445,24 @@ void lua_socklibopen(lua_State *L) server_tag = lua_newtag(L); /* Lua exported functions */ for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { - lua_pushnumber(L, client_tag); - lua_pushnumber(L, server_tag); - lua_pushcclosure(L, funcs[i].func, 2 ); - lua_setglobal(L, funcs[i].name ); + push_tags(L, client_tag, server_tag); + lua_pushcclosure(L, funcs[i].func, 2); + lua_setglobal(L, funcs[i].name); } /* fallbacks */ - lua_pushnumber(L, client_tag); - lua_pushnumber(L, server_tag); + push_tags(L, client_tag, server_tag); lua_pushcclosure(L, client_gettable, 2); lua_settagmethod(L, client_tag, "gettable"); - lua_pushnumber(L, client_tag); - lua_pushnumber(L, server_tag); + push_tags(L, client_tag, server_tag); lua_pushcclosure(L, server_gettable, 2); lua_settagmethod(L, server_tag, "gettable"); - lua_pushnumber(L, client_tag); - lua_pushnumber(L, server_tag); + push_tags(L, client_tag, server_tag); lua_pushcclosure(L, sock_gc, 2); lua_settagmethod(L, client_tag, "gc"); - lua_pushnumber(L, client_tag); - lua_pushnumber(L, server_tag); + push_tags(L, client_tag, server_tag); lua_pushcclosure(L, sock_gc, 2); lua_settagmethod(L, server_tag, "gc"); @@ -1308,13 +1492,12 @@ lua_pushcfunction(L, net_time); lua_setglobal(L, "time"); * Returns * pointer to client socket, or doesn't return in case of error \*-------------------------------------------------------------------------*/ -static p_sock check_client(lua_State *L, int numArg) +static p_sock check_client(lua_State *L, int numArg, int client_tag) { p_sock sock; - lua_Object o = lua_getparam(L, numArg); - luaL_arg_check(L, lua_tag(L, o) == get_tag(L, CLIENT_TAG), + luaL_arg_check(L, lua_tag(L, numArg) == client_tag, numArg, "client socket expected"); - sock = (p_sock) lua_getuserdata(L, o); + sock = (p_sock) lua_touserdata(L, numArg); if (sock->sock < 0) lua_error(L, "operation on closed socket"); return sock; @@ -1328,13 +1511,12 @@ static p_sock check_client(lua_State *L, int numArg) * Returns * pointer to server socket, or doesn't return in case of error \*-------------------------------------------------------------------------*/ -static p_sock check_server(lua_State *L, int numArg) +static p_sock check_server(lua_State *L, int numArg, int server_tag) { p_sock sock; - lua_Object o = lua_getparam(L, numArg); - luaL_arg_check(L, lua_tag(L, o) == get_tag(L, SERVER_TAG), + luaL_arg_check(L, lua_tag(L, numArg) == server_tag, numArg, "server socket expected"); - sock = (p_sock) lua_getuserdata(L, o); + sock = (p_sock) lua_touserdata(L, numArg); if (sock->sock < 0) lua_error(L, "operation on closed socket"); return sock; @@ -1348,10 +1530,12 @@ static p_sock check_server(lua_State *L, int numArg) * Returns * pointer to socket, or doesn't return in case of error \*-------------------------------------------------------------------------*/ -static p_sock check_sock(lua_State *L, int numArg) +static p_sock check_sock(lua_State *L, int numArg, int client_tag, + int server_tag) { - lua_Object o = lua_getparam(L, numArg); - luaL_arg_check(L, (lua_tag(L, o) == get_tag(L, CLIENT_TAG)) || - (lua_tag(L, o) == get_tag(L, SERVER_TAG)), numArg, "socket expected"); - return lua_getuserdata(L, o); + p_sock sock; + luaL_arg_check(L, (lua_tag(L, numArg) == client_tag) || + (lua_tag(L, numArg) == server_tag), numArg, "socket expected"); + sock = lua_touserdata(L, numArg); + return sock; } From 6f9d15b66027cef58441549f8ac0603ca42da0ac Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 29 Dec 2000 22:08:56 +0000 Subject: [PATCH 003/483] Simplified SIGPIPE treatment. Changed some ints to size_ts. --- src/luasocket.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index ab44490..2216b90 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -325,7 +325,7 @@ static int net_accept(lua_State *L) int client_tag, server_tag; p_sock server; int client_sock = -1; - unsigned int client_len = sizeof(client_addr); + size_t client_len = sizeof(client_addr); p_sock client; pop_tags(L, &client_tag, &server_tag); server = check_server(L, 1, server_tag); @@ -364,7 +364,7 @@ static int net_bind(lua_State *L) unsigned short port = (unsigned short) luaL_check_number(L, 2); unsigned int backlog = (unsigned int) luaL_opt_number(L, 3, 1.0); struct sockaddr_in server; - int server_size = sizeof(server); + size_t server_size = sizeof(server); int client_tag, server_tag; p_sock sock = create_tcpsock(); pop_tags(L, &client_tag, &server_tag); @@ -687,20 +687,10 @@ static int sock_gc(lua_State *L) static void handle_sigpipe(void); static void handle_sigpipe(void) { - struct sigaction old, new; - bzero(&new, sizeof new); + struct sigaction new; + memset(&new, 0, sizeof(new)); new.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &new, &old); - /* test if the signal had been before, and restore it if so */ - if (old.sa_handler != SIG_DFL) { -#ifdef _DEBUG -/* this is a somewhat dangerous situation. we can only hope the -** installed signal handler understands that this signal can be -** raised by a socket operation */ -printf("SIGPIPE ALREADY REDEFINED!!!\n"); -#endif - sigaction(SIGPIPE, &old, NULL); - } + sigaction(SIGPIPE, &new, NULL); } #endif @@ -928,7 +918,7 @@ static int send_raw(p_sock sock, const char *data, int wanted, return total; } #ifdef _DEBUG_BLOCK -printf("luasocket: sent %d bytes, %dms elapsed\n", put, time_since(start)); +printf("luasocket: sent %d, wanted %d, %dms elapsed\n", put, wanted, time_since(start)); #endif wanted -= put; data += put; From 17c4d1c30544f0ed638879835f179ada96249868 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 29 Dec 2000 22:15:09 +0000 Subject: [PATCH 004/483] Initial revision --- README | 24 +++ etc/dict.lua | 39 ++++ makefile.dist | 81 ++++++++ samples/README | 32 ++++ samples/listener.lua | 25 +++ samples/talker.lua | 22 +++ src/ftp.lua | 437 +++++++++++++++++++++++++++++++++++++++++++ src/http.lua | 312 ++++++++++++++++++++++++++++++ src/luasocket.h | 18 ++ src/smtp.lua | 338 +++++++++++++++++++++++++++++++++ test/testclnt.lua | 359 +++++++++++++++++++++++++++++++++++ test/testsrvr.lua | 90 +++++++++ 12 files changed, 1777 insertions(+) create mode 100644 README create mode 100644 etc/dict.lua create mode 100644 makefile.dist create mode 100644 samples/README create mode 100644 samples/listener.lua create mode 100644 samples/talker.lua create mode 100644 src/ftp.lua create mode 100644 src/http.lua create mode 100644 src/luasocket.h create mode 100644 src/smtp.lua create mode 100644 test/testclnt.lua create mode 100644 test/testsrvr.lua diff --git a/README b/README new file mode 100644 index 0000000..c4ac6fd --- /dev/null +++ b/README @@ -0,0 +1,24 @@ +This directory contains the implementation of the protocols FTP, HTTP and +SMTP. The files provided are: + + http.lua -- HTTP protocol implementation + base64.lua -- base64 encoding implementation + +The module http.lua provides functionality to download an URL from a +HTTP server. The implementation conforms to the HTTP/1.1 standard, RFC +2068. The base64.lua module provides base64 encoding and decoding. The +module is used for the HTTP Basic Authentication Scheme, and conforms to +RFC 1521. + + smtp.lua -- SMTP protocol implementation + +The module smtp.lua provides functionality to send e-mail messages to a +SMTP mail server. The implementation conforms to RFC 821. + + ftp.lua -- FTP protocol implementation + +The module ftp.lua provides functions to download and upload files from +and to FTP servers. The implementation conforms to RFC 959. + +These implementations are supported. Please send any comments do +diego@tecgraf.puc-rio.br. diff --git a/etc/dict.lua b/etc/dict.lua new file mode 100644 index 0000000..683cb45 --- /dev/null +++ b/etc/dict.lua @@ -0,0 +1,39 @@ +-- dict.lua +-- simple client for DICT protocol (see http://www.dict.org/) +-- shows definitions for each word from stdin. uses only "wn" dictionary. +-- if a word is "=", then the rest of the line is sent verbatim as a protocol +-- command to the server. + +if verbose then verbose=write else verbose=function()end end + +verbose(">>> connecting to server\n") +local s,e=connect("dict.org",2628) +assert(s,e) +verbose(">>> connected\n") + +while 1 do + local w=read"*w" + if w==nil then break end + if w=="=" then + w=read"*l" + verbose(">>>",w,"\n") + send(s,w,"\r\n") + else + verbose(">>> looking up `",w,"'\n") + send(s,"DEFINE wn ",w,"\r\n") + end + while 1 do + local l=receive(s) + if l==nil then break end + if strfind(l,"^[0-9]") then + write("<<< ",l,"\n") + else + write(l,"\n") + end + if strfind(l,"^250") or strfind(l,"^[45]") then break end + end +end + +send(s,"QUIT\r\n") +verbose("<<< ",receive(s),"\n") +close(s) diff --git a/makefile.dist b/makefile.dist new file mode 100644 index 0000000..19a3775 --- /dev/null +++ b/makefile.dist @@ -0,0 +1,81 @@ +#-------------------------------------------------------------------------- +# LuaSocket makefile +# Test executable for socklib +# Diego Nehab, 29/8/1999 +#-------------------------------------------------------------------------- + +# don't echo commands +# .SILENT: + +CXX = g++ +CC = gcc + +DIST = luasocket-1.1 + +WARNINGS = -Wall -Wshadow -Wpointer-arith -Waggregate-return -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wnested-externs + +CFLAGS = $(WARNINGS) -D_DEBUG -O2 + +LUA = /home/diego/lib/lua +LUALIB = $(LUA)/lib +LUAINC = $(LUA)/include + +INC = -I$(LUAINC) +# LIB = $(LUA_LIB)/liblualib.a $(LUA_LIB)/liblua.a -lm -lsocket -lnsl +# LIB = $(LUA_LIB)/liblualib.a $(LUA_LIB)/liblua.a -lm -lnsl +LIB = $(LUALIB)/liblualib.a $(LUALIB)/liblua.a -lm + +SRC = ~diego/tec/luasocket +OBJ = /tmp +DEP = /tmp + +# list of .cpp files +c_sources = luasocket.c lua.c + +# corresponding .o files +c_objects = $(addprefix $(OBJ)/, $(addsuffix .o, $(basename $(c_sources)))) + +# binary depends on objects +luasocket: $(c_objects) + $(CC) $(CPPFLAGS) -o $@ $(c_objects) $(LIB) + +# rule to create them +$(c_objects): $(OBJ)/%.o: $(SRC)/%.c + $(CC) -c $(CFLAGS) $(INC) -o $@ $< + +# corresponding .d files +c_deps = $(addprefix $(DEP)/, $(addsuffix .d, $(basename $(c_sources)))) + +# makefile depend on them... +makefile : $(c_deps) + +# ... since it includes them +-include $(c_deps) + +# rule to create them +$(c_deps) : $(DEP)/%.d : $(SRC)/%.c + $(SHELL) -ec '$(CC) -MM $(CFLAGS) $(INC) $< \ + | sed '\''s%\($*\.o\)%$@ $(OBJ)/\1%'\'' > $@; \ + [ -s $@ ] || rm -f $@' + +# clean all trash +clean: + rm -f $(OBJ)/*.o + rm -f $(DEP)/*.d + rm -f luasocket core + +dist: + mkdir -p $(DIST)/lua + mkdir -p $(DIST)/examples + mkdir -p $(DIST)/html + cp -vf *.c $(DIST) + cp -vf *.h $(DIST) + cp -vf makefile $(DIST) + cp -vf README $(DIST) + cp -vf lua/*.lua $(DIST)/lua + cp -vf lua/README $(DIST)/lua + cp -vf examples/*.lua $(DIST)/examples + cp -vf examples/README $(DIST)/examples + cp -vf html/manual.html $(DIST)/html + tar -zcvf $(DIST).tar.gz $(DIST) + zip -r $(DIST).zip $(DIST) diff --git a/samples/README b/samples/README new file mode 100644 index 0000000..ef5017b --- /dev/null +++ b/samples/README @@ -0,0 +1,32 @@ +This directory contains some sample programs using LuaSocket as well as +the automatic tests used to make sure the library is working properly. + +The files provided are: + + server.lua -- test server + client.lua -- test client + command.lua -- test command definitions + +The automatic tests are composed by three files: client.lua, command.lua +and server.lua. To run the automatic tests on your system, make sure to +compile the library with _DEBUG defined (check makefile) and then open +two terminals. Run 'luasocket server.lua' on one of them and 'luasocket +client.lua' on the other. The programs should start talking to each +other. + + listen.lua -- echo server + talk.lua -- echo tester + +listen.lua and talk.lua are about the simplest applications you can +write using LuaSocket. Run 'luasocket listen.lua' and 'luasocket +talk.lua' on different terminals. Whatever you type on talk.lua will be +printed by listen.lua. + + dict.lua -- dict client + +The dict.lua module is a cool simple client for the DICT protocol, +written by Luiz Henrique Figueiredo. Just run it and enter a few words +to see it working. + +Good luck, +Diego. diff --git a/samples/listener.lua b/samples/listener.lua new file mode 100644 index 0000000..a47d9a3 --- /dev/null +++ b/samples/listener.lua @@ -0,0 +1,25 @@ +host = "localhost" +port = 8080 +if arg then + host = arg[1] or host + port = arg[2] or port +end +print("Binding to host '" ..host.. "' and port " ..port.. "...") +s, i, p, e = bind(host, port) +if not s then + print(e) + exit() +end +print("Waiting connection from talker on " .. i .. ":" .. p .. "...") +c, e = s:accept() +if not c then + print(e) + exit() +end +print("Connected. Here is the stuff:") +l, e = c:receive() +while not e do + print(l) + l, e = c:receive() +end +print(e) diff --git a/samples/talker.lua b/samples/talker.lua new file mode 100644 index 0000000..b3313e6 --- /dev/null +++ b/samples/talker.lua @@ -0,0 +1,22 @@ +host = "localhost" +port = 8080 +if arg then + host = arg[1] or host + port = arg[2] or port +end +print("Attempting connection to host '" ..host.. "' and port " ..port.. "...") +c, e = connect(host, port) +if not c then + print(e) + exit() +end +print("Connected! Please type stuff (empty line to stop):") +l = read() +while l and l ~= "" and not e do + e = c:send(l, "\n") + if e then + print(e) + exit() + end + l = read() +end diff --git a/src/ftp.lua b/src/ftp.lua new file mode 100644 index 0000000..b817356 --- /dev/null +++ b/src/ftp.lua @@ -0,0 +1,437 @@ +----------------------------------------------------------------------------- +-- Simple FTP support for the Lua language using the LuaSocket toolkit. +-- Author: Diego Nehab +-- Date: 26/12/2000 +-- Conforming to: RFC 959 +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout in seconds before the program gives up on a connection +local TIMEOUT = 60 +-- default port for ftp service +local PORT = 21 +-- this is the default anonymous password. used when no password is +-- provided in url. should be changed for your e-mail. +local EMAIL = "anonymous@anonymous.org" + +----------------------------------------------------------------------------- +-- Parses a url and returns its scheme, user, password, host, port +-- and path components, according to RFC 1738, Uniform Resource Locators (URL), +-- of December 1994 +-- Input +-- url: unique resource locator desired +-- default: table containing default values to be returned +-- Returns +-- table with the following fields: +-- host: host to connect +-- path: url path +-- port: host port to connect +-- user: user name +-- pass: password +-- scheme: protocol +----------------------------------------------------------------------------- +local split_url = function(url, default) + -- initialize default parameters + local parsed = default or {} + -- get scheme + url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end) + -- get user name and password. both can be empty! + -- moreover, password can be ommited + url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p) + %parsed.user = u + -- there can be an empty password, but the ':' has to be there + -- or else there is no password + %parsed.pass = nil -- kill default password + if c == ":" then %parsed.pass = p end + end) + -- get host + url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end) + -- get port if any + url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end) + -- whatever is left is the path + if url ~= "" then parsed.path = url end + return parsed +end + +----------------------------------------------------------------------------- +-- Gets ip and port for data connection from PASV answer +-- Input +-- pasv: PASV command answer +-- Returns +-- ip: string containing ip for data connection +-- port: port for data connection +----------------------------------------------------------------------------- +local get_pasv = function(pasv) + local a,b,c,d,p1,p2 + local ip, port + _,_, a, b, c, d, p1, p2 = + strfind(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)") + if not a or not b or not c or not d or not p1 or not p2 then + return nil, nil + end + ip = format("%d.%d.%d.%d", a, b, c, d) + port = tonumber(p1)*256 + tonumber(p2) + return ip, port +end + +----------------------------------------------------------------------------- +-- Sends a FTP command through socket +-- Input +-- control: control connection socket +-- cmd: command +-- arg: command argument if any +----------------------------------------------------------------------------- +local send_command = function(control, cmd, arg) + local line, err + if arg then line = cmd .. " " .. arg .. "\r\n" + else line = cmd .. "\r\n" end + err = control:send(line) + return err +end + +----------------------------------------------------------------------------- +-- Gets FTP command answer, unfolding if neccessary +-- Input +-- control: control connection socket +-- Returns +-- answer: whole server reply, nil if error +-- code: answer status code or error message +----------------------------------------------------------------------------- +local get_answer = function(control) + local code, lastcode, sep + local line, err = control:receive() + local answer = line + if err then return nil, err end + _,_, code, sep = strfind(line, "^(%d%d%d)(.)") + if not code or not sep then return nil, answer end + if sep == "-" then -- answer is multiline + repeat + line, err = control:receive() + if err then return nil, err end + _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)") + answer = answer .. "\n" .. line + until code == lastcode and sep == " " -- answer ends with same code + end + return answer, tonumber(code) +end + +----------------------------------------------------------------------------- +-- Checks if a message return is correct. Closes control connection if not. +-- Input +-- control: control connection socket +-- success: table with successfull reply status code +-- Returns +-- code: reply code or nil in case of error +-- answer: server complete answer or system error message +----------------------------------------------------------------------------- +local check_answer = function(control, success) + local answer, code = %get_answer(control) + if not answer then + control:close() + return nil, code + end + if type(success) ~= "table" then success = {success} end + for i = 1, getn(success) do + if code == success[i] then + return code, answer + end + end + control:close() + return nil, answer +end + +----------------------------------------------------------------------------- +-- Trys a command on control socked, in case of error, the control connection +-- is closed. +-- Input +-- control: control connection socket +-- cmd: command +-- arg: command argument or nil if no argument +-- success: table with successfull reply status code +-- Returns +-- code: reply code or nil in case of error +-- answer: server complete answer or system error message +----------------------------------------------------------------------------- +local try_command = function(control, cmd, arg, success) + local err = %send_command(control, cmd, arg) + if err then + control:close() + return nil, err + end + local code, answer = %check_answer(control, success) + if not code then return nil, answer end + return code, answer +end + +----------------------------------------------------------------------------- +-- Creates a table with all directories in path +-- Input +-- file: abolute path to file +-- Returns +-- file: filename +-- path: table with directories to reach filename +-- isdir: is it a directory or a file +----------------------------------------------------------------------------- +local split_path = function(file) + local path = {} + local isdir + file = file or "/" + -- directory ends with a '/' + _,_, isdir = strfind(file, "([/])$") + gsub(file, "([^/]+)", function (dir) tinsert(%path, dir) end) + if not isdir then file = tremove(path) + else file = nil end + return file, path, isdir +end + +----------------------------------------------------------------------------- +-- Check server greeting +-- Input +-- control: control connection with server +-- Returns +-- code: nil if error +-- answer: server answer or error message +----------------------------------------------------------------------------- +local check_greeting = function(control) + local code, answer = %check_answer(control, {120, 220}) + if not code then return nil, answer end + if code == 120 then -- please try again, somewhat busy now... + code, answer = %check_answer(control, {220}) + end + return code, answer +end + +----------------------------------------------------------------------------- +-- Log in on server +-- Input +-- control: control connection with server +-- user: user name +-- pass: user password if any +-- Returns +-- code: nil if error +-- answer: server answer or error message +----------------------------------------------------------------------------- +local login = function(control, user, pass) + local code, answer = %try_command(control, "user", parsed.user, {230, 331}) + if not code then return nil, answer end + if code == 331 and parsed.pass then -- need pass and we have pass + code, answer = %try_command(control, "pass", parsed.pass, {230, 202}) + end + return code, answer +end + +----------------------------------------------------------------------------- +-- Change to target directory +-- Input +-- control: socket for control connection with server +-- path: array with directories in order +-- Returns +-- code: nil if error +-- answer: server answer or error message +----------------------------------------------------------------------------- +local cwd = function(control, path) + local code, answer = 250, "Home directory used" + for i = 1, getn(path) do + code, answer = %try_command(control, "cwd", path[i], {250}) + if not code then return nil, answer end + end + return code, answer +end + +----------------------------------------------------------------------------- +-- Start data connection with server +-- Input +-- control: control connection with server +-- Returns +-- data: socket for data connection with server, nil if error +-- answer: server answer or error message +----------------------------------------------------------------------------- +local start_dataconnection = function(control) + -- ask for passive data connection + local code, answer = %try_command(control, "pasv", nil, {227}) + if not code then return nil, answer end + -- get data connection parameters from server reply + local host, port = %get_pasv(answer) + if not host or not port then return nil, answer end + -- start data connection with given parameters + local data, err = connect(host, port) + if not data then return nil, err end + data:timeout(%TIMEOUT) + return data +end + +----------------------------------------------------------------------------- +-- Closes control connection with server +-- Input +-- control: control connection with server +-- Returns +-- code: nil if error +-- answer: server answer or error message +----------------------------------------------------------------------------- +local logout = function(control) + local code, answer = %try_command(control, "quit", nil, {221}) + if not code then return nil, answer end + control:close() + return code, answer +end + +----------------------------------------------------------------------------- +-- Retrieves file or directory listing +-- Input +-- control: control connection with server +-- data: data connection with server +-- file: file name under current directory +-- isdir: is file a directory name? +-- Returns +-- file: string with file contents, nil if error +-- answer: server answer or error message +----------------------------------------------------------------------------- +local retrieve_file = function(control, data, file, isdir) + -- ask server for file or directory listing accordingly + if isdir then code, answer = %try_command(control, "nlst", file, {150, 125}) + else code, answer = %try_command(control, "retr", file, {150, 125}) end + if not code then + control:close() + data:close() + return nil, answer + end + -- download whole file + file, err = data:receive("*a") + data:close() + if err then + control:close() + return nil, err + end + -- make sure file transfered ok + code, answer = %check_answer(control, {226, 250}) + if not code then return nil, answer + else return file, answer end +end + +----------------------------------------------------------------------------- +-- Stores a file +-- Input +-- control: control connection with server +-- data: data connection with server +-- file: file name under current directory +-- bytes: file contents in string +-- Returns +-- file: string with file contents, nil if error +-- answer: server answer or error message +----------------------------------------------------------------------------- +local store_file = function (control, data, file, bytes) + local code, answer = %try_command(control, "stor", file, {150, 125}) + if not code then + data:close() + return nil, answer + end + -- send whole file and close connection to mark file end + answer = data:send(bytes) + data:close() + if answer then + control:close() + return nil, answer + end + -- check if file was received right + return %check_answer(control, {226, 250}) +end + +----------------------------------------------------------------------------- +-- Change transfer type +-- Input +-- control: control connection with server +-- type: new transfer type +-- Returns +-- code: nil if error +-- answer: server answer or error message +----------------------------------------------------------------------------- +local change_type = function(control, type) + if type == "b" then type = "i" else type = "a" end + return %try_command(control, "type", type, {200}) +end + +----------------------------------------------------------------------------- +-- Retrieve a file from a ftp server +-- Input +-- url: file location +-- type: "binary" or "ascii" +-- Returns +-- file: downloaded file or nil in case of error +-- err: error message if any +----------------------------------------------------------------------------- +function ftp_get(url, type) + local control, data, err + local answer, code, server, file, path + parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) + -- start control connection + control, err = connect(parsed.host, parsed.port) + if not control then return nil, err end + control:timeout(%TIMEOUT) + -- get and check greeting + code, answer = %check_greeting(control) + if not code then return nil, answer end + -- try to log in + code, answer = %login(control, parsed.user, parsed.pass) + if not code then return nil, answer end + -- go to directory + file, path, isdir = %split_path(parsed.path) + code, answer = %cwd(control, path) + if not code then return nil, answer end + -- change to binary type? + code, answer = %change_type(control, type) + if not code then return nil, answer end + -- start data connection + data, answer = %start_dataconnection(control) + if not data then return nil, answer end + -- ask server to send file or directory listing + file, answer = %retrieve_file(control, data, file, isdir) + if not file then return nil, answer end + -- disconnect + %logout(control) + -- return whatever file we received plus a possible error + return file, answer +end + +----------------------------------------------------------------------------- +-- Uploads a file to a FTP server +-- Input +-- url: file location +-- bytes: file contents +-- type: "binary" or "ascii" +-- Returns +-- err: error message if any +----------------------------------------------------------------------------- +function ftp_put(url, bytes, type) + local control, data + local answer, code, server, file, path + parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) + -- start control connection + control, answer = connect(parsed.host, parsed.port) + if not control then return answer end + control:timeout(%TIMEOUT) + -- get and check greeting + code, answer = %check_greeting(control) + if not code then return answer end + -- try to log in + code, answer = %login(control, parsed.user, parsed.pass) + if not code then return answer end + -- go to directory + file, path, isdir = %split_path(parsed.path) + code, answer = %cwd(control, path) + if not code then return answer end + -- change to binary type? + code, answer = %change_type(control, type) + if not code then return answer end + -- start data connection + data, answer = %start_dataconnection(control) + if not data then return answer end + -- ask server to send file or directory listing + code, answer = %store_file(control, data, file, bytes) + if not code then return answer end + -- disconnect + %logout(control) + -- return whatever file we received plus a possible error + return nil +end diff --git a/src/http.lua b/src/http.lua new file mode 100644 index 0000000..8f08725 --- /dev/null +++ b/src/http.lua @@ -0,0 +1,312 @@ +----------------------------------------------------------------------------- +-- Simple HTTP/1.1 support for the Lua language using the LuaSocket toolkit. +-- Author: Diego Nehab +-- Date: 26/12/2000 +-- Conforming to: RFC 2068 +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- connection timeout in seconds +local TIMEOUT = 60 +-- default port for document retrieval +local PORT = 80 +-- user agent field sent in request +local USERAGENT = "LuaSocket/HTTP 1.0" + +----------------------------------------------------------------------------- +-- Tries to get a line from the server or close socket if error +-- sock: socket connected to the server +-- Returns +-- line: line received or nil in case of error +-- err: error message if any +----------------------------------------------------------------------------- +local try_getline = function(sock) + line, err = sock:receive() + if err then + sock:close() + return nil, err + end + return line +end + +----------------------------------------------------------------------------- +-- Tries to send a line to the server or close socket if error +-- sock: socket connected to the server +-- line: line to send +-- Returns +-- err: error message if any +----------------------------------------------------------------------------- +local try_sendline = function(sock, line) + err = sock:send(line) + if err then sock:close() end + return err +end + +----------------------------------------------------------------------------- +-- Retrieves status from http reply +-- Input +-- reply: http reply string +-- Returns +-- status: integer with status code +----------------------------------------------------------------------------- +local get_status = function(reply) + local _,_, status = strfind(reply, " (%d%d%d) ") + return tonumber(status) +end + +----------------------------------------------------------------------------- +-- Receive server reply messages +-- Input +-- sock: server socket +-- Returns +-- status: server reply status code or nil if error +-- reply: full server reply +-- err: error message if any +----------------------------------------------------------------------------- +local get_reply = function(sock) + local reply, err + reply, err = %try_getline(sock) + if not err then return %get_status(reply), reply + else return nil, nil, err end +end + +----------------------------------------------------------------------------- +-- Receive and parse mime headers +-- Input +-- sock: server socket +-- mime: a table that might already contain mime headers +-- Returns +-- mime: a table with all mime headers in the form +-- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"} +-- all name_i are lowercase +-- nil and error message in case of error +----------------------------------------------------------------------------- +local get_mime = function(sock, mime) + local line, err + local name, value + -- get first line + line, err = %try_getline(sock) + if err then return nil, err end + -- headers go until a blank line is found + while line ~= "" do + -- get field-name and value + _,_, name, value = strfind(line, "(.-):%s*(.*)") + name = strlower(name) + -- get next line (value might be folded) + line, err = %try_getline(sock) + if err then return nil, err end + -- unfold any folded values + while not err and line ~= "" and (strsub(line, 1, 1) == " ") do + value = value .. line + line, err = %try_getline(sock) + if err then return nil, err end + end + -- save pair in table + if mime[name] then + -- join any multiple field + mime[name] = mime[name] .. ", " .. value + else + -- new field + mime[name] = value + end + end + return mime +end + +----------------------------------------------------------------------------- +-- Receives http body +-- Input +-- sock: server socket +-- mime: initial mime headers +-- Returns +-- body: a string containing the body of the document +-- nil and error message in case of error +-- Obs: +-- mime: headers might be modified by chunked transfer +----------------------------------------------------------------------------- +local get_body = function(sock, mime) + local body, err + if mime["transfer-encoding"] == "chunked" then + local chunk_size, line + body = "" + repeat + -- get chunk size, skip extention + line, err = %try_getline(sock) + if err then return nil, err end + chunk_size = tonumber(gsub(line, ";.*", ""), 16) + if not chunk_size then + sock:close() + return nil, "invalid chunk size" + end + -- get chunk + line, err = sock:receive(chunk_size) + if err then + sock:close() + return nil, err + end + -- concatenate new chunk + body = body .. line + -- skip blank line + _, err = %try_getline(sock) + if err then return nil, err end + until chunk_size <= 0 + -- store extra mime headers + --_, err = %get_mime(sock, mime) + --if err then return nil, err end + elseif mime["content-length"] then + body, err = sock:receive(tonumber(mime["content-length"])) + if err then + sock:close() + return nil, err + end + else + -- get it all until connection closes! + body, err = sock:receive("*a") + if err then + sock:close() + return nil, err + end + end + -- return whole body + return body +end + +----------------------------------------------------------------------------- +-- Parses a url and returns its scheme, user, password, host, port +-- and path components, according to RFC 1738, Uniform Resource Locators (URL), +-- of December 1994 +-- Input +-- url: unique resource locator desired +-- default: table containing default values to be returned +-- Returns +-- table with the following fields: +-- host: host to connect +-- path: url path +-- port: host port to connect +-- user: user name +-- pass: password +-- scheme: protocol +----------------------------------------------------------------------------- +local split_url = function(url, default) + -- initialize default parameters + local parsed = default or {} + -- get scheme + url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end) + -- get user name and password. both can be empty! + -- moreover, password can be ommited + url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p) + %parsed.user = u + -- there can be an empty password, but the ':' has to be there + -- or else there is no password + %parsed.pass = nil -- kill default password + if c == ":" then %parsed.pass = p end + end) + -- get host + url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end) + -- get port if any + url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end) + -- whatever is left is the path + if url ~= "" then parsed.path = url end + return parsed +end + +----------------------------------------------------------------------------- +-- Sends a GET message through socket +-- Input +-- socket: http connection socket +-- path: path requested +-- mime: mime headers to send in request +-- Returns +-- err: nil in case of success, error message otherwise +----------------------------------------------------------------------------- +local send_get = function(sock, path, mime) + local err = %try_sendline(sock, "GET " .. path .. " HTTP/1.1\r\n") + if err then return err end + for i, v in mime do + err = %try_sendline(sock, i .. ": " .. v .. "\r\n") + if err then return err end + end + err = %try_sendline(sock, "\r\n") + return err +end + +----------------------------------------------------------------------------- +-- Converts field names to lowercase +-- Input +-- headers: user header fields +-- parsed: parsed url components +-- Returns +-- mime: a table with the same headers, but with lowercase field names +----------------------------------------------------------------------------- +local fill_headers = function(headers, parsed) + local mime = {} + headers = headers or {} + for i,v in headers do + mime[strlower(i)] = v + end + mime["connection"] = "close" + mime["host"] = parsed.host + mime["user-agent"] = %USERAGENT + if parsed.user and parsed.pass then -- Basic Authentication + mime["authorization"] = "Basic ".. + base64(parsed.user .. ":" .. parsed.pass) + end + return mime +end + +----------------------------------------------------------------------------- +-- We need base64 convertion routines for Basic Authentication Scheme +----------------------------------------------------------------------------- +dofile("base64.lua") + +----------------------------------------------------------------------------- +-- Downloads and receives a http url, with its mime headers +-- Input +-- url: unique resource locator desired +-- headers: headers to send with request +-- tried: is this an authentication retry? +-- Returns +-- body: document body, if successfull +-- mime: headers received with document, if sucessfull +-- reply: server reply, if successfull +-- err: error message, if any +----------------------------------------------------------------------------- +function http_get(url, headers) + local sock, err, mime, body, status, reply + -- get url components + local parsed = %split_url(url, {port = %PORT, path ="/"}) + -- fill default headers + headers = %fill_headers(headers, parsed) + -- try connection + sock, err = connect(parsed.host, parsed.port) + if not sock then return nil, nil, nil, err end + -- set connection timeout + sock:timeout(%TIMEOUT) + -- send request + err = %send_get(sock, parsed.path, headers) + if err then return nil, nil, nil, err end + -- get server message + status, reply, err = %get_reply(sock) + if err then return nil, nil, nil, err end + -- get url accordingly + if status == 200 then -- ok, go on and get it + mime, err = %get_mime(sock, {}) + if err then return nil, nil, reply, err end + body, err = %get_body(sock, mime) + if err then return nil, mime, reply, err end + sock:close() + return body, mime, reply + elseif status == 301 then -- moved permanently, try again + mime = %get_mime(sock, {}) + sock:close() + if mime["location"] then return http_get(mime["location"], headers) + else return nil, mime, reply end + elseif status == 401 then + mime, err = %get_mime(sock, {}) + if err then return nil, nil, reply, err end + return nil, mime, reply + end + return nil, nil, reply +end diff --git a/src/luasocket.h b/src/luasocket.h new file mode 100644 index 0000000..d4037cd --- /dev/null +++ b/src/luasocket.h @@ -0,0 +1,18 @@ +/*=========================================================================*\ +* TCP/IP support for LUA +* Diego Nehab +* 9/11/1999 +\*=========================================================================*/ + +#ifndef _LUASOCKET_H_ +#define _LUASOCKET_H_ + +/*=========================================================================*\ +* Exported function declarations +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Initializes toolkit +\*-------------------------------------------------------------------------*/ +void lua_socketlibopen(lua_State *L); + +#endif /* _LUASOCKET_H_ */ diff --git a/src/smtp.lua b/src/smtp.lua new file mode 100644 index 0000000..f9ed64c --- /dev/null +++ b/src/smtp.lua @@ -0,0 +1,338 @@ +----------------------------------------------------------------------------- +-- Simple SMTP support for the Lua language using the LuaSocket toolkit. +-- Author: Diego Nehab +-- Date: 26/12/2000 +-- Conforming to: RFC 821 +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout in secconds before we give up waiting +local TIMEOUT = 180 +-- port used for connection +local PORT = 25 +-- domain used in HELO command. If we are under a CGI, try to get from +-- environment +local DOMAIN = getenv("SERVER_NAME") +if not DOMAIN then + DOMAIN = "localhost" +end + +----------------------------------------------------------------------------- +-- Tries to send DOS mode lines. Closes socket on error. +-- Input +-- sock: server socket +-- line: string to be sent +-- Returns +-- err: message in case of error, nil if successfull +----------------------------------------------------------------------------- +local puts = function(sock, line) + local err = sock:send(line .. "\r\n") + if err then sock:close() end + return err +end + +----------------------------------------------------------------------------- +-- Tries to receive DOS mode lines. Closes socket on error. +-- Input +-- sock: server socket +-- Returns +-- line: received string if successfull, nil in case of error +-- err: error message if any +----------------------------------------------------------------------------- +local gets = function(sock) + local line, err = sock:receive("*l") + if err then + sock:close() + return nil, err + end + return line +end + +----------------------------------------------------------------------------- +-- Gets a reply from the server and close connection if it is wrong +-- Input +-- sock: server socket +-- accept: acceptable errorcodes +-- Returns +-- code: server reply code. nil if error +-- line: complete server reply message or error message +----------------------------------------------------------------------------- +local get_reply = function(sock, accept) + local line, err = %gets(sock) + if line then + if type(accept) ~= "table" then accept = {accept} end + local _,_, code = strfind(line, "^(%d%d%d)") + if not code then return nil, line end + code = tonumber(code) + for i = 1, getn(accept) do + if code == accept[i] then return code, line end + end + sock:close() + return nil, line + end + return nil, err +end + +----------------------------------------------------------------------------- +-- Sends a command to the server +-- Input +-- sock: server socket +-- command: command to be sent +-- param: command parameters if any +-- Returns +-- err: error message if any +----------------------------------------------------------------------------- +local send_command = function(sock, command, param) + local line + if param then line = command .. " " .. param + else line = command end + return %puts(sock, line) +end + +----------------------------------------------------------------------------- +-- Gets the initial server greeting +-- Input +-- sock: server socket +-- Returns +-- code: server status code, nil if error +-- answer: complete server reply +----------------------------------------------------------------------------- +local get_helo = function(sock) + return %get_reply(sock, 220) +end + +----------------------------------------------------------------------------- +-- Sends initial client greeting +-- Input +-- sock: server socket +-- Returns +-- code: server status code, nil if error +-- answer: complete server reply +----------------------------------------------------------------------------- +local send_helo = function(sock) + local err = %send_command(sock, "HELO", %DOMAIN) + if not err then + return %get_reply(sock, 250) + else return nil, err end +end + +----------------------------------------------------------------------------- +-- Sends mime headers +-- Input +-- sock: server socket +-- mime: table with mime headers to be sent +-- Returns +-- err: error message if any +----------------------------------------------------------------------------- +local send_mime = function(sock, mime) + local err + mime = mime or {} + -- send all headers + for name,value in mime do + err = sock:send(name .. ": " .. value .. "\r\n") + if err then + sock:close() + return err + end + end + -- end mime part + err = sock:send("\r\n") + if err then sock:close() end + return err +end + +----------------------------------------------------------------------------- +-- Sends connection termination command +-- Input +-- sock: server socket +-- Returns +-- code: server status code, nil if error +-- answer: complete server reply +----------------------------------------------------------------------------- +local send_quit = function(sock) + local code, answer + local err = %send_command(sock, "QUIT") + if not err then + code, answer = %get_reply(sock, 221) + sock:close() + return code, answer + else return nil, err end +end + +----------------------------------------------------------------------------- +-- Sends sender command +-- Input +-- sock: server socket +-- sender: e-mail of sender +-- Returns +-- code: server status code, nil if error +-- answer: complete server reply +----------------------------------------------------------------------------- +local send_mail = function(sock, sender) + local param = format("FROM:<%s>", sender) + local err = %send_command(sock, "MAIL", param) + if not err then + return %get_reply(sock, 250) + else return nil, err end +end + +----------------------------------------------------------------------------- +-- Sends message mime headers and body +-- Input +-- sock: server socket +-- mime: table containing all mime headers to be sent +-- body: message body +-- Returns +-- code: server status code, nil if error +-- answer: complete server reply +----------------------------------------------------------------------------- +local send_data = function (sock, mime, body) + local err = %send_command(sock, "DATA") + if not err then + local code, answer = %get_reply(sock, 354) + if not code then return nil, answer end + -- avoid premature end in message body + body = gsub(body or "", "\n%.", "\n%.%.") + -- mark end of message body + body = body .. "\r\n." + err = %send_mime(sock, mime) + if err then return nil, err end + err = %puts(sock, body) + return %get_reply(sock, 250) + else return nil, err end +end + +----------------------------------------------------------------------------- +-- Sends recipient list command +-- Input +-- sock: server socket +-- rcpt: lua table with recipient list +-- Returns +-- code: server status code, nil if error +-- answer: complete server reply +----------------------------------------------------------------------------- +local send_rcpt = function(sock, rcpt) + local err, code, answer + if type(rcpt) ~= "table" then rcpt = {rcpt} end + for i = 1, getn(rcpt) do + err = %send_command(sock, "RCPT", format("TO:<%s>", rcpt[i])) + if not err then + code, answer = %get_reply(sock, {250, 251}) + if not code then return code, answer end + else return nil, err end + end + return code, answer +end + +----------------------------------------------------------------------------- +-- Sends verify recipient command +-- Input +-- sock: server socket +-- user: user to be verified +-- Returns +-- code: server status code, nil if error +-- answer: complete server reply +----------------------------------------------------------------------------- +local send_vrfy = function (sock, user) + local err = %send_command(sock, "VRFY", format("<%s>", user)) + if not err then + return %get_reply(sock, {250, 251}) + else return nil, err end +end + +----------------------------------------------------------------------------- +-- Connection oriented mail functions +----------------------------------------------------------------------------- +function smtp_connect(server) + local code, answer + -- connect to server + local sock, err = connect(server, %PORT) + if not sock then return nil, err end + sock:timeout(%TIMEOUT) + -- initial server greeting + code, answer = %get_helo(sock) + if not code then return nil, answer end + -- HELO + code, answer = %send_helo(sock) + if not code then return nil, answer end + return sock +end + +function smtp_send(sock, from, rcpt, mime, body) + local code, answer + -- MAIL + code, answer = %send_mail(sock, from) + if not code then return nil, answer end + -- RCPT + code, answer = %send_rcpt(sock, rcpt) + if not code then return nil, answer end + -- DATA + return %send_data(sock, mime, body) +end + +function smtp_close(sock) + -- QUIT + return %send_quit(sock) +end + +----------------------------------------------------------------------------- +-- Main mail function +-- Input +-- from: message sender +-- rcpt: table containing message recipients +-- mime: table containing mime headers +-- body: message body +-- server: smtp server to be used +-- Returns +-- nil if successfull, error message in case of error +----------------------------------------------------------------------------- +function smtp_mail(from, rcpt, mime, body, server) + local sock, err = smtp_connect(server) + if not sock then return err end + local code, answer = smtp_send(sock, from, rcpt, mime, body) + if not code then return answer end + code, answer = smtp_close(sock) + if not code then return answer + else return nil end +end + +--=========================================================================== +-- Compatibility functions +--=========================================================================== +----------------------------------------------------------------------------- +-- Converts a comma separated list into a Lua table with one entry for each +-- list element. +-- Input +-- str: string containing the list to be converted +-- tab: table to be filled with entries +-- Returns +-- a table t, where t.n is the number of elements with an entry t[i] +-- for each element +----------------------------------------------------------------------------- +local fill = function(str, tab) + gsub(str, "([^%s,]+)", function (w) tinsert(%tab, w) end) + return tab +end + +----------------------------------------------------------------------------- +-- Client mail function, implementing CGILUA 3.2 interface +----------------------------------------------------------------------------- +function mail(msg) + local rcpt = {} + local mime = {} + mime["Subject"] = msg.subject + mime["To"] = msg.to + mime["From"] = msg.from + %fill(msg.to, rcpt) + if msg.cc then + %fill(msg.cc, rcpt) + mime["Cc"] = msg.cc + end + if msg.bcc then + %fill(msg.bcc, rcpt) + end + rcpt.n = nil + return %smtp_mail(msg.from, rcpt, mime, msg.message, msg.mailserver) +end diff --git a/test/testclnt.lua b/test/testclnt.lua new file mode 100644 index 0000000..c1c22bd --- /dev/null +++ b/test/testclnt.lua @@ -0,0 +1,359 @@ +----------------------------------------------------------------------------- +-- LuaSocket automated test module +-- client.lua +-- This is the client module. It connects with the server module and executes +-- all tests. +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Prints a header to separate the test phases +-- Input +-- test: test phase name +----------------------------------------------------------------------------- +function new_test(test) + write("----------------------------------------------\n", + test, "\n", + "----------------------------------------------\n") +end + +----------------------------------------------------------------------------- +-- Read command definitions and stablish control connection +----------------------------------------------------------------------------- +new_test("initializing...") +dofile("command.lua") +test_debug_mode() +while control == nil do + print("client: trying control connection...") + control, err = connect(HOST, PORT) + if control then + print("client: control connection stablished!") + else + sleep(2) + end +end + +----------------------------------------------------------------------------- +-- Make sure server is ready for data transmission +----------------------------------------------------------------------------- +function sync() + send_command(SYNC) + get_command() +end + +----------------------------------------------------------------------------- +-- Close and reopen data connection, to get rid of any unread blocks +----------------------------------------------------------------------------- +function reconnect() + if data then + data:close() + send_command(CLOSE) + data = nil + end + while data == nil do + send_command(CONNECT) + data = connect(HOST, PORT) + if not data then + print("client: waiting for data connection.") + sleep(1) + end + end + sync() +end + +----------------------------------------------------------------------------- +-- Tests the command connection +----------------------------------------------------------------------------- +function test_command(cmd, par) + local cmd_back, par_back + reconnect() + send_command(COMMAND) + write("testing command ") + print_command(cmd, par) + send_command(cmd, par) + cmd_back, par_back = get_command() + if cmd_back ~= cmd or par_back ~= par then + fail(cmd) + else + pass() + end +end + +----------------------------------------------------------------------------- +-- Tests ASCII line transmission +-- Input +-- len: length of line to be tested +----------------------------------------------------------------------------- +function test_asciiline(len) + local str, str10, back, err + reconnect() + send_command(ECHO_LINE) + str = strrep("x", mod(len, 10)) + str10 = strrep("aZb.c#dAe?", floor(len/10)) + str = str .. str10 + write("testing ", len, " byte(s) line\n") + err = data:send(str, "\n") + if err then fail(err) end + back, err = data:receive() + if err then fail(err) end + if back == str then pass("lines match") + else fail("lines don't match") end +end + +----------------------------------------------------------------------------- +-- Tests closed connection detection +----------------------------------------------------------------------------- +function test_closed() + local str = "This is our little test line" + local len = strlen(str) + local back, err, total + reconnect() + print("testing close while reading line") + send_command(ECHO_BLOCK, len) + data:send(str) + send_command(CLOSE) + -- try to get a line + back, err = data:receive() + if not err then fail("shold have gotten 'closed'.") + elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") + elseif str ~= back then fail("didn't receive what i should 'closed'.") + else pass("rightfull 'closed' received") end + reconnect() + print("testing close while reading block") + send_command(ECHO_BLOCK, len) + data:send(str) + send_command(CLOSE) + -- try to get a line + back, err = data:receive(2*len) + if not err then fail("shold have gotten 'closed'.") + elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") + elseif str ~= back then fail("didn't receive what I should.") + else pass("rightfull 'closed' received") end +end + +----------------------------------------------------------------------------- +-- Tests binary line transmission +-- Input +-- len: length of line to be tested +----------------------------------------------------------------------------- +function test_rawline(len) + local str, str10, back, err + reconnect() + send_command(ECHO_LINE) + str = strrep(strchar(47), mod(len, 10)) + str10 = strrep(strchar(120,21,77,4,5,0,7,36,44,100), floor(len/10)) + str = str .. str10 + write("testing ", len, " byte(s) line\n") + err = data:send(str, "\n") + if err then fail(err) end + back, err = data:receive() + if err then fail(err) end + if back == str then pass("lines match") + else fail("lines don't match") end +end + +----------------------------------------------------------------------------- +-- Tests block transmission +-- Input +-- len: length of block to be tested +----------------------------------------------------------------------------- +function test_block(len) + local half = floor(len/2) + local s1, s2, back, err + reconnect() + send_command(ECHO_BLOCK, len) + write("testing ", len, " byte(s) block\n") + s1 = strrep("x", half) + err = data:send(s1) + if err then fail(err) end + sleep(1) + s2 = strrep("y", len-half) + err = data:send(s2) + if err then fail(err) end + back, err = data:receive(len) + if err then fail(err) end + if back == s1..s2 then pass("blocks match") + else fail("blocks don't match") end +end + +----------------------------------------------------------------------------- +-- Tests if return-timeout was respected +-- delta: time elapsed during transfer +-- t: timeout value +-- err: error code returned by I/O operation +----------------------------------------------------------------------------- +function returntimed_out(delta, t, err) + if err == "timeout" then + if delta + 0.1 >= t then + pass("got right timeout") + return 1 + else + fail("shouldn't have gotten timeout") + end + elseif delta > t then + fail("should have gotten timeout") + end +end + +----------------------------------------------------------------------------- +-- Tests if return-timeout was respected +-- delta: time elapsed during transfer +-- t: timeout value +-- err: error code returned by I/O operation +-- o: operation being executed +----------------------------------------------------------------------------- +function blockedtimed_out(t, s, err, o) + if err == "timeout" then + if s >= t then + pass("got right forced timeout") + return 1 + else + pass("got natural cause timeout (may be wrong)") + return 1 + end + elseif s > t then + if o == "send" then + pass("must have been buffered (may be wrong)") + else + fail("should have gotten timeout") + end + end +end + +----------------------------------------------------------------------------- +-- Tests blocked-timeout conformance +-- Input +-- len: length of block to be tested +-- t: timeout value +-- s: server sleep between transfers +----------------------------------------------------------------------------- +function test_blockedtimeout(len, t, s) + local str, err, back, total + reconnect() + send_command(RECEIVE_BLOCK, len) + send_command(SLEEP, s) + send_command(RECEIVE_BLOCK, len) + write("testing ", len, " bytes, ", t, + "s block timeout, ", s, "s sleep\n") + data:timeout(t) + str = strrep("a", 2*len) + err, total = data:send(str) + if blockedtimed_out(t, s, err, "send") then return end + if err then fail(err) end + send_command(SEND_BLOCK) + send_command(SLEEP, s) + send_command(SEND_BLOCK) + back, err = data:receive(2*len) + if blockedtimed_out(t, s, err, "receive") then return end + if err then fail(err) end + if back == str then pass("blocks match") + else fail("blocks don't match") end +end + +----------------------------------------------------------------------------- +-- Tests return-timeout conformance +-- Input +-- len: length of block to be tested +-- t: timeout value +-- s: server sleep between transfers +----------------------------------------------------------------------------- +function test_returntimeout(len, t, s) + local str, err, back, delta, total + reconnect() + send_command(RECEIVE_BLOCK, len) + send_command(SLEEP, s) + send_command(RECEIVE_BLOCK, len) + write("testing ", len, " bytes, ", t, + "s return timeout, ", s, "s sleep\n") + data:timeout(t, "return") + str = strrep("a", 2*len) + err, total, delta = data:send(str) + print("delta: " .. delta) + if returntimed_out(delta, t, err) then return end + if err then fail(err) end + send_command(SEND_BLOCK) + send_command(SLEEP, s) + send_command(SEND_BLOCK) + back, err, delta = data:receive(2*len) + print("delta: " .. delta) + if returntimed_out(delta, t, err) then return end + if err then fail(err) end + if back == str then pass("blocks match") + else fail("blocks don't match") end +end + +----------------------------------------------------------------------------- +-- Execute all tests +----------------------------------------------------------------------------- +new_test("control connection test") +test_command(EXIT) +test_command(CONNECT) +test_command(CLOSE) +test_command(ECHO_BLOCK, 12234) +test_command(SLEEP, 1111) +test_command(ECHO_LINE) + +new_test("connection close test") +test_closed() + +new_test("binary string test") +test_rawline(1) +test_rawline(17) +test_rawline(200) +test_rawline(3000) +test_rawline(8000) +test_rawline(40000) + +new_test("blocking transfer test") +test_block(1) +test_block(17) +test_block(200) +test_block(3000) +test_block(80000) +test_block(800000) + +new_test("non-blocking transfer test") +-- the value is not important, we only want +-- to test non-blockin I/O anyways +data:timeout(200) +test_block(1) +test_block(17) +test_block(200) +test_block(3000) +test_block(80000) +test_block(800000) +test_block(8000000) + +new_test("character string test") +test_asciiline(1) +test_asciiline(17) +test_asciiline(200) +test_asciiline(3000) +test_asciiline(8000) +test_asciiline(40000) + +new_test("return timeout test") +test_returntimeout(80, .5, 1) +test_returntimeout(80, 1, 0.5) +test_returntimeout(8000, .5, 0) +test_returntimeout(80000, .5, 0) +test_returntimeout(800000, .5, 0) + +new_test("blocked timeout test") +test_blockedtimeout(80, .5, 1) +test_blockedtimeout(80, 1, 1) +test_blockedtimeout(80, 1.5, 1) +test_blockedtimeout(800, 1, 0) +test_blockedtimeout(8000, 1, 0) +test_blockedtimeout(80000, 1, 0) +test_blockedtimeout(800000, 1, 0) + +----------------------------------------------------------------------------- +-- Close connection and exit server. We are done. +----------------------------------------------------------------------------- +new_test("the library has passed all tests") +print("client: closing connection with server") +send_command(CLOSE) +send_command(EXIT) +control:close() +print("client: exiting...") +exit() diff --git a/test/testsrvr.lua b/test/testsrvr.lua new file mode 100644 index 0000000..99ecd2a --- /dev/null +++ b/test/testsrvr.lua @@ -0,0 +1,90 @@ +----------------------------------------------------------------------------- +-- LuaSocket automated test module +-- server.lua +-- This is the server module. It's completely controled by the client module +-- by the use of a control connection. +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Read command definitions +----------------------------------------------------------------------------- +dofile("command.lua") +test_debug_mode() + +----------------------------------------------------------------------------- +-- Bind to address and wait for control connection +----------------------------------------------------------------------------- +server, err = bind(HOST, PORT) +if not server then + print(err) + exit(1) +end +print("server: waiting for control connection...") +control = server:accept() +print("server: control connection stablished!") + +----------------------------------------------------------------------------- +-- Executes a command, detecting any possible failures +-- Input +-- cmd: command to be executed +-- par: command parameters, if needed +----------------------------------------------------------------------------- +function execute_command(cmd, par) + if cmd == CONNECT then + print("server: waiting for data connection...") + data = server:accept() + if not data then + fail("server: unable to start data connection!") + else + print("server: data connection stablished!") + end + elseif cmd == CLOSE then + print("server: closing connection with client...") + if data then + data:close() + data = nil + end + elseif cmd == ECHO_LINE then + str, err = data:receive() + if err then fail("server: " .. err) end + err = data:send(str, "\n") + if err then fail("server: " .. err) end + elseif cmd == ECHO_BLOCK then + str, err = data:receive(par) + if err then fail("server: " .. err) end + err = data:send(str) + if err then fail("server: " .. err) end + elseif cmd == RECEIVE_BLOCK then + str, err = data:receive(par) + elseif cmd == SEND_BLOCK then + err = data:send(str) + elseif cmd == ECHO_TIMEOUT then + str, err = data:receive(par) + if err then fail("server: " .. err) end + err = data:send(str) + if err then fail("server: " .. err) end + elseif cmd == COMMAND then + cmd, par = get_command() + send_command(cmd, par) + elseif cmd == EXIT then + print("server: exiting...") + exit(0) + elseif cmd == SYNC then + print("server: synchronizing...") + send_command(SYNC) + elseif cmd == SLEEP then + print("server: sleeping for " .. par .. " seconds...") + sleep(par) + print("server: woke up!") + end +end + +----------------------------------------------------------------------------- +-- Loop forever, accepting and executing commands +----------------------------------------------------------------------------- +while 1 do + cmd, par = get_command() + if not cmd then fail("server: " .. par) end + print_command(cmd, par) + execute_command(cmd, par) +end From 41643c2643751de17125da4c23b616840dc0f1e3 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Sat, 13 Jan 2001 07:10:00 +0000 Subject: [PATCH 005/483] All input from sockets is now buffered. This has drastically improved line I/O. The code is much simpler now, too. All timeout management has been rewritten. --- src/luasocket.c | 596 ++++++++++++++++++++++-------------------------- 1 file changed, 274 insertions(+), 322 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index 2216b90..4f03ad2 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -6,9 +6,7 @@ * Module: LUASOCKET.C * * This module is part of an effort to make the most important features -* of the TCP/IP protocol available for Lua scripts. -* The main intent of the project was the distribution with the CGILua -* toolkit, in which is is used to implement the SMTP client functions. +* of the TCP/IP protocol available to Lua scripts. * The Lua interface to TCP/IP follows the BSD TCP/IP API closely, * trying to simplify all tasks involved in setting up a client connection * and simple server connections. @@ -21,16 +19,15 @@ /*=========================================================================*\ * Common include files \*=========================================================================*/ +#include +#include +#include #include #include -#include #include -#include -#include - -#include #include +#include #include #include "luasocket.h" @@ -41,11 +38,11 @@ #ifdef WIN32 #include #include +#else /*=========================================================================*\ * BSD include files \*=========================================================================*/ -#else /* close function */ #include /* fnctnl function and associated constants */ @@ -100,6 +97,12 @@ #define NET_TIMEOUT 0 /* operation timed out */ #define NET_CLOSED 1 /* the connection has been closed */ +/*-------------------------------------------------------------------------*\ +* Time out mode to be checked +\*-------------------------------------------------------------------------*/ +#define TM_RECEIVE 1 +#define TM_SEND 2 + /*-------------------------------------------------------------------------*\ * As far as a Lua script is concerned, there are two kind of objects * representing a socket. A client socket is an object created by the @@ -107,7 +110,7 @@ * and close. A server socket is an object created by the function bind, * and implementing the methods listen, accept and close. Lua tag values * for these objects are created in the lua_socketlibopen function, and -* passed as closure values (first argumnents to every library function, +* passed as closure values (last argumnents to every library function) # because we can't have any global variables. \*-------------------------------------------------------------------------*/ #define CLIENT_TAG -2 @@ -115,27 +118,30 @@ /*-------------------------------------------------------------------------*\ * Both socket types are stored in the same structure to simplify -* implementation. The tag value used is different, though. The timeout -* fields are not used for the server socket object. -* There are two timeout values. The block timeout specifies the maximum -* time the any IO operation performed by luasocket can be blocked waiting -* for completion. The return timeout specifies the maximum time a Lua script -* can be blocked waiting for an luasocket IO operation to complete. +* implementation. The tag value used is different, though. +* The timeout and buffer parameters are not used by server sockets. \*-------------------------------------------------------------------------*/ typedef struct t_sock { - SOCKET sock; /* operating system socket object */ - int b; /* block timeout in ms */ - int r; /* return timeout in ms */ - int blocking; /* is this socket in blocking mode? */ + /* operating system socket object */ + SOCKET sock; + /* start time of the current operation */ + int tm_start; +#ifdef _DEBUG + /* end time of current operation, for debug purposes */ + int tm_end; +#endif + /* return and blocking timeout values (-1 if no limit) */ + int tm_return, tm_block; + /* buffered I/O storage */ + unsigned char bf_buffer[LUASOCKET_BUFFERSIZE]; + /* first and last red bytes not yet passed to application */ + int bf_first, bf_last; } t_sock; typedef t_sock *p_sock; /*-------------------------------------------------------------------------*\ * Macros and internal declarations \*-------------------------------------------------------------------------*/ -/* return time since marked start in ms */ -#define time_since(start) (get_time()-start) - /* min and max macros */ #ifndef min #define min(x, y) ((x) < (y) ? x : y) @@ -157,6 +163,24 @@ static int net_receive(lua_State *L); static int net_timeout(lua_State *L); static int net_close(lua_State *L); +/* buffered I/O management */ +static const unsigned char *bf_receive(p_sock sock, int *length); +static void bf_skip(p_sock sock, int length); +static int bf_isempty(p_sock sock); + +/* timeout management */ +static int tm_timedout(p_sock sock, int mode); +static int tm_gettimeleft(p_sock sock); +static int tm_gettime(void); +static void tm_markstart(p_sock sock); + +/* I/O */ +static int send_raw(p_sock sock, const char *data, int wanted, int *err); +static int receive_raw(lua_State *L, p_sock sock, int wanted); +static int receive_dosline(lua_State *L, p_sock sock); +static int receive_unixline(lua_State *L, p_sock sock); +static int receive_all(lua_State *L, p_sock sock); + /* fallbacks */ static int server_gettable(lua_State *L); static int client_gettable(lua_State *L); @@ -170,7 +194,7 @@ static p_sock check_sock(lua_State *L, int numArg, int server_tag, static void pop_tags(lua_State *L, int *client_tag, int *server_tag); static void push_tags(lua_State *L, int client_tag, int server_tag); -/* result handling routines */ +/* error code translations functions */ static char *host_strerror(void); static char *bind_strerror(void); static char *sock_strerror(void); @@ -181,7 +205,6 @@ static void push_client(lua_State *L, p_sock sock, int client_tag); static void push_server(lua_State *L, p_sock sock, int server_tag); /* plataform specific functions */ -static int get_time(void); static void set_blocking(p_sock sock); static void set_nonblocking(p_sock sock); @@ -190,19 +213,6 @@ static p_sock create_sock(void); static p_sock create_tcpsock(void); static int fill_sockaddr(struct sockaddr_in *server, const char *hostname, unsigned short port); -static int get_timeout(p_sock sock, int elapsed); -static int read_or_timeout(p_sock sock, int elapsed); -static int write_or_timeout(p_sock sock, int elapsed); -static int send_raw(p_sock sock, const char *data, int wanted, - int start, int *err, int *end); -static void receive_raw(lua_State *L, p_sock sock, int wanted, - int start, int *err, int *end); -static void receive_dosline(lua_State *L, p_sock sock, int start, - int *err, int *end); -static void receive_unixline(lua_State *L, p_sock sock, int start, - int *err, int *end); -static void receive_all(lua_State *L, p_sock sock, int start, - int *err, int *end); /*=========================================================================*\ * Test support functions @@ -214,7 +224,7 @@ static void receive_all(lua_State *L, p_sock sock, int start, static int net_time(lua_State *L); static int net_time(lua_State *L) { - lua_pushnumber(L, get_time()/1000.0); + lua_pushnumber(L, tm_gettime()/1000.0); return 1; } @@ -279,6 +289,7 @@ static int net_connect(lua_State *L) lua_pushstring(L, connect_strerror()); return 2; } + set_nonblocking(sock); push_client(L, sock, client_tag); lua_pushnil(L); return 2; @@ -341,6 +352,7 @@ static int net_accept(lua_State *L) return 2; } else { client->sock = client_sock; + set_nonblocking(client); push_client(L, client, client_tag); lua_pushnil(L); return 2; @@ -431,10 +443,10 @@ static int net_timeout(lua_State *L) mode = luaL_opt_string(L, 3, "b"); switch (*mode) { case 'b': - sock->b = ms; + sock->tm_block = ms; break; case 'r': - sock->r = ms; + sock->tm_return = ms; break; default: luaL_arg_check(L, 0, 3, "invalid timeout mode"); @@ -459,35 +471,26 @@ static int net_send(lua_State *L) { p_sock sock; const char *data; - size_t size; - int start = get_time(); + int wanted; long total = 0; int arg; int err = NET_DONE; - int end; int top; int client_tag, server_tag; pop_tags(L, &client_tag, &server_tag); top = lua_gettop(L); sock = check_client(L, 1, client_tag); -#ifdef _DEBUG_BLOCK -printf("luasocket: send start\n"); -#endif + tm_markstart(sock); for (arg = 2; arg <= top; arg++) { - data = luaL_opt_lstr(L, arg, NULL, &size); - if (!data || err != NET_DONE) - break; - total += send_raw(sock, data, size, start, &err, &end); + data = luaL_opt_lstr(L, arg, NULL, &wanted); + if (!data || err != NET_DONE) break; + total += send_raw(sock, data, wanted, &err); } push_error(L, err); lua_pushnumber(L, (double) total); -#ifdef _DEBUG_BLOCK -printf("luasocket: send end\n"); -#endif #ifdef _DEBUG -/* pass the time elapsed during function execution to Lua, so that -** the test script can make sure we respected the timeouts */ -lua_pushnumber(L, (end-start)/1000.0); + /* push time elapsed during operation as the last return value */ + lua_pushnumber(L, (sock->tm_end - sock->tm_start)/1000.0); #endif return lua_gettop(L) - top; } @@ -501,33 +504,31 @@ lua_pushnumber(L, (end-start)/1000.0); * by a LF character, preceded or not by a CR character. This is * the default pattern * "*lu": reads a text line, terminanted by a CR character only. (Unix mode) +* "*a": reads until connection closed * number: reads 'number' characters from the socket * Returns * On success: one string for each pattern * On error: all strings for which there was no error, followed by one -* nil value for each failed string, followed by an error code +* nil value for the remaining strings, followed by an error code \*-------------------------------------------------------------------------*/ static int net_receive(lua_State *L) { static const char *const modenames[] = {"*l", "*lu", "*a", NULL}; int err = NET_DONE, arg = 2; - int start = get_time(); - int end; + const char *mode; int client_tag, server_tag; int top; p_sock sock; - const char *mode; pop_tags(L, &client_tag, &server_tag); sock = check_client(L, 1, client_tag); -#ifdef _DEBUG_BLOCK -printf("luasocket: receive start\n"); -#endif + tm_markstart(sock); /* push default pattern */ top = lua_gettop(L); if (top < 2) { lua_pushstring(L, "*l"); top++; } + /* receive all patterns */ for (arg = 2; arg <= top; arg++) { /* if one pattern failed, we just skip all other patterns */ if (err != NET_DONE) { @@ -536,24 +537,24 @@ printf("luasocket: receive start\n"); } if (lua_isnumber(L, arg)) { long size = (long) lua_tonumber(L, arg); - receive_raw(L, sock, size, start, &err, &end); + err = receive_raw(L, sock, size); } else { mode = luaL_opt_string(L, arg, NULL); /* get next pattern */ switch (luaL_findstring(mode, modenames)) { /* DOS line mode */ case 0: - receive_dosline(L, sock, start, &err, &end); + err = receive_dosline(L, sock); break; /* Unix line mode */ case 1: - receive_unixline(L, sock, start, &err, &end); + err = receive_unixline(L, sock); break; - /* 'Til closed mode */ + /* until closed mode */ case 2: - receive_all(L, sock, start, &err, &end); + err = receive_all(L, sock); break; - /* else it must be a number, raw mode */ + /* else it is an error */ default: luaL_arg_check(L, 0, arg, "invalid receive pattern"); break; @@ -562,13 +563,9 @@ printf("luasocket: receive start\n"); } /* last return is an error code */ push_error(L, err); -#ifdef _DEBUG_BLOCK -printf("luasocket: receive end\n"); -#endif #ifdef _DEBUG -/* pass the time elapsed during function execution to Lua, so that -** the test script can make sure we respected the timeouts */ -lua_pushnumber(L, (end-start)/1000.0); + /* push time elapsed during operation as the last return value */ + lua_pushnumber(L, (sock->tm_end - sock->tm_start)/1000.0); #endif return lua_gettop(L) - top; } @@ -598,8 +595,8 @@ static int net_close(lua_State *L) \*-------------------------------------------------------------------------*/ static int client_gettable(lua_State *L) { - static const char *const net_api[] = {"receive","send","timeout","close", - "connect", NULL}; + static const char *const net_api[] = + {"receive","send","timeout","close", "connect", NULL}; const char *idx = luaL_check_string(L, 2); int server_tag, client_tag; pop_tags(L, &client_tag, &server_tag); @@ -700,12 +697,11 @@ static void handle_sigpipe(void) static p_sock create_sock(void) { p_sock sock = (p_sock) malloc(sizeof(t_sock)); - if (!sock) - return NULL; + if (!sock) return NULL; sock->sock = -1; - sock->r = -1; - sock->b = -1; - sock->blocking = 1; + sock->tm_block = -1; + sock->tm_return = -1; + sock->bf_first = sock->bf_last = 0; return sock; } @@ -777,63 +773,27 @@ static int fill_sockaddr(struct sockaddr_in *address, const char *hostname, } /*-------------------------------------------------------------------------*\ -* Determine the time limit to be passed to the select function, given -* the time elapsed since the beginning of the operation. +* Determines how much time we have left for the current io operation +* an IO write operation. * Input * sock: socket structure being used in operation -* elapsed: time elapsed since operation started * Returns -* time limit before function return in ms or -1 in case there is no -* time limit +* the number of ms left or -1 if there is no time limit \*-------------------------------------------------------------------------*/ -static int get_timeout(p_sock sock, int elapsed) +static int tm_gettimeleft(p_sock sock) { /* no timeout */ - if (sock->b < 0 && sock->r < 0) + if (sock->tm_block < 0 && sock->tm_return < 0) return -1; /* there is no block timeout, we use the return timeout */ - if (sock->b < 0) - return max(0, sock->r - elapsed); + else if (sock->tm_block < 0) + return max(sock->tm_return - tm_gettime() + sock->tm_start, 0); /* there is no return timeout, we use the block timeout */ - else if (sock->r < 0) - return sock->b; + else if (sock->tm_return < 0) + return sock->tm_block; /* both timeouts are specified */ - else - return min(sock->b, max(0, sock->r - elapsed)); -} - -/*-------------------------------------------------------------------------*\ -* Determines if we have a timeout condition or if we can proceed with -* an IO read operation. -* Input -* sock: socket structure being used in operation -* elapsed: time elapsed since operation started -* Returns -* 1 if we can proceed, 0 if a timeou has occured -\*-------------------------------------------------------------------------*/ -static int read_or_timeout(p_sock sock, int elapsed) -{ - fd_set set; /* file descriptor set */ - struct timeval to; /* timeout structure */ - int ms = get_timeout(sock, elapsed); - int err; - /* got timeout */ - if (ms == 0) - return 0; - FD_ZERO(&set); - FD_SET(sock->sock, &set); - /* we have a limit on the time we can wait */ - if (ms > 0) { - to.tv_sec = ms / 1000; - to.tv_usec = (ms % 1000) * 1000; - err = select(sock->sock+1, &set, NULL, NULL, &to); - set_nonblocking(sock); - /* we can wait forever */ - } else { - err = select(sock->sock+1, &set, NULL, NULL, NULL); - set_blocking(sock); - } - return (err > 0); + else return min(sock->tm_block, + max(sock->tm_return - tm_gettime() + sock->tm_start, 0)); } /*-------------------------------------------------------------------------*\ @@ -841,33 +801,110 @@ static int read_or_timeout(p_sock sock, int elapsed) * an IO write operation. * Input * sock: socket structure being used in operation -* elapsed: time elapsed since operation started +* mode: TM_RECEIVE or TM_SEND * Returns -* 1 if we can proceed, 0 if a timeou has occured +* 1 if we can proceed, 0 if a timeout has occured \*-------------------------------------------------------------------------*/ -static int write_or_timeout(p_sock sock, int elapsed) +static int tm_timedout(p_sock sock, int mode) { - fd_set set; /* file descriptor set */ - struct timeval to; /* timeout structure */ - int ms = get_timeout(sock, elapsed); - int err; - /* got timeout */ - if (ms == 0) - return 0; - FD_ZERO(&set); - FD_SET(sock->sock, &set); - /* we have a limit on the time we can wait */ - if (ms > 0) { - to.tv_sec = ms / 1000; - to.tv_usec = (ms % 1000) * 1000; - err = select(sock->sock+1, NULL, &set, NULL, &to); - set_nonblocking(sock); - /* we can wait forever */ - } else { - err = select(sock->sock+1, NULL, &set, NULL, NULL); - set_blocking(sock); + fd_set fds; + int ret, delta; + fd_set *preadfds = NULL, *pwritefds = NULL; + struct timeval tm; + struct timeval *ptm = NULL; + /* find out how much time we have left, in ms */ + int ms = tm_gettimeleft(sock); + /* fill file descriptor set */ + FD_ZERO(&fds); FD_SET(sock->sock, &fds); + /* fill timeval structure */ + tm.tv_sec = ms / 1000; + tm.tv_usec = (ms % 1000) * 1000; + /* define function parameters */ + if (ms > 0) ptm = &tm; /* ptm == NULL when we don't have timeout */ + if (mode == TM_RECEIVE) preadfds = &fds; + else pwritefds = &fds; + delta = tm_gettime(); + /* see if we can read or write or if we timedout */ + ret = select(sock->sock+1, preadfds, pwritefds, NULL, ptm); +#ifdef _DEBUG + /* store end time for this operation before calling select */ + sock->tm_end = tm_gettime(); +#endif + return ret <= 0; +} + +/*-------------------------------------------------------------------------*\ +* Marks the operation start time in sock structure +* Input +* sock: socket structure being used in operation +\*-------------------------------------------------------------------------*/ +static void tm_markstart(p_sock sock) +{ + sock->tm_start = tm_gettime(); +#ifdef _DEBUG + sock->tm_end = sock->tm_start; +#endif +} + +/*-------------------------------------------------------------------------*\ +* Determines of there is any data in the read buffer +* Input +* sock: socket structure being used in operation +* Returns +* 1 if empty, 0 if there is data +\*-------------------------------------------------------------------------*/ +static int bf_isempty(p_sock sock) +{ + return sock->bf_first >= sock->bf_last; +} + +/*-------------------------------------------------------------------------*\ +* Skip a given number of bytes in read buffer +* Input +* sock: socket structure being used in operation +* length: number of bytes to skip +\*-------------------------------------------------------------------------*/ +static void bf_skip(p_sock sock, int length) +{ + sock->bf_first += length; + if (bf_isempty(sock)) sock->bf_first = sock->bf_last = 0; +} + +/*-------------------------------------------------------------------------*\ +* Return any data avilable in buffer, or get more data from transport layer +* if there is none. +* Input +* sock: socket structure being used in operation +* Output +* length: number of bytes available in buffer +* Returns +* pointer to start of data +\*-------------------------------------------------------------------------*/ +static const unsigned char *bf_receive(p_sock sock, int *length) +{ + if (bf_isempty(sock)) { + int got = recv(sock->sock, sock->bf_buffer, LUASOCKET_BUFFERSIZE, 0); + sock->bf_first = 0; + if (got >= 0) sock->bf_last = got; + else sock->bf_last = 0; } - return (err > 0); + *length = sock->bf_last - sock->bf_first; + return sock->bf_buffer + sock->bf_first; +} + +/*-------------------------------------------------------------------------*\ +* Gets time in ms, relative to system startup. +* Returns +* time in ms. +\*-------------------------------------------------------------------------*/ +static int tm_gettime(void) +{ +#ifdef _WIN32 + return GetTickCount(); +#else + struct tms t; + return (times(&t)*1000)/CLK_TCK; +#endif } /*-------------------------------------------------------------------------*\ @@ -877,49 +914,24 @@ static int write_or_timeout(p_sock sock, int elapsed) * sock: socket structure being used in operation * data: buffer to be sent * wanted: number of bytes in buffer -* start: time the operation started, in ms * Output * err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED * Returns * Number of bytes written \*-------------------------------------------------------------------------*/ -static int send_raw(p_sock sock, const char *data, int wanted, - int start, int *err, int *end) +static int send_raw(p_sock sock, const char *data, int wanted, int *err) { int put = 0, total = 0; - *end = start; while (wanted > 0) { - if(!write_or_timeout(sock, time_since(start))) { -#ifdef _DEBUG -*end = get_time(); -#endif + if (tm_timedout(sock, TM_SEND)) { *err = NET_TIMEOUT; return total; } -#ifdef _DEBUG -/* the lua_pushlstring function can take a long time to pass a large block -** to Lua, therefore, we mark the time before passing the result. -** also, the call to write or read might take longer then the time we had -** left, so that the end of the operation is marked before the last call -** to the OS */ -*end = get_time(); -#endif put = send(sock->sock, data, wanted, 0); if (put <= 0) { -#ifdef WIN32 - /* on WinSock, a select over a socket on which there is a - ** non-blocking operation pending returns immediately, even - ** if the call would block. therefore, we have to do a busy - ** wait here. */ - if (WSAGetLastError() == WSAEWOULDBLOCK) - continue; -#endif *err = NET_CLOSED; return total; } -#ifdef _DEBUG_BLOCK -printf("luasocket: sent %d, wanted %d, %dms elapsed\n", put, wanted, time_since(start)); -#endif wanted -= put; data += put; total += put; @@ -934,180 +946,146 @@ printf("luasocket: sent %d, wanted %d, %dms elapsed\n", put, wanted, time_since( * Input * sock: socket structure being used in operation * wanted: number of bytes to be read -* start: time the operation started, in ms -* Output -* err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED * Returns -* Number of bytes read +* operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED \*-------------------------------------------------------------------------*/ -#define MIN(x,y) ((x)<(y)?(x):(y)) -static void receive_raw(lua_State *L, p_sock sock, int wanted, int start, - int *err, int *end) +static int receive_raw(lua_State *L, p_sock sock, int wanted) { int got = 0; - char *buffer = NULL; + const unsigned char *buffer = NULL; luaL_Buffer b; - *end = start; luaL_buffinit(L, &b); while (wanted > 0) { - if(!read_or_timeout(sock, time_since(start))) { -#ifdef _DEBUG -*end = get_time(); -#endif - *err = NET_TIMEOUT; + if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { luaL_pushresult(&b); - return; + return NET_TIMEOUT; } -#ifdef _DEBUG -*end = get_time(); -#endif - buffer = luaL_prepbuffer(&b); - got = recv(sock->sock, buffer, MIN(wanted, LUAL_BUFFERSIZE), 0); -#ifdef _DEBUG_BLOCK -printf("luasocket: wanted %d, got %d, %dms elapsed\n", wanted, got, time_since(start)); -#endif + buffer = bf_receive(sock, &got); if (got <= 0) { - *err = NET_CLOSED; luaL_pushresult(&b); - return; + return NET_CLOSED; } + got = min(got, wanted); + luaL_addlstring(&b, buffer, got); + bf_skip(sock, got); wanted -= got; - luaL_addsize(&b, got); } - *err = NET_DONE; luaL_pushresult(&b); + return NET_DONE; } /*-------------------------------------------------------------------------*\ * Reads everything until the connection is closed * Input * sock: socket structure being used in operation -* start: time the operation started, in ms -* Output -* err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED * Result -* a string is pushed into the Lua stack with the line just read +* operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED \*-------------------------------------------------------------------------*/ -static void receive_all(lua_State *L, p_sock sock, int start, - int *err, int *end) +static int receive_all(lua_State *L, p_sock sock) { - int got; - char *buffer; + int got = 0; + const unsigned char *buffer = NULL; luaL_Buffer b; - *end = start; luaL_buffinit(L, &b); for ( ;; ) { - buffer = luaL_prepbuffer(&b); - if (read_or_timeout(sock, time_since(start))) { -#ifdef _DEBUG -*end = get_time(); -#endif - got = recv(sock->sock, buffer, LUAL_BUFFERSIZE, 0); + if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { + buffer = bf_receive(sock, &got); if (got <= 0) { - *err = NET_DONE; - break; + luaL_pushresult(&b); + return NET_DONE; } - luaL_addsize(&b, got); + luaL_addlstring(&b, buffer, got); } else { - *err = NET_TIMEOUT; - break; + luaL_pushresult(&b); + return NET_TIMEOUT; } } - luaL_pushresult(&b); } /*-------------------------------------------------------------------------*\ * Reads a line terminated by a CR LF pair or just by a LF. The CR and LF -* are not returned by the function. All operations are non-blocking and the -* function respects the timeout values in sock. +* are not returned by the function and are discarded from the stream. All +* operations are non-blocking and the function respects the timeout +* values in sock. * Input * sock: socket structure being used in operation -* wanted: number of bytes in buffer -* start: time the operation started, in ms -* Output -* data: pointer to an internal buffer containing the data read -* err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED * Result -* a string is pushed into the Lua stack with the line just read +* operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED \*-------------------------------------------------------------------------*/ -static void receive_dosline(lua_State *L, p_sock sock, int start, - int *err, int *end) +static int receive_dosline(lua_State *L, p_sock sock) { - char c = ' '; - long got = 0; + int got = 0; + const unsigned char *buffer = NULL; luaL_Buffer b; - *end = start; luaL_buffinit(L, &b); for ( ;; ) { - if (read_or_timeout(sock, time_since(start))) { -#ifdef _DEBUG -*end = get_time(); -#endif - got = recv(sock->sock, &c, 1, 0); - if (got <= 0) { - *err = NET_CLOSED; - break; - } - if (c != '\n') { - if (c != '\r') luaL_putchar(&b, c); - } else { - *err = NET_DONE; - break; + if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { + luaL_pushresult(&b); + return NET_TIMEOUT; + } + buffer = bf_receive(sock, &got); + if (got > 0) { + int len = 0, end = 1; + while (len < got) { + if (buffer[len] == '\n') { /* found eol */ + if (len > 0 && buffer[len-1] == '\r') { + end++; len--; + } + luaL_addlstring(&b, buffer, len); + bf_skip(sock, len + end); /* skip '\r\n' in stream */ + luaL_pushresult(&b); + return NET_DONE; + } + len++; } + luaL_addlstring(&b, buffer, got); + bf_skip(sock, got); } else { - *err = NET_TIMEOUT; - break; + luaL_pushresult(&b); + return NET_CLOSED; } } - luaL_pushresult(&b); } /*-------------------------------------------------------------------------*\ * Reads a line terminated by a LF character, which is not returned by -* the function. All operations are non-blocking and the function respects -* the timeout values in sock. +* the function, and is skipped in the stream. All operations are +* non-blocking and the function respects the timeout values in sock. * Input * sock: socket structure being used in operation -* wanted: number of bytes in buffer -* start: time the operation started, in ms -* Output -* data: pointer to an internal buffer containing the data read -* err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED * Returns -* Number of bytes read +* operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED \*-------------------------------------------------------------------------*/ -static void receive_unixline(lua_State *L, p_sock sock, int start, - int *err, int *end) +static int receive_unixline(lua_State *L, p_sock sock) { - char c = ' '; - long got = 0; - long size = 0; + int got = 0; + const unsigned char *buffer = NULL; luaL_Buffer b; - *end = start; luaL_buffinit(L, &b); for ( ;; ) { - if (read_or_timeout(sock, time_since(start))) { -#ifdef _DEBUG -*end = get_time(); -#endif - got = recv(sock->sock, &c, 1, 0); - if (got <= 0) { - *err = NET_CLOSED; - break; - } - if (c != '\n') { - luaL_putchar(&b, c); - size++; - } else { - *err = NET_DONE; - break; + if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { + luaL_pushresult(&b); + return NET_TIMEOUT; + } + buffer = bf_receive(sock, &got); + if (got > 0) { + int len = 0; + while (len < got) { + if (buffer[len] == '\n') { /* found eol */ + luaL_addlstring(&b, buffer, len); + bf_skip(sock, len + 1); /* skip '\n' in stream */ + luaL_pushresult(&b); + return NET_DONE; + } + len++; } + luaL_addlstring(&b, buffer, got); + bf_skip(sock, got); } else { - *err = NET_TIMEOUT; - break; + luaL_pushresult(&b); + return NET_CLOSED; } } - luaL_pushresult(&b); } /*-------------------------------------------------------------------------*\ @@ -1204,15 +1182,6 @@ static int wsock_open(void) return 1; } -/*-------------------------------------------------------------------------*\ -* Gets time in ms, relative to system startup. -* Returns -* time in ms. -\*-------------------------------------------------------------------------*/ -static int get_time(void) -{ - return GetTickCount(); -} /*-------------------------------------------------------------------------*\ * Put socket into blocking mode. @@ -1303,28 +1272,14 @@ static char *connect_strerror(void) /*=========================================================================*\ * BSD specific functions. \*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Gets time in ms, relative to system startup. -* Returns -* time in ms. -\*-------------------------------------------------------------------------*/ -static int get_time(void) -{ - struct tms t; - return (times(&t)*1000)/CLK_TCK; -} - /*-------------------------------------------------------------------------*\ * Put socket into blocking mode. \*-------------------------------------------------------------------------*/ static void set_blocking(p_sock sock) { - if (!sock->blocking) { - int flags = fcntl(sock->sock, F_GETFL, 0); - flags &= (~(O_NONBLOCK)); - fcntl(sock->sock, F_SETFL, flags); - sock->blocking = 1; - } + int flags = fcntl(sock->sock, F_GETFL, 0); + flags &= (~(O_NONBLOCK)); + fcntl(sock->sock, F_SETFL, flags); } /*-------------------------------------------------------------------------*\ @@ -1332,12 +1287,9 @@ static void set_blocking(p_sock sock) \*-------------------------------------------------------------------------*/ static void set_nonblocking(p_sock sock) { - if (sock->blocking) { - int flags = fcntl(sock->sock, F_GETFL, 0); - flags |= O_NONBLOCK; - fcntl(sock->sock, F_SETFL, flags); - sock->blocking = 0; - } + int flags = fcntl(sock->sock, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(sock->sock, F_SETFL, flags); } /*-------------------------------------------------------------------------*\ From b40d2ba005034c36c6b7f4439e6d6df1ef08d5e4 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Sat, 13 Jan 2001 07:11:44 +0000 Subject: [PATCH 006/483] Included LUASOCKET_BUFFERSIZE and LUASOCKET_VERSION defines. --- src/luasocket.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/luasocket.h b/src/luasocket.h index d4037cd..793a315 100644 --- a/src/luasocket.h +++ b/src/luasocket.h @@ -7,12 +7,9 @@ #ifndef _LUASOCKET_H_ #define _LUASOCKET_H_ -/*=========================================================================*\ -* Exported function declarations -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Initializes toolkit -\*-------------------------------------------------------------------------*/ +#define LUASOCKET_VERSION "LuaSocket 1.2" +#define LUASOCKET_BUFFERSIZE 8192 + void lua_socketlibopen(lua_State *L); #endif /* _LUASOCKET_H_ */ From 84baa83864b4832844151aef694465ddf96c28f3 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 15 Jan 2001 04:16:35 +0000 Subject: [PATCH 007/483] The actuall bind to the Lua language has been rewritten with generalized use of closure values. Sockets are now real tables, where each method receives a p_sock structure as a closure. Global version of methods are now optional, and call the table versions. Included the toip function that converts from host name to ip address. new implementation of '*a' was broken as has been fixed. The windows code has been tested and is working. --- src/luasocket.c | 899 ++++++++++++++++++++++++------------------------ 1 file changed, 442 insertions(+), 457 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index 4f03ad2..e6f423c 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -104,17 +104,10 @@ #define TM_SEND 2 /*-------------------------------------------------------------------------*\ -* As far as a Lua script is concerned, there are two kind of objects -* representing a socket. A client socket is an object created by the -* function connect, and implementing the methods send, receive, timeout -* and close. A server socket is an object created by the function bind, -* and implementing the methods listen, accept and close. Lua tag values -* for these objects are created in the lua_socketlibopen function, and -* passed as closure values (last argumnents to every library function) -# because we can't have any global variables. +* Each socket is represented by a table with the supported methods and +* the p_sock structure as fields. \*-------------------------------------------------------------------------*/ -#define CLIENT_TAG -2 -#define SERVER_TAG -1 +#define P_SOCK "(p_sock)sock" /*-------------------------------------------------------------------------*\ * Both socket types are stored in the same structure to simplify @@ -126,19 +119,27 @@ typedef struct t_sock { SOCKET sock; /* start time of the current operation */ int tm_start; -#ifdef _DEBUG - /* end time of current operation, for debug purposes */ - int tm_end; -#endif /* return and blocking timeout values (-1 if no limit) */ int tm_return, tm_block; /* buffered I/O storage */ unsigned char bf_buffer[LUASOCKET_BUFFERSIZE]; /* first and last red bytes not yet passed to application */ int bf_first, bf_last; +#ifdef _DEBUG + /* end time of current operation, for debug purposes */ + int tm_end; +#endif } t_sock; typedef t_sock *p_sock; +/*-------------------------------------------------------------------------*\ +* Tags passed as closure values to global LuaSocket API functions +\*-------------------------------------------------------------------------*/ +typedef struct t_tags { + int client, server, table; +} t_tags; +typedef t_tags *p_tags; + /*-------------------------------------------------------------------------*\ * Macros and internal declarations \*-------------------------------------------------------------------------*/ @@ -154,14 +155,24 @@ typedef t_sock *p_sock; * Internal function prototypes \*=========================================================================*/ /* luasocket API functions */ -static int net_connect(lua_State *L); -static int net_bind(lua_State *L); -static int net_listen(lua_State *L); -static int net_accept(lua_State *L); -static int net_send(lua_State *L); -static int net_receive(lua_State *L); -static int net_timeout(lua_State *L); -static int net_close(lua_State *L); +static int global_connect(lua_State *L); +static int global_bind(lua_State *L); +static int table_listen(lua_State *L); +static int table_accept(lua_State *L); +static int table_send(lua_State *L); +static int table_receive(lua_State *L); +static int table_timeout(lua_State *L); +static int table_close(lua_State *L); +#ifndef LUASOCKET_NOGLOBALS +static int global_listen(lua_State *L); +static int global_accept(lua_State *L); +static int global_send(lua_State *L); +static int global_receive(lua_State *L); +static int global_timeout(lua_State *L); +static int global_close(lua_State *L); +static p_sock get_selfclientsock(lua_State *L, p_tags tags); +static p_sock get_selfserversock(lua_State *L, p_tags tags); +#endif /* buffered I/O management */ static const unsigned char *bf_receive(p_sock sock, int *length); @@ -181,18 +192,14 @@ static int receive_dosline(lua_State *L, p_sock sock); static int receive_unixline(lua_State *L, p_sock sock); static int receive_all(lua_State *L, p_sock sock); -/* fallbacks */ -static int server_gettable(lua_State *L); -static int client_gettable(lua_State *L); -static int sock_gc(lua_State *L); - -/* argument checking routines */ -static p_sock check_client(lua_State *L, int numArg, int client_tag); -static p_sock check_server(lua_State *L, int numArg, int server_tag); -static p_sock check_sock(lua_State *L, int numArg, int server_tag, - int client_tag); -static void pop_tags(lua_State *L, int *client_tag, int *server_tag); -static void push_tags(lua_State *L, int client_tag, int server_tag); +/* parameter manipulation functions */ +static p_tags pop_tags(lua_State *L); +static p_sock pop_sock(lua_State *L); +static p_sock get_selfsock(lua_State *L, p_tags tags); +static int gc_sock(lua_State *L); +static p_sock push_servertable(lua_State *L, p_tags tags); +static p_sock push_clienttable(lua_State *L, p_tags tags); +static void push_error(lua_State *L, int err); /* error code translations functions */ static char *host_strerror(void); @@ -200,20 +207,17 @@ static char *bind_strerror(void); static char *sock_strerror(void); static char *connect_strerror(void); -static void push_error(lua_State *L, int err); -static void push_client(lua_State *L, p_sock sock, int client_tag); -static void push_server(lua_State *L, p_sock sock, int server_tag); - -/* plataform specific functions */ +/* auxiliary functions */ static void set_blocking(p_sock sock); static void set_nonblocking(p_sock sock); - -/* auxiliary functions */ -static p_sock create_sock(void); -static p_sock create_tcpsock(void); +static int create_tcpsocket(p_sock sock); static int fill_sockaddr(struct sockaddr_in *server, const char *hostname, unsigned short port); +#ifdef WIN32 +static int winsock_open(void); +#endif + /*=========================================================================*\ * Test support functions \*=========================================================================*/ @@ -221,8 +225,8 @@ static int fill_sockaddr(struct sockaddr_in *server, const char *hostname, /*-------------------------------------------------------------------------*\ * Returns the time the system has been up, in secconds. \*-------------------------------------------------------------------------*/ -static int net_time(lua_State *L); -static int net_time(lua_State *L) +static int global_time(lua_State *L); +static int global_time(lua_State *L) { lua_pushnumber(L, tm_gettime()/1000.0); return 1; @@ -231,8 +235,8 @@ static int net_time(lua_State *L) /*-------------------------------------------------------------------------*\ * Causes a Lua script to sleep for the specified number of secconds \*-------------------------------------------------------------------------*/ -static int net_sleep(lua_State *L); -static int net_sleep(lua_State *L) +static int global_sleep(lua_State *L); +static int global_sleep(lua_State *L) { int sec = (int) luaL_check_number(L, 1); #ifdef WIN32 @@ -253,23 +257,26 @@ static int net_sleep(lua_State *L) * Creates a client socket and returns it to the Lua script. The timeout * values are initialized as -1 so that the socket will block at any * IO operation. -* Input +* Lua Input * host: host name or ip address to connect to * port: port number on host -* Returns +* Lua Returns * On success: client socket * On error: nil, followed by an error message \*-------------------------------------------------------------------------*/ -static int net_connect(lua_State *L) +static int global_connect(lua_State *L) { + p_tags tags = pop_tags(L); const char *hostname = luaL_check_string(L, 1); unsigned short port = (unsigned short) luaL_check_number(L, 2); - int client_tag, server_tag; struct sockaddr_in server; - p_sock sock; - pop_tags(L, &client_tag, &server_tag); - sock = create_tcpsock(); + p_sock sock = push_clienttable(L, tags); if (!sock) { + lua_pushnil(L); + lua_pushstring(L, "out of memory"); + return 2; + } + if (!create_tcpsocket(sock)) { lua_pushnil(L); lua_pushstring(L, sock_strerror()); return 2; @@ -277,41 +284,60 @@ static int net_connect(lua_State *L) /* fills the sockaddr structure with the information needed to ** connect our socket with the remote host */ if (!fill_sockaddr(&server, hostname, port)) { - free(sock); lua_pushnil(L); lua_pushstring(L, host_strerror()); return 2; } - if (connect(sock->sock,(struct sockaddr *)&server,sizeof(server)) < 0) { + if (connect(sock->sock, (struct sockaddr *)&server, sizeof(server)) < 0) { /* no connection? we close the socket to free the descriptor */ closesocket(sock->sock); lua_pushnil(L); lua_pushstring(L, connect_strerror()); return 2; } + /* all operations on client sockets are non-blocking */ set_nonblocking(sock); - push_client(L, sock, client_tag); lua_pushnil(L); return 2; } +/*-------------------------------------------------------------------------*\ +* Converts from ip number to host name +* Lua Input +* ip: ip number +* Lua Returns +* On success: domain name +* On error: nil, followed by an error message +\*-------------------------------------------------------------------------*/ +static int global_toip(lua_State *L) +{ + struct hostent *host; + struct in_addr addr; + pop_tags(L); + host = gethostbyname(luaL_check_string(L, 1)); + if (!host) { + lua_pushnil(L); + lua_pushstring(L, host_strerror()); + return 2; + } + memcpy(&addr, host->h_addr, (unsigned) host->h_length); + lua_pushstring(L, inet_ntoa(addr)); + return 1; +} + /*-------------------------------------------------------------------------*\ * Specifies the number of connections that can be queued on a server * socket. -* Input +* Lua Input * sock: server socket created by the bind function -* Returns +* Lua Returns * On success: nil * On error: an error message \*-------------------------------------------------------------------------*/ -static int net_listen(lua_State *L) +static int table_listen(lua_State *L) { - p_sock sock; - int client_tag, server_tag; - unsigned int backlog; - pop_tags(L, &client_tag, &server_tag); - sock = check_server(L, 1, server_tag); - backlog = (unsigned int) luaL_check_number(L, 2); + p_sock sock = pop_sock(L); + unsigned int backlog = (unsigned int) luaL_check_number(L, 2); if (listen(sock->sock, backlog) < 0) { lua_pushstring(L, "listen error"); return 1; @@ -324,39 +350,25 @@ static int net_listen(lua_State *L) /*-------------------------------------------------------------------------*\ * Returns a client socket attempting to connect to a server socket. * The function blocks until a client shows up. -* Input +* Lua Input * sock: server socket created by the bind function -* Returns +* Lua Returns * On success: client socket attempting connection * On error: nil followed by an error message \*-------------------------------------------------------------------------*/ -static int net_accept(lua_State *L) +static int table_accept(lua_State *L) { struct sockaddr_in client_addr; - int client_tag, server_tag; - p_sock server; - int client_sock = -1; size_t client_len = sizeof(client_addr); - p_sock client; - pop_tags(L, &client_tag, &server_tag); - server = check_server(L, 1, server_tag); - /* waits for a connection */ - client_sock = accept(server->sock, (struct sockaddr *) &client_addr, + p_sock server = pop_sock(L); + p_tags tags = pop_tags(L); + p_sock client = push_clienttable(L, tags); + SOCKET accepted = accept(server->sock, (struct sockaddr *) &client_addr, &client_len); - /* we create and return a client socket object, passing the received - ** socket to Lua, as a client socket */ - client = create_sock(); - if (!client) { - lua_pushnil(L); - lua_pushstring(L, "out of memory"); - return 2; - } else { - client->sock = client_sock; - set_nonblocking(client); - push_client(L, client, client_tag); - lua_pushnil(L); - return 2; - } + client->sock = accepted; + set_nonblocking(client); + lua_pushnil(L); + return 2; } /*-------------------------------------------------------------------------*\ @@ -370,16 +382,15 @@ static int net_accept(lua_State *L) * On success: server socket bound to address, the ip address and port bound * On error: nil, followed by an error message \*-------------------------------------------------------------------------*/ -static int net_bind(lua_State *L) +static int global_bind(lua_State *L) { + p_tags tags = pop_tags(L); const char *hostname = luaL_check_string(L, 1); unsigned short port = (unsigned short) luaL_check_number(L, 2); unsigned int backlog = (unsigned int) luaL_opt_number(L, 3, 1.0); struct sockaddr_in server; size_t server_size = sizeof(server); - int client_tag, server_tag; - p_sock sock = create_tcpsock(); - pop_tags(L, &client_tag, &server_tag); + p_sock sock = push_servertable(L, tags); if (!sock) { lua_pushnil(L); lua_pushstring(L, sock_strerror()); @@ -406,8 +417,6 @@ static int net_bind(lua_State *L) } /* pass the created socket to Lua, as a server socket */ else { - /* pass server */ - push_server(L, sock, server_tag); /* get used address and port */ getsockname(sock->sock, (struct sockaddr *)&server, &server_size); /* pass ip number */ @@ -421,26 +430,19 @@ static int net_bind(lua_State *L) /*-------------------------------------------------------------------------*\ * Sets timeout values for IO operations on a client socket -* Input +* Lua Input * sock: client socket created by the connect function * time: time out value in seconds * mode: optional timeout mode. "block" specifies the upper bound on * the time any IO operation on sock can cause the program to block. * "return" specifies the upper bound on the time elapsed before the * function returns control to the script. "block" is the default. -* Returns -* no return value \*-------------------------------------------------------------------------*/ -static int net_timeout(lua_State *L) +static int table_timeout(lua_State *L) { - int client_tag, server_tag; - p_sock sock; - int ms; - const char *mode; - pop_tags(L, &client_tag, &server_tag); - sock = check_client(L, 1, client_tag); - ms = (int) (luaL_check_number(L, 2)*1000.0); - mode = luaL_opt_string(L, 3, "b"); + p_sock sock = pop_sock(L); + int ms = (int) (luaL_check_number(L, 2)*1000.0); + const char *mode = luaL_opt_string(L, 3, "b"); switch (*mode) { case 'b': sock->tm_block = ms; @@ -457,34 +459,30 @@ static int net_timeout(lua_State *L) /*-------------------------------------------------------------------------*\ * Send data through a socket -* Input: sock, a_1 [, a_2, a_3 ... a_n] +* Lua Input: sock, a_1 [, a_2, a_3 ... a_n] * sock: client socket created by the connect function * a_i: strings to be sent. The strings will be sent on the order they * appear as parameters -* Returns +* Lua Returns * On success: nil, followed by the total number of bytes sent * On error: NET_TIMEOUT if the connection timedout, or NET_CLOSED if * the connection has been closed, followed by the total number of * bytes sent \*-------------------------------------------------------------------------*/ -static int net_send(lua_State *L) +static int table_send(lua_State *L) { - p_sock sock; - const char *data; - int wanted; - long total = 0; int arg; + p_sock sock = pop_sock(L); + int top = lua_gettop(L); + int total = 0; int err = NET_DONE; - int top; - int client_tag, server_tag; - pop_tags(L, &client_tag, &server_tag); - top = lua_gettop(L); - sock = check_client(L, 1, client_tag); tm_markstart(sock); - for (arg = 2; arg <= top; arg++) { - data = luaL_opt_lstr(L, arg, NULL, &wanted); + for (arg = 2; arg <= top; arg++) { /* skip self table */ + int sent, wanted; + const char *data = luaL_opt_lstr(L, arg, NULL, &wanted); if (!data || err != NET_DONE) break; - total += send_raw(sock, data, wanted, &err); + err = send_raw(sock, data, wanted, &sent); + total += sent; } push_error(L, err); lua_pushnumber(L, (double) total); @@ -497,7 +495,7 @@ static int net_send(lua_State *L) /*-------------------------------------------------------------------------*\ * Receive data from a socket -* Input: sock [pat_1, pat_2 ... pat_n] +* Lua Input: sock [pat_1, pat_2 ... pat_n] * sock: client socket created by the connect function * pat_i: may be one of the following * "*l": reads a text line, defined as a string of caracters terminates @@ -506,24 +504,21 @@ static int net_send(lua_State *L) * "*lu": reads a text line, terminanted by a CR character only. (Unix mode) * "*a": reads until connection closed * number: reads 'number' characters from the socket -* Returns +* Lua Returns * On success: one string for each pattern * On error: all strings for which there was no error, followed by one * nil value for the remaining strings, followed by an error code \*-------------------------------------------------------------------------*/ -static int net_receive(lua_State *L) +static int table_receive(lua_State *L) { static const char *const modenames[] = {"*l", "*lu", "*a", NULL}; - int err = NET_DONE, arg = 2; const char *mode; - int client_tag, server_tag; - int top; - p_sock sock; - pop_tags(L, &client_tag, &server_tag); - sock = check_client(L, 1, client_tag); + int err = NET_DONE; + int arg; + p_sock sock = pop_sock(L); + int top = lua_gettop(L); tm_markstart(sock); - /* push default pattern */ - top = lua_gettop(L); + /* push default pattern if need be */ if (top < 2) { lua_pushstring(L, "*l"); top++; @@ -536,7 +531,7 @@ static int net_receive(lua_State *L) continue; } if (lua_isnumber(L, arg)) { - long size = (long) lua_tonumber(L, arg); + int size = (int) lua_tonumber(L, arg); err = receive_raw(L, sock, size); } else { mode = luaL_opt_string(L, arg, NULL); @@ -572,104 +567,30 @@ static int net_receive(lua_State *L) /*-------------------------------------------------------------------------*\ * Closes a socket. -* Input +* Lua Input * sock: socket to be closed \*-------------------------------------------------------------------------*/ -static int net_close(lua_State *L) +static int table_close(lua_State *L) { - int client_tag, server_tag; - p_sock sock; - pop_tags(L, &client_tag, &server_tag); - sock = check_sock(L, 1, client_tag, server_tag); + /* close socket and set value to -1 so that pop_socket can later + ** detect the use of a closed socket */ + p_sock sock = pop_sock(L); closesocket(sock->sock); - /* set value to -1 so that we can later detect the use of a - ** closed socket */ sock->sock = -1; return 0; } -/*-------------------------------------------------------------------------*\ -* Gettable fallback for the client socket. This function provides the -* alternative interface client:receive, client:send etc for the client -* socket methods. -\*-------------------------------------------------------------------------*/ -static int client_gettable(lua_State *L) -{ - static const char *const net_api[] = - {"receive","send","timeout","close", "connect", NULL}; - const char *idx = luaL_check_string(L, 2); - int server_tag, client_tag; - pop_tags(L, &client_tag, &server_tag); - switch (luaL_findstring(idx, net_api)) { - case 0: - push_tags(L, client_tag, server_tag); - lua_pushcclosure(L, net_receive, 2); - break; - case 1: - push_tags(L, client_tag, server_tag); - lua_pushcclosure(L, net_send, 2); - break; - case 2: - push_tags(L, client_tag, server_tag); - lua_pushcclosure(L, net_timeout, 2); - break; - case 3: - push_tags(L, client_tag, server_tag); - lua_pushcclosure(L, net_close, 2); - break; - default: - lua_pushnil(L); - break; - } - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Gettable fallback for the server socket. This function provides the -* alternative interface server:listen, server:accept etc for the server -* socket methods. -\*-------------------------------------------------------------------------*/ -static int server_gettable(lua_State *L) -{ - static const char *const net_api[] = {"listen","accept","close", NULL}; - const char *idx = luaL_check_string(L, 2); - int server_tag, client_tag; - pop_tags(L, &client_tag, &server_tag); - switch (luaL_findstring(idx, net_api)) { - case 0: - push_tags(L, client_tag, server_tag); - lua_pushcclosure(L, net_listen, 2); - break; - case 1: - push_tags(L, client_tag, server_tag); - lua_pushcclosure(L, net_accept, 2); - break; - case 2: - push_tags(L, client_tag, server_tag); - lua_pushcclosure(L, net_close, 2); - break; - default: - lua_pushnil(L); - break; - } - return 1; -} - /*-------------------------------------------------------------------------*\ * Garbage collection fallback for the socket objects. This function -* makes sure that all collected sockets are closed and that the memory -* used by the C structure t_sock is properly released. +* makes sure that all collected sockets are closed. \*-------------------------------------------------------------------------*/ -static int sock_gc(lua_State *L) +static int gc_sock(lua_State *L) { - int server_tag, client_tag; - p_sock sock; - pop_tags(L, &client_tag, &server_tag); - sock = check_sock(L, 1, client_tag, server_tag); - if (sock->sock >= 0) - closesocket(sock->sock); - free(sock); - return 1; + p_tags tags = pop_tags(L); + p_sock sock = get_selfsock(L, tags); + /* sock might have been closed */ + if (sock->sock >= 0) closesocket(sock->sock); + return 0; } /*=========================================================================*\ @@ -691,35 +612,17 @@ static void handle_sigpipe(void) } #endif -/*-------------------------------------------------------------------------*\ -* Creates a t_sock structure with default values. -\*-------------------------------------------------------------------------*/ -static p_sock create_sock(void) -{ - p_sock sock = (p_sock) malloc(sizeof(t_sock)); - if (!sock) return NULL; - sock->sock = -1; - sock->tm_block = -1; - sock->tm_return = -1; - sock->bf_first = sock->bf_last = 0; - return sock; -} - /*-------------------------------------------------------------------------*\ * Creates a TCP/IP socket. +* Input +* sock: structure to receive new socket * Returns -* A pointer to a t_sock structure or NULL in case of error +* 1 if successfull, 0 in case or error \*-------------------------------------------------------------------------*/ -static p_sock create_tcpsock(void) +static int create_tcpsocket(p_sock sock) { - p_sock sock = create_sock(); - if (!sock) - return NULL; sock->sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock->sock < 0) { - free(sock); - sock = NULL; - } + if (sock->sock < 0) return 0; #ifdef _DEBUG /* this allow us to re-bind onto an address even if there is still ** a TIME_WAIT condition. debugging is much more confortable, because @@ -733,7 +636,7 @@ static p_sock create_tcpsock(void) sizeof(val)); } #endif - return sock; + return 1; } /*-------------------------------------------------------------------------*\ @@ -756,10 +659,8 @@ static int fill_sockaddr(struct sockaddr_in *address, const char *hostname, /* BSD says we could have used gethostbyname even if the hostname is ** in ip address form, but WinSock2 says we can't. Therefore we ** choose a method that works on both plataforms */ - if (addr == INADDR_NONE) - host = gethostbyname(hostname); - else - host = gethostbyaddr((char * ) &addr, sizeof(unsigned long), + if (addr == INADDR_NONE) host = gethostbyname(hostname); + else host = gethostbyaddr((char * ) &addr, sizeof(unsigned long), AF_INET); if (!host) return 0; @@ -772,6 +673,87 @@ static int fill_sockaddr(struct sockaddr_in *address, const char *hostname, return 1; } +/*-------------------------------------------------------------------------*\ +* Creates a t_sock structure with default values for a client sock. +* Pushes the Lua table with sock fields and appropriate methods +* Input +* tags: tags structure +* Returns +* pointer to allocated t_sock structure, NULL in case of error +\*-------------------------------------------------------------------------*/ +static p_sock push_clienttable(lua_State *L, p_tags tags) +{ + static struct luaL_reg funcs[] = { + {"send", table_send}, + {"receive", table_receive}, + {"close", table_close}, + {"timeout", table_timeout}, + }; + int i; + p_sock sock; + lua_newtable(L); lua_settag(L, tags->table); + lua_pushstring(L, P_SOCK); + sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); + if (!sock) lua_error(L, "out of memory"); + lua_settag(L, tags->client); + lua_settable(L, -3); + sock->sock = -1; + sock->tm_block = -1; + sock->tm_return = -1; + sock->bf_first = sock->bf_last = 0; + for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { + lua_pushstring(L, funcs[i].name); + lua_pushusertag(L, sock, tags->client); + lua_pushcclosure(L, funcs[i].func, 1); + lua_settable(L, -3); + } + return sock; +} + +/*-------------------------------------------------------------------------*\ +* Creates a t_sock structure with default values for a server sock. +* Pushes the Lua table with sock fields and appropriate methods +* Input +* tags: tags structure +* Returns +* pointer to allocated t_sock structure, NULL in case of error +\*-------------------------------------------------------------------------*/ +static p_sock push_servertable(lua_State *L, p_tags tags) +{ + static struct luaL_reg funcs[] = { + {"listen", table_listen}, + {"close", table_close}, + }; + int i; + p_sock sock; + lua_newtable(L); lua_settag(L, tags->table); + lua_pushstring(L, P_SOCK); + sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); + if (!sock) lua_error(L, "out of memory"); + lua_settag(L, tags->server); + lua_settable(L, -3); + if (!create_tcpsocket(sock)) return NULL; + sock->tm_block = -1; + sock->tm_return = -1; + sock->bf_first = sock->bf_last = 0; + for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { + lua_pushstring(L, funcs[i].name); + lua_pushusertag(L, sock, tags->client); + lua_pushcclosure(L, funcs[i].func, 1); + lua_settable(L, -3); + } + /* the accept method is different, it needs the tags closure too */ + lua_pushstring(L, "accept"); + lua_pushuserdata(L, tags); + lua_pushusertag(L, sock, tags->client); + lua_pushcclosure(L, table_accept, 2); + lua_settable(L, -3); + return sock; +} + +/*=========================================================================*\ +* Timeout management functions +\*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Determines how much time we have left for the current io operation * an IO write operation. @@ -827,7 +809,7 @@ static int tm_timedout(p_sock sock, int mode) /* see if we can read or write or if we timedout */ ret = select(sock->sock+1, preadfds, pwritefds, NULL, ptm); #ifdef _DEBUG - /* store end time for this operation before calling select */ + /* store end time for this operation next call to OS */ sock->tm_end = tm_gettime(); #endif return ret <= 0; @@ -846,6 +828,24 @@ static void tm_markstart(p_sock sock) #endif } +/*-------------------------------------------------------------------------*\ +* Gets time in ms, relative to system startup. +* Returns +* time in ms. +\*-------------------------------------------------------------------------*/ +static int tm_gettime(void) +{ +#ifdef _WIN32 + return GetTickCount(); +#else + struct tms t; + return (times(&t)*1000)/CLK_TCK; +#endif +} + +/*=========================================================================*\ +* Buffered I/O management functions +\*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Determines of there is any data in the read buffer * Input @@ -892,21 +892,9 @@ static const unsigned char *bf_receive(p_sock sock, int *length) return sock->bf_buffer + sock->bf_first; } -/*-------------------------------------------------------------------------*\ -* Gets time in ms, relative to system startup. -* Returns -* time in ms. -\*-------------------------------------------------------------------------*/ -static int tm_gettime(void) -{ -#ifdef _WIN32 - return GetTickCount(); -#else - struct tms t; - return (times(&t)*1000)/CLK_TCK; -#endif -} - +/*=========================================================================*\ +* These are the function that are called for each I/O pattern +\*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Sends a raw block of data through a socket. The operations are all * non-blocking and the function respects the timeout values in sock. @@ -915,29 +903,32 @@ static int tm_gettime(void) * data: buffer to be sent * wanted: number of bytes in buffer * Output -* err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED +* total: Number of bytes written * Returns -* Number of bytes written +* operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED \*-------------------------------------------------------------------------*/ -static int send_raw(p_sock sock, const char *data, int wanted, int *err) +static int send_raw(p_sock sock, const char *data, int wanted, int *total) { - int put = 0, total = 0; + int put = 0; + *total = 0; while (wanted > 0) { - if (tm_timedout(sock, TM_SEND)) { - *err = NET_TIMEOUT; - return total; - } + if (tm_timedout(sock, TM_SEND)) return NET_TIMEOUT; put = send(sock->sock, data, wanted, 0); if (put <= 0) { - *err = NET_CLOSED; - return total; +#ifdef WIN32 + /* a bug in WinSock forces us to do a busy wait until we manage + ** to write, because select returns immediately even though it + ** should have blocked us */ + if (WSAGetLastError() == WSAEWOULDBLOCK) + continue; +#endif + return NET_CLOSED; } wanted -= put; data += put; - total += put; + *total += put; } - *err = NET_DONE; - return total; + return NET_DONE; } /*-------------------------------------------------------------------------*\ @@ -989,16 +980,16 @@ static int receive_all(lua_State *L, p_sock sock) luaL_buffinit(L, &b); for ( ;; ) { if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { - buffer = bf_receive(sock, &got); - if (got <= 0) { - luaL_pushresult(&b); - return NET_DONE; - } - luaL_addlstring(&b, buffer, got); - } else { luaL_pushresult(&b); return NET_TIMEOUT; + } + buffer = bf_receive(sock, &got); + if (got <= 0) { + luaL_pushresult(&b); + return NET_DONE; } + luaL_addlstring(&b, buffer, got); + bf_skip(sock, got); } } @@ -1088,18 +1079,125 @@ static int receive_unixline(lua_State *L, p_sock sock) } } +/*=========================================================================*\ +* Module exported functions +\*=========================================================================*/ /*-------------------------------------------------------------------------*\ -* Pops tags from closures -* Input -* L: lua environment +* Initializes the library interface with Lua and the socket library. +* Defines the symbols exported to Lua. \*-------------------------------------------------------------------------*/ -static void pop_tags(lua_State *L, int *client_tag, int *server_tag) +void lua_socketlibopen(lua_State *L) { - *client_tag = (int) lua_tonumber(L, CLIENT_TAG); - *server_tag = (int) lua_tonumber(L, SERVER_TAG); - lua_pop(L, 2); + static struct luaL_reg funcs[] = { + {"connect", global_connect}, + {"bind", global_bind}, + {"toip", global_toip}, + }; + int i; + /* declare new Lua tags for used userdata values */ + p_tags tags = (p_tags) lua_newuserdata(L, sizeof(t_tags)); + if (!tags) lua_error(L, "out of memory"); + tags->client = lua_newtag(L); + tags->server = lua_newtag(L); + tags->table = lua_newtag(L); + /* global functions exported */ + for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { + lua_pushuserdata(L, tags); + lua_pushcclosure(L, funcs[i].func, 1); + lua_setglobal(L, funcs[i].name); + } + /* socket garbage collection */ + lua_pushuserdata(L, tags); + lua_pushcclosure(L, gc_sock, 1); + lua_settagmethod(L, tags->table, "gc"); + +#ifndef LUASOCKET_NOGLOBALS + /* global version of socket table functions */ +{ static struct luaL_reg opt_funcs[] = { + {"send", global_send}, + {"receive", global_receive}, + {"accept", global_accept}, + {"close", global_close}, + {"timeout", global_timeout}, + {"listen", global_listen}, + }; + for (i = 0; i < sizeof(opt_funcs)/sizeof(opt_funcs[0]); i++) { + lua_pushuserdata(L, tags); + lua_pushcclosure(L, opt_funcs[i].func, 1); + lua_setglobal(L, opt_funcs[i].name); + } +} +#endif +#ifdef WIN32 + /* WinSock needs special initialization */ + winsock_open(); +#else + /* avoid getting killed by a SIGPIPE signal thrown by send */ + handle_sigpipe(); +#endif +#ifdef _DEBUG + /* test support functions */ + lua_pushcfunction(L, global_sleep); lua_setglobal(L, "sleep"); + lua_pushcfunction(L, global_time); lua_setglobal(L, "time"); +#endif + /* avoid stupid compiler warnings */ + (void) set_blocking; } +/*=========================================================================*\ +* Optional global version of socket table methods +\*=========================================================================*/ +#ifndef LUASOCKET_NOGLOBALS +int global_accept(lua_State *L) +{ + p_tags tags = pop_tags(L); + p_sock sock = get_selfserversock(L, tags); + lua_pushuserdata(L, tags); + lua_pushusertag(L, sock, tags->server); + return table_accept(L); +} + +int global_listen(lua_State *L) +{ + p_tags tags = pop_tags(L); + p_sock sock = get_selfserversock(L, tags); + lua_pushusertag(L, sock, tags->server); + return table_listen(L); +} + +int global_send(lua_State *L) +{ + p_tags tags = pop_tags(L); + p_sock sock = get_selfclientsock(L, tags); + lua_pushusertag(L, sock, tags->client); + return table_send(L); +} + +int global_receive(lua_State *L) +{ + p_tags tags = pop_tags(L); + p_sock sock = get_selfclientsock(L, tags); + lua_pushusertag(L, sock, tags->client); + return table_receive(L); +} + +int global_timeout(lua_State *L) +{ + p_tags tags = pop_tags(L); + p_sock sock = get_selfclientsock(L, tags); + lua_pushusertag(L, sock, tags->client); + return table_timeout(L); +} + +int global_close(lua_State *L) +{ + return gc_sock(L); +} +#endif + +/*=========================================================================*\ +* Parameter manipulation functions +\*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Passes an error code to Lua. The NET_DONE error is translated to nil. * Input @@ -1120,43 +1218,63 @@ static void push_error(lua_State *L, int err) } } -/*-------------------------------------------------------------------------*\ -* Passes socket tags to lua in correct order -* Input: -* client_tag, server_tag -\*-------------------------------------------------------------------------*/ -static void push_tags(lua_State *L, int client_tag, int server_tag) +static p_tags pop_tags(lua_State *L) { - lua_pushnumber(L, client_tag); - lua_pushnumber(L, server_tag); + p_tags tags = (p_tags) lua_touserdata(L, -1); + if (!tags) lua_error(L, "invalid closure! (probably misuse of library)"); + lua_pop(L, 1); + return tags; } -/*-------------------------------------------------------------------------*\ -* Passes a client socket to Lua. -* Must be called from a closure receiving the socket tags as its -* parameters. -* Input -* L: lua environment -* sock: pointer to socket structure to be used -\*-------------------------------------------------------------------------*/ -static void push_client(lua_State *L, p_sock sock, int client_tag) +static p_sock pop_sock(lua_State *L) { - lua_pushusertag(L, (void *) sock, client_tag); + p_sock sock = (p_sock) lua_touserdata(L, -1); + if (!sock) lua_error(L, "invalid socket object"); + if (sock->sock < 0) lua_error(L, "operation on closed socket"); + lua_pop(L, 1); + return sock; } -/*-------------------------------------------------------------------------*\ -* Passes a server socket to Lua. -* Must be called from a closure receiving the socket tags as its -* parameters. -* Input -* L: lua environment -* sock: pointer to socket structure to be used -\*-------------------------------------------------------------------------*/ -static void push_server(lua_State *L, p_sock sock, int server_tag) +static p_sock get_selfsock(lua_State *L, p_tags tags) { - lua_pushusertag(L, (void *) sock, server_tag); + p_sock sock; + if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); + lua_pushstring(L, P_SOCK); + lua_gettable(L, 1); + sock = lua_touserdata(L, -1); + if (!sock) lua_error(L, "invalid socket object"); + lua_pop(L, 1); + return sock; } +#ifndef LUASOCKET_NOGLOBALS +static p_sock get_selfclientsock(lua_State *L, p_tags tags) +{ + p_sock sock; + if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); + lua_pushstring(L, P_SOCK); + lua_gettable(L, 1); + sock = lua_touserdata(L, -1); + if (!sock || lua_tag(L, -1) != tags->client) + lua_error(L, "client socket expected"); + lua_pop(L, 1); + return sock; +} + +static p_sock get_selfserversock(lua_State *L, p_tags tags) +{ + p_sock sock; + if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); + lua_pushstring(L, P_SOCK); + lua_gettable(L, 1); + sock = lua_touserdata(L, -1); + if (!sock || lua_tag(L, -1) != tags->server) + lua_error(L, "server socket expected"); + lua_pop(L, 1); + return sock; +} +#endif + /*=========================================================================*\ * WinSock2 specific functions. \*=========================================================================*/ @@ -1166,7 +1284,7 @@ static void push_server(lua_State *L, p_sock sock, int server_tag) * Returns * 1 in case of success. 0 in case of error. \*-------------------------------------------------------------------------*/ -static int wsock_open(void) +static int winsock_open(void) { WORD wVersionRequested;WSADATA wsaData;int err; wVersionRequested = MAKEWORD( 2, 0 ); @@ -1181,7 +1299,6 @@ static int wsock_open(void) } return 1; } - /*-------------------------------------------------------------------------*\ * Put socket into blocking mode. @@ -1189,10 +1306,7 @@ static int wsock_open(void) static void set_blocking(p_sock sock) { u_long argp = 0; - if (!sock->blocking) { - ioctlsocket(sock->sock, FIONBIO, &argp); - sock->blocking = 1; - } + ioctlsocket(sock->sock, FIONBIO, &argp); } /*-------------------------------------------------------------------------*\ @@ -1201,10 +1315,7 @@ static void set_blocking(p_sock sock) static void set_nonblocking(p_sock sock) { u_long argp = 1; - if (sock->blocking) { - ioctlsocket(sock->sock, FIONBIO, &argp); - sock->blocking = 0; - } + ioctlsocket(sock->sock, FIONBIO, &argp); } /*-------------------------------------------------------------------------*\ @@ -1354,130 +1465,4 @@ static char *connect_strerror(void) default: return "unknown error"; } } - #endif - -/*=========================================================================*\ -* Module exported functions -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Initializes the library interface with Lua and the socket library. -* Defines the symbols exported to Lua. -\*-------------------------------------------------------------------------*/ -void lua_socketlibopen(lua_State *L) -{ - int client_tag, server_tag; - static struct luaL_reg funcs[] = { - {"connect", net_connect}, - {"bind", net_bind}, - {"listen", net_listen}, - {"accept", net_accept}, - {"close", net_close}, - {"send", net_send}, - {"receive", net_receive}, - {"timeout", net_timeout} - }; - int i; - -#ifdef WIN32 - wsock_open(); -#endif - /* declare new Lua tags for used userdata values */ - client_tag = lua_newtag(L); - server_tag = lua_newtag(L); - /* Lua exported functions */ - for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { - push_tags(L, client_tag, server_tag); - lua_pushcclosure(L, funcs[i].func, 2); - lua_setglobal(L, funcs[i].name); - } - /* fallbacks */ - push_tags(L, client_tag, server_tag); - lua_pushcclosure(L, client_gettable, 2); - lua_settagmethod(L, client_tag, "gettable"); - - push_tags(L, client_tag, server_tag); - lua_pushcclosure(L, server_gettable, 2); - lua_settagmethod(L, server_tag, "gettable"); - - push_tags(L, client_tag, server_tag); - lua_pushcclosure(L, sock_gc, 2); - lua_settagmethod(L, client_tag, "gc"); - - push_tags(L, client_tag, server_tag); - lua_pushcclosure(L, sock_gc, 2); - lua_settagmethod(L, server_tag, "gc"); - - /* avoid stupid compiler warnings */ - (void) set_blocking; - -#ifndef WIN32 - /* avoid getting killed by a SIGPIPE signal */ - handle_sigpipe(); -#endif - -#ifdef _DEBUG -/* test support functions */ -lua_pushcfunction(L, net_sleep); lua_setglobal(L, "sleep"); -lua_pushcfunction(L, net_time); lua_setglobal(L, "time"); -#endif -} - -/*=========================================================================*\ -* Lua2c and c2lua stack auxiliary functions -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Checks if argument is a client socket, printing an error message in -* case of error -* Input -* numArg: argument position in lua2c stack -* Returns -* pointer to client socket, or doesn't return in case of error -\*-------------------------------------------------------------------------*/ -static p_sock check_client(lua_State *L, int numArg, int client_tag) -{ - p_sock sock; - luaL_arg_check(L, lua_tag(L, numArg) == client_tag, - numArg, "client socket expected"); - sock = (p_sock) lua_touserdata(L, numArg); - if (sock->sock < 0) - lua_error(L, "operation on closed socket"); - return sock; -} - -/*-------------------------------------------------------------------------*\ -* Checks if argument is a server socket, printing an error message in -* case of error -* Input -* numArg: argument position in lua2c stack -* Returns -* pointer to server socket, or doesn't return in case of error -\*-------------------------------------------------------------------------*/ -static p_sock check_server(lua_State *L, int numArg, int server_tag) -{ - p_sock sock; - luaL_arg_check(L, lua_tag(L, numArg) == server_tag, - numArg, "server socket expected"); - sock = (p_sock) lua_touserdata(L, numArg); - if (sock->sock < 0) - lua_error(L, "operation on closed socket"); - return sock; -} - -/*-------------------------------------------------------------------------*\ -* Checks if argument is a socket, printing an error message in -* case of error -* Input -* numArg: argument position in lua2c stack -* Returns -* pointer to socket, or doesn't return in case of error -\*-------------------------------------------------------------------------*/ -static p_sock check_sock(lua_State *L, int numArg, int client_tag, - int server_tag) -{ - p_sock sock; - luaL_arg_check(L, (lua_tag(L, numArg) == client_tag) || - (lua_tag(L, numArg) == server_tag), numArg, "socket expected"); - sock = lua_touserdata(L, numArg); - return sock; -} From 6370be578b27f60bcfa1a89980602511974203e3 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 17 Jan 2001 19:51:21 +0000 Subject: [PATCH 008/483] Modified beause of name changes in examples. --- samples/README | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/samples/README b/samples/README index ef5017b..b891547 100644 --- a/samples/README +++ b/samples/README @@ -1,32 +1,32 @@ -This directory contains some sample programs using LuaSocket as well as -the automatic tests used to make sure the library is working properly. +This directory contains some sample programs using LuaSocket as +well as the automatic tests used to make sure the library is +working properly. The files provided are: - server.lua -- test server - client.lua -- test client - command.lua -- test command definitions + testsrvr.lua -- test server + testclnt.lua -- test client + testcmd.lua -- test command definitions -The automatic tests are composed by three files: client.lua, command.lua -and server.lua. To run the automatic tests on your system, make sure to -compile the library with _DEBUG defined (check makefile) and then open -two terminals. Run 'luasocket server.lua' on one of them and 'luasocket -client.lua' on the other. The programs should start talking to each -other. +To run the automatic tests on your system, make sure to compile +the library with _DEBUG defined (check makefile) and then open two +terminals. Run 'luasocket testsrvr.lua' on one of them and +'luasocket testclnt.lua' on the other. The programs should start +talking to each other. - listen.lua -- echo server - talk.lua -- echo tester + listener.lua -- echo server + talker.lua -- echo tester -listen.lua and talk.lua are about the simplest applications you can -write using LuaSocket. Run 'luasocket listen.lua' and 'luasocket -talk.lua' on different terminals. Whatever you type on talk.lua will be -printed by listen.lua. +listener.lua and talker.lua are about the simplest applications +you can write using LuaSocket. Run 'luasocket listen.lua' and +'luasocket talk.lua' on different terminals. Whatever you type on +talk.lua will be printed by listen.lua. dict.lua -- dict client -The dict.lua module is a cool simple client for the DICT protocol, -written by Luiz Henrique Figueiredo. Just run it and enter a few words -to see it working. +The dict.lua module is a cool simple client for the DICT protocol, +written by Luiz Henrique Figueiredo. Just run it and enter a few +words to see it working. Good luck, Diego. From 7674d3b649af95d43a0b43aa900d48fc8d2a381f Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 25 Jan 2001 21:49:48 +0000 Subject: [PATCH 009/483] It just creates the distribution now. Updated for LuaSocket 1.2 --- makefile.dist | 70 ++++++++------------------------------------------- 1 file changed, 10 insertions(+), 60 deletions(-) diff --git a/makefile.dist b/makefile.dist index 19a3775..1d8df38 100644 --- a/makefile.dist +++ b/makefile.dist @@ -1,73 +1,16 @@ #-------------------------------------------------------------------------- -# LuaSocket makefile -# Test executable for socklib -# Diego Nehab, 29/8/1999 +# Distribution makefile #-------------------------------------------------------------------------- -# don't echo commands -# .SILENT: - -CXX = g++ -CC = gcc - -DIST = luasocket-1.1 - -WARNINGS = -Wall -Wshadow -Wpointer-arith -Waggregate-return -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wnested-externs - -CFLAGS = $(WARNINGS) -D_DEBUG -O2 - -LUA = /home/diego/lib/lua -LUALIB = $(LUA)/lib -LUAINC = $(LUA)/include - -INC = -I$(LUAINC) -# LIB = $(LUA_LIB)/liblualib.a $(LUA_LIB)/liblua.a -lm -lsocket -lnsl -# LIB = $(LUA_LIB)/liblualib.a $(LUA_LIB)/liblua.a -lm -lnsl -LIB = $(LUALIB)/liblualib.a $(LUALIB)/liblua.a -lm +DIST = luasocket-1.2 SRC = ~diego/tec/luasocket -OBJ = /tmp -DEP = /tmp - -# list of .cpp files -c_sources = luasocket.c lua.c - -# corresponding .o files -c_objects = $(addprefix $(OBJ)/, $(addsuffix .o, $(basename $(c_sources)))) - -# binary depends on objects -luasocket: $(c_objects) - $(CC) $(CPPFLAGS) -o $@ $(c_objects) $(LIB) - -# rule to create them -$(c_objects): $(OBJ)/%.o: $(SRC)/%.c - $(CC) -c $(CFLAGS) $(INC) -o $@ $< - -# corresponding .d files -c_deps = $(addprefix $(DEP)/, $(addsuffix .d, $(basename $(c_sources)))) - -# makefile depend on them... -makefile : $(c_deps) - -# ... since it includes them --include $(c_deps) - -# rule to create them -$(c_deps) : $(DEP)/%.d : $(SRC)/%.c - $(SHELL) -ec '$(CC) -MM $(CFLAGS) $(INC) $< \ - | sed '\''s%\($*\.o\)%$@ $(OBJ)/\1%'\'' > $@; \ - [ -s $@ ] || rm -f $@' - -# clean all trash -clean: - rm -f $(OBJ)/*.o - rm -f $(DEP)/*.d - rm -f luasocket core dist: mkdir -p $(DIST)/lua mkdir -p $(DIST)/examples mkdir -p $(DIST)/html + mkdir -p $(DIST)/test cp -vf *.c $(DIST) cp -vf *.h $(DIST) cp -vf makefile $(DIST) @@ -77,5 +20,12 @@ dist: cp -vf examples/*.lua $(DIST)/examples cp -vf examples/README $(DIST)/examples cp -vf html/manual.html $(DIST)/html + cp -vf html/lua.png $(DIST)/html + cp -vf html/vim.png $(DIST)/html + cp -vf html/anybrowser.png $(DIST)/html + cp -vf test/testclnt.lua $(DIST)/test + cp -vf test/testsrvr.lua $(DIST)/test + cp -vf test/testcmd.lua $(DIST)/test + cp -vf test/README $(DIST)/test tar -zcvf $(DIST).tar.gz $(DIST) zip -r $(DIST).zip $(DIST) From 973295ba18631846ef2d2d1f891f436cfa5199e5 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 25 Jan 2001 21:53:02 +0000 Subject: [PATCH 010/483] UDP code implemented. DNS code has been rewritten. Almost everything has been changed. :-) --- src/luasocket.c | 2267 ++++++++++++++++++++++++++++++----------------- 1 file changed, 1468 insertions(+), 799 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index e6f423c..b81bca7 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -1,12 +1,12 @@ /*=========================================================================*\ -* TCP/IP bind for the Lua language +* IPv4 Sockets for the Lua language * Diego Nehab * 26/11/1999 * -* Module: LUASOCKET.C +* Module: luasocket.c * * This module is part of an effort to make the most important features -* of the TCP/IP protocol available to Lua scripts. +* of the IPv4 Socket layer available to Lua scripts. * The Lua interface to TCP/IP follows the BSD TCP/IP API closely, * trying to simplify all tasks involved in setting up a client connection * and simple server connections. @@ -60,28 +60,20 @@ #include /* gethostbyname and gethostbyaddr functions */ #include -/* for some reason, bcopy and it's friends are not defined automatically -** on the IRIX plataforms... */ -#ifdef __sgi -#include -#endif #endif /*=========================================================================*\ * Datatype compatibilization and some simple changes \*=========================================================================*/ #ifndef WIN32 -#define closesocket close /* WinSock2 has a closesock function instead - ** of using the regular close function */ -#define SOCKET int /* it defines a SOCKET type instead of - ** using an integer file descriptor */ -#define INVALID_SOCKET (-1) /* and uses the this macro to represent and - ** invalid socket */ -#ifndef INADDR_NONE /* some unix flavours don't define this */ -#define INADDR_NONE (-1) -#endif -#ifndef CLK_TCK /* SunOS, for instance, does not define */ -#define CLK_TCK 60 /* CLK_TCK */ +#define closesocket close /* WinSock2 has a closesock function instead + ** of using the regular close function */ +#define SOCKET int /* it defines a SOCKET type instead of + ** using an integer file descriptor */ +#define INVALID_SOCKET (-1) /* and uses the this macro to represent and + ** invalid socket */ +#ifndef CLK_TCK /* SunOS, does not define CLK_TCK */ +#define CLK_TCK 60 #endif #endif @@ -93,9 +85,10 @@ * codes. The values are mapped into Lua values by the function * push_error. \*-------------------------------------------------------------------------*/ -#define NET_DONE -1 /* operation completed successfully */ -#define NET_TIMEOUT 0 /* operation timed out */ -#define NET_CLOSED 1 /* the connection has been closed */ +#define NET_DONE -1 /* operation completed successfully */ +#define NET_TIMEOUT 0 /* operation timed out */ +#define NET_CLOSED 1 /* the connection has been closed */ +#define NET_REFUSED 2 /* the data transfer has been refused */ /*-------------------------------------------------------------------------*\ * Time out mode to be checked @@ -109,25 +102,32 @@ \*-------------------------------------------------------------------------*/ #define P_SOCK "(p_sock)sock" +/*-------------------------------------------------------------------------*\ +* The maximum message size handled (576 bytes should be enough...) +\*-------------------------------------------------------------------------*/ +#define UDPMAX 4096 + /*-------------------------------------------------------------------------*\ * Both socket types are stored in the same structure to simplify * implementation. The tag value used is different, though. -* The timeout and buffer parameters are not used by server sockets. +* The buffer parameters are not used by server and UDP sockets. \*-------------------------------------------------------------------------*/ typedef struct t_sock { - /* operating system socket object */ - SOCKET sock; - /* start time of the current operation */ - int tm_start; - /* return and blocking timeout values (-1 if no limit) */ - int tm_return, tm_block; - /* buffered I/O storage */ - unsigned char bf_buffer[LUASOCKET_BUFFERSIZE]; - /* first and last red bytes not yet passed to application */ - int bf_first, bf_last; + /* operating system socket object */ + SOCKET sock; + /* start time of the current operation */ + int tm_start; + /* return and blocking timeout values (-1 if no limit) */ + int tm_return, tm_block; + /* buffered I/O storage */ + unsigned char bf_buffer[LUASOCKET_BUFFERSIZE]; + /* first and last red bytes not yet passed to application */ + int bf_first, bf_last; + /* is this udp socket in "connected" state? */ + int is_connected; #ifdef _DEBUG - /* end time of current operation, for debug purposes */ - int tm_end; + /* end time of current operation, for debug purposes */ + int tm_end; #endif } t_sock; typedef t_sock *p_sock; @@ -136,7 +136,7 @@ typedef t_sock *p_sock; * Tags passed as closure values to global LuaSocket API functions \*-------------------------------------------------------------------------*/ typedef struct t_tags { - int client, server, table; + int client, server, table, udp; } t_tags; typedef t_tags *p_tags; @@ -151,27 +151,46 @@ typedef t_tags *p_tags; #define max(x, y) ((x) > (y) ? x : y) #endif +/* we are lazy.. */ +typedef struct sockaddr SA; + /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ -/* luasocket API functions */ -static int global_connect(lua_State *L); -static int global_bind(lua_State *L); -static int table_listen(lua_State *L); -static int table_accept(lua_State *L); -static int table_send(lua_State *L); -static int table_receive(lua_State *L); +/* luasocket global API functions */ +static int global_tcpconnect(lua_State *L); +static int global_tcpbind(lua_State *L); +static int global_udpsocket(lua_State *L); +static int global_toip(lua_State *L); +static int global_tohostname(lua_State *L); + +/* luasocket table method API functions */ +static int table_tcpaccept(lua_State *L); +static int table_tcpsend(lua_State *L); +static int table_tcpreceive(lua_State *L); +static int table_udpsendto(lua_State *L); +static int table_udpreceivefrom(lua_State *L); +static int table_udpsetpeername(lua_State *L); static int table_timeout(lua_State *L); static int table_close(lua_State *L); +static int table_poll(lua_State *L); +static int table_getpeername(lua_State *L); +static int table_getsockname(lua_State *L); + +/* luasocket optional global API functions */ #ifndef LUASOCKET_NOGLOBALS -static int global_listen(lua_State *L); -static int global_accept(lua_State *L); +static int global_tcpaccept(lua_State *L); +static int global_udpsendto(lua_State *L); +static int global_udpreceivefrom(lua_State *L); +static int global_udpsetpeername(lua_State *L); +static int global_udpsetsockname(lua_State *L); +static int global_getsockname(lua_State *L); +static int global_getpeername(lua_State *L); static int global_send(lua_State *L); static int global_receive(lua_State *L); static int global_timeout(lua_State *L); static int global_close(lua_State *L); -static p_sock get_selfclientsock(lua_State *L, p_tags tags); -static p_sock get_selfserversock(lua_State *L, p_tags tags); +static int global_poll(lua_State *L); #endif /* buffered I/O management */ @@ -195,29 +214,49 @@ static int receive_all(lua_State *L, p_sock sock); /* parameter manipulation functions */ static p_tags pop_tags(lua_State *L); static p_sock pop_sock(lua_State *L); -static p_sock get_selfsock(lua_State *L, p_tags tags); -static int gc_sock(lua_State *L); +static p_sock get_selfsock(lua_State *L, p_tags tags, int *tag); static p_sock push_servertable(lua_State *L, p_tags tags); static p_sock push_clienttable(lua_State *L, p_tags tags); +static p_sock push_udptable(lua_State *L, p_tags tags); static void push_error(lua_State *L, int err); +static void push_resolved(lua_State *L, struct hostent *hp); /* error code translations functions */ static char *host_strerror(void); static char *bind_strerror(void); -static char *sock_strerror(void); +static char *socket_strerror(void); static char *connect_strerror(void); -/* auxiliary functions */ +/* socket auxiliary functions */ +const char *tcp_trybind(p_sock sock, const char *address, + unsigned short port, int backlog); +const char *tcp_tryconnect(p_sock sock, const char *address, + unsigned short port); +const char *udp_setpeername(p_sock sock, const char *address, + unsigned short port); +const char *udp_setsockname(p_sock sock, const char *address, + unsigned short port); +static void set_reuseaddr(p_sock sock); static void set_blocking(p_sock sock); static void set_nonblocking(p_sock sock); -static int create_tcpsocket(p_sock sock); -static int fill_sockaddr(struct sockaddr_in *server, const char *hostname, - unsigned short port); #ifdef WIN32 static int winsock_open(void); +#define LUASOCKET_ATON #endif +#ifdef LUASOCKET_ATON +static int inet_aton(const char *cp, struct in_addr *inp); +#endif + +#ifndef LUASOCKET_NOGLOBALS +static p_sock get_selfserversock(lua_State *L, p_tags tags); +static p_sock get_selfudpsock(lua_State *L, p_tags tags); +#endif + +/* tag methods */ +static int gc_table(lua_State *L); + /*=========================================================================*\ * Test support functions \*=========================================================================*/ @@ -228,8 +267,8 @@ static int winsock_open(void); static int global_time(lua_State *L); static int global_time(lua_State *L) { - lua_pushnumber(L, tm_gettime()/1000.0); - return 1; + lua_pushnumber(L, tm_gettime()/1000.0); + return 1; } /*-------------------------------------------------------------------------*\ @@ -244,7 +283,7 @@ static int global_sleep(lua_State *L) #else sleep(sec); #endif - return 0; + return 0; } #endif @@ -257,244 +296,427 @@ static int global_sleep(lua_State *L) * Creates a client socket and returns it to the Lua script. The timeout * values are initialized as -1 so that the socket will block at any * IO operation. -* Lua Input -* host: host name or ip address to connect to +* Lua Input: address, port +* address: host name or ip address to connect to * port: port number on host * Lua Returns -* On success: client socket +* On success: client socket object * On error: nil, followed by an error message \*-------------------------------------------------------------------------*/ -static int global_connect(lua_State *L) +static int global_tcpconnect(lua_State *L) { - p_tags tags = pop_tags(L); - const char *hostname = luaL_check_string(L, 1); - unsigned short port = (unsigned short) luaL_check_number(L, 2); - struct sockaddr_in server; - p_sock sock = push_clienttable(L, tags); - if (!sock) { - lua_pushnil(L); - lua_pushstring(L, "out of memory"); - return 2; - } - if (!create_tcpsocket(sock)) { - lua_pushnil(L); - lua_pushstring(L, sock_strerror()); - return 2; - } - /* fills the sockaddr structure with the information needed to - ** connect our socket with the remote host */ - if (!fill_sockaddr(&server, hostname, port)) { - lua_pushnil(L); - lua_pushstring(L, host_strerror()); - return 2; - } - if (connect(sock->sock, (struct sockaddr *)&server, sizeof(server)) < 0) { - /* no connection? we close the socket to free the descriptor */ - closesocket(sock->sock); - lua_pushnil(L); - lua_pushstring(L, connect_strerror()); - return 2; - } - /* all operations on client sockets are non-blocking */ - set_nonblocking(sock); - lua_pushnil(L); - return 2; + p_tags tags = pop_tags(L); + const char *address = luaL_check_string(L, 1); + unsigned short port = (unsigned short) luaL_check_number(L, 2); + p_sock sock = push_clienttable(L, tags); + const char *err; + if (!sock) { + lua_pushnil(L); + lua_pushstring(L, "out of memory"); + return 2; + } + err = tcp_tryconnect(sock, address, port); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + set_nonblocking(sock); + return 1; } /*-------------------------------------------------------------------------*\ -* Converts from ip number to host name -* Lua Input -* ip: ip number +* Creates a udp socket object and returns it to the Lua script. +* The timeout values are initialized as -1 so that the socket will block +* at any IO operation. * Lua Returns -* On success: domain name +* On success: udp socket * On error: nil, followed by an error message \*-------------------------------------------------------------------------*/ -static int global_toip(lua_State *L) +static int global_udpsocket(lua_State *L) { - struct hostent *host; - struct in_addr addr; - pop_tags(L); - host = gethostbyname(luaL_check_string(L, 1)); - if (!host) { - lua_pushnil(L); - lua_pushstring(L, host_strerror()); - return 2; - } - memcpy(&addr, host->h_addr, (unsigned) host->h_length); - lua_pushstring(L, inet_ntoa(addr)); - return 1; + p_tags tags = pop_tags(L); + p_sock sock = push_udptable(L, tags); + if (!sock) return 2; + return 1; } /*-------------------------------------------------------------------------*\ -* Specifies the number of connections that can be queued on a server -* socket. -* Lua Input -* sock: server socket created by the bind function -* Lua Returns -* On success: nil -* On error: an error message -\*-------------------------------------------------------------------------*/ -static int table_listen(lua_State *L) -{ - p_sock sock = pop_sock(L); - unsigned int backlog = (unsigned int) luaL_check_number(L, 2); - if (listen(sock->sock, backlog) < 0) { - lua_pushstring(L, "listen error"); - return 1; - } else { - lua_pushnil(L); - return 1; - } -} - -/*-------------------------------------------------------------------------*\ -* Returns a client socket attempting to connect to a server socket. -* The function blocks until a client shows up. -* Lua Input +* Waits for and returns a client socket object attempting connection +* with a server socket. The function blocks until a client shows up or +* until a timeout condition is met. +* Lua Input: sock * sock: server socket created by the bind function * Lua Returns * On success: client socket attempting connection * On error: nil followed by an error message \*-------------------------------------------------------------------------*/ -static int table_accept(lua_State *L) +static int table_tcpaccept(lua_State *L) { - struct sockaddr_in client_addr; - size_t client_len = sizeof(client_addr); - p_sock server = pop_sock(L); - p_tags tags = pop_tags(L); - p_sock client = push_clienttable(L, tags); - SOCKET accepted = accept(server->sock, (struct sockaddr *) &client_addr, - &client_len); - client->sock = accepted; - set_nonblocking(client); - lua_pushnil(L); - return 2; + struct sockaddr_in client_addr; + size_t client_len = sizeof(client_addr); + p_sock server = pop_sock(L); + p_tags tags = pop_tags(L); + p_sock client = push_clienttable(L, tags); + tm_markstart(server); + if (tm_gettimeleft(server) >= 0) { + set_nonblocking(server); + do { + if (tm_timedout(server, TM_RECEIVE)) { + lua_pushnil(L); + push_error(L, NET_TIMEOUT); + return 2; + } + client->sock = accept(server->sock, (SA *) &client_addr, + &client_len); + } while (client->sock == INVALID_SOCKET); + + } else { + set_blocking(server); + client->sock = accept(server->sock, (SA *) &client_addr, &client_len); + } + set_nonblocking(client); + return 1; } /*-------------------------------------------------------------------------*\ * Associates an address to a server socket. -* Input -* host: host name or ip address to bind to +* Lua Input: address, port [, backlog] +* address: host name or ip address to bind to * port: port to bind to -* backlog: optional parameter specifying the number of connections -* to keep waiting before refuse a connection. the default value is 1. -* Returns -* On success: server socket bound to address, the ip address and port bound +* backlog: connection queue length (default: 1) +* Lua Returns +* On success: server socket bound to address * On error: nil, followed by an error message \*-------------------------------------------------------------------------*/ -static int global_bind(lua_State *L) +static int global_tcpbind(lua_State *L) { - p_tags tags = pop_tags(L); - const char *hostname = luaL_check_string(L, 1); - unsigned short port = (unsigned short) luaL_check_number(L, 2); - unsigned int backlog = (unsigned int) luaL_opt_number(L, 3, 1.0); - struct sockaddr_in server; - size_t server_size = sizeof(server); - p_sock sock = push_servertable(L, tags); - if (!sock) { - lua_pushnil(L); - lua_pushstring(L, sock_strerror()); - return 2; - } - /* fills the sockaddr structure with the information needed to - ** connect our socket with local address */ - else if (!fill_sockaddr(&server, hostname, port)) { - free(sock); - lua_pushnil(L); - lua_pushstring(L, host_strerror()); - return 2; - } - else if (bind(sock->sock,(struct sockaddr *)&server, server_size) < 0) { - lua_pushnil(L); - lua_pushstring(L, bind_strerror()); - return 2; - } - /* define the connection waiting queue length */ - else if (listen(sock->sock, backlog) < 0) { - lua_pushnil(L); - lua_pushstring(L, "listen error"); - return 2; - } - /* pass the created socket to Lua, as a server socket */ - else { - /* get used address and port */ - getsockname(sock->sock, (struct sockaddr *)&server, &server_size); - /* pass ip number */ - lua_pushstring(L, inet_ntoa(server.sin_addr)); - /* pass port number */ - lua_pushnumber(L, ntohs(server.sin_port)); - lua_pushnil(L); - return 4; - } + p_tags tags = pop_tags(L); + const char *address = luaL_check_string(L, 1); + unsigned short port = (unsigned short) luaL_check_number(L, 2); + int backlog = (int) luaL_opt_number(L, 3, 1); + p_sock sock = push_servertable(L, tags); + const char *err; + if (!sock) { + lua_pushnil(L); + lua_pushstring(L, "out of memory"); + return 2; + } + err = tcp_trybind(sock, address, port, backlog); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + return 1; } /*-------------------------------------------------------------------------*\ -* Sets timeout values for IO operations on a client socket -* Lua Input +* Associates a local address to UDP socket +* Lua Input: address, port +* address: host name or ip address to bind to +* port: port to bind to +* Lua Returns +* On success: nil +* On error: error message +\*-------------------------------------------------------------------------*/ +static int table_udpsetsockname(lua_State *L) +{ + p_sock sock = pop_sock(L); + const char *address = luaL_check_string(L, 2); + unsigned short port = (unsigned short) luaL_check_number(L, 3); + const char *err = udp_setsockname(sock, address, port); + if (err) { + lua_pushstring(L, err); + return 1; + } + lua_pushnil(L); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Sets a peer for a UDP socket +* Lua Input: address, port +* address: remote host name +* port: remote host port +* Lua Returns +* On success: nil +* On error: error message +\*-------------------------------------------------------------------------*/ +static int table_udpsetpeername(lua_State *L) +{ + p_sock sock = pop_sock(L); + const char *address = luaL_check_string(L, 2); + unsigned short port = (unsigned short) luaL_check_number(L, 3); + const char *err = udp_setpeername(sock, address, port); + if (err) { + lua_pushstring(L, err); + return 1; + } + sock->is_connected = 1; + lua_pushnil(L); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Sets timeout values for IO operations on a socket +* Lua Input: sock, time [, mode] * sock: client socket created by the connect function * time: time out value in seconds -* mode: optional timeout mode. "block" specifies the upper bound on -* the time any IO operation on sock can cause the program to block. -* "return" specifies the upper bound on the time elapsed before the -* function returns control to the script. "block" is the default. +* mode: "b" for block timeout, "r" for return timeout. (default: b) \*-------------------------------------------------------------------------*/ static int table_timeout(lua_State *L) { - p_sock sock = pop_sock(L); - int ms = (int) (luaL_check_number(L, 2)*1000.0); - const char *mode = luaL_opt_string(L, 3, "b"); - switch (*mode) { - case 'b': - sock->tm_block = ms; - break; - case 'r': - sock->tm_return = ms; - break; - default: - luaL_arg_check(L, 0, 3, "invalid timeout mode"); - break; - } - return 0; + p_sock sock = pop_sock(L); + int ms = lua_isnil(L, 2) ? -1 : (int) (luaL_check_number(L, 2)*1000.0); + const char *mode = luaL_opt_string(L, 3, "b"); + switch (*mode) { + case 'b': + sock->tm_block = ms; + break; + case 'r': + sock->tm_return = ms; + break; + default: + luaL_arg_check(L, 0, 3, "invalid timeout mode"); + break; + } + return 0; } /*-------------------------------------------------------------------------*\ -* Send data through a socket +* Send data through a TCP socket * Lua Input: sock, a_1 [, a_2, a_3 ... a_n] * sock: client socket created by the connect function * a_i: strings to be sent. The strings will be sent on the order they * appear as parameters * Lua Returns * On success: nil, followed by the total number of bytes sent -* On error: NET_TIMEOUT if the connection timedout, or NET_CLOSED if -* the connection has been closed, followed by the total number of -* bytes sent +* On error: error message \*-------------------------------------------------------------------------*/ -static int table_send(lua_State *L) +static int table_tcpsend(lua_State *L) { - int arg; - p_sock sock = pop_sock(L); - int top = lua_gettop(L); - int total = 0; - int err = NET_DONE; - tm_markstart(sock); - for (arg = 2; arg <= top; arg++) { /* skip self table */ - int sent, wanted; - const char *data = luaL_opt_lstr(L, arg, NULL, &wanted); - if (!data || err != NET_DONE) break; - err = send_raw(sock, data, wanted, &sent); - total += sent; - } - push_error(L, err); - lua_pushnumber(L, (double) total); + int arg; + p_sock sock = pop_sock(L); + int top = lua_gettop(L); + int total = 0; + int err = NET_DONE; + tm_markstart(sock); + for (arg = 2; arg <= top; arg++) { /* skip self table */ + int sent; + size_t wanted; + const char *data = luaL_opt_lstr(L, arg, NULL, &wanted); + if (!data || err != NET_DONE) break; + err = send_raw(sock, data, wanted, &sent); + total += sent; + } + push_error(L, err); + lua_pushnumber(L, total); #ifdef _DEBUG - /* push time elapsed during operation as the last return value */ - lua_pushnumber(L, (sock->tm_end - sock->tm_start)/1000.0); + /* push time elapsed during operation as the last return value */ + lua_pushnumber(L, (sock->tm_end - sock->tm_start)/1000.0); #endif - return lua_gettop(L) - top; + return lua_gettop(L) - top; } /*-------------------------------------------------------------------------*\ -* Receive data from a socket +* Send data through a unconnected UDP socket +* Lua Input: sock, data, ip, port +* sock: udp socket +* data: data to be sent +* ip: ip address of target +* port: port in target +* Lua Returns +* On success: nil, followed by the total number of bytes sent +* On error: error message +\*-------------------------------------------------------------------------*/ +static int table_udpsendto(lua_State *L) +{ + p_sock sock = pop_sock(L); + size_t wanted; + const char *data = luaL_check_lstr(L, 2, &wanted); + const char *ip = luaL_check_string(L, 3); + unsigned short port = (unsigned short) luaL_check_number(L, 4); + struct sockaddr_in peer; + int sent; + if (sock->is_connected) lua_error(L, "sendto on connected socket"); + tm_markstart(sock); + if (tm_timedout(sock, TM_SEND)) { + push_error(L, NET_TIMEOUT); + return 1; + } + memset(&peer, 0, sizeof(peer)); + peer.sin_family = AF_INET; + peer.sin_port = htons(port); + if (!inet_aton(ip, &peer.sin_addr)) lua_error(L, "invalid ip address"); + sent = sendto(sock->sock, data, wanted, 0, (SA *) &peer, sizeof(peer)); + if (sent >= 0) { + lua_pushnil(L); + lua_pushnumber(L, sent); + return 2; + } else { + push_error(L, NET_REFUSED); + return 1; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns the list of ip addresses associated with a host name +* Lua Input: address +* address: ip address or hostname to dns lookup +* Lua Returns +* On success: first IP address followed by a resolved table +* On error: nil, followed by an error message +\*-------------------------------------------------------------------------*/ +static int global_toip(lua_State *L) +{ + const char *address = luaL_check_string(L, 1); + struct in_addr addr; + struct hostent *hp; + if (inet_aton(address, &addr)) + hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET); + else hp = gethostbyname(address); + if (!hp) { + lua_pushnil(L); + lua_pushstring(L, host_strerror()); + return 2; + } + addr = *((struct in_addr *) hp->h_addr); + lua_pushstring(L, inet_ntoa(addr)); + push_resolved(L, hp); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Returns the list of host names associated with an ip address +* Lua Input: address +* address: ip address or host name to reverse dns lookup +* Lua Returns +* On success: canonic name followed by a resolved table +* On error: nil, followed by an error message +\*-------------------------------------------------------------------------*/ +static int global_tohostname(lua_State *L) +{ + const char *address = luaL_check_string(L, 1); + struct in_addr addr; + struct hostent *hp; + if (inet_aton(address, &addr)) + hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET); + else hp = gethostbyname(address); + if (!hp) { + lua_pushnil(L); + lua_pushstring(L, host_strerror()); + return 2; + } + lua_pushstring(L, hp->h_name); + push_resolved(L, hp); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Send data through a connected UDP socket +* Lua Input: sock, data +* sock: udp socket +* data: data to be sent +* Lua Returns +* On success: nil, followed by the total number of bytes sent +* On error: error message +\*-------------------------------------------------------------------------*/ +static int table_udpsend(lua_State *L) +{ + p_sock sock = pop_sock(L); + size_t wanted; + int sent; + const char *data = luaL_check_lstr(L, 2, &wanted); + if (!sock->is_connected) lua_error(L, "send on unconnected socket"); + tm_markstart(sock); + if (tm_timedout(sock, TM_SEND)) { + push_error(L, NET_TIMEOUT); + return 1; + } + sent = send(sock->sock, data, wanted, 0); + if (sent >= 0) { + lua_pushnil(L); + lua_pushnumber(L, sent); + return 2; + } else { + push_error(L, NET_REFUSED); + return 1; + } +} + +/*-------------------------------------------------------------------------*\ +* Receives a datagram from a UDP socket +* Lua Input: sock [, wanted] +* sock: client socket created by the connect function +* wanted: the number of bytes expected (default: UDPMAX) +* Lua Returns +* On success: datagram received, ip and port of sender +* On error: nil, followed by an error message +\*-------------------------------------------------------------------------*/ +static int table_udpreceivefrom(lua_State *L) +{ + p_sock sock = pop_sock(L); + size_t wanted = (int) luaL_opt_number(L, 2, UDPMAX); + struct sockaddr_in peer; + size_t peer_len = sizeof(peer); + unsigned char buffer[UDPMAX]; + int got; + if (sock->is_connected) lua_error(L, "receivefrom on connected socket"); + tm_markstart(sock); + if (tm_timedout(sock, TM_RECEIVE)) { + lua_pushnil(L); + push_error(L, NET_TIMEOUT); + return 2; + } + wanted = min(wanted, sizeof(buffer)); + got = recvfrom(sock->sock, buffer, wanted, 0, (SA *) &peer, &peer_len); + if (got >= 0) { + lua_pushlstring(L, buffer, got); + lua_pushstring(L, inet_ntoa(peer.sin_addr)); + lua_pushnumber(L, ntohs(peer.sin_port)); + return 3; + } else { + lua_pushnil(L); + push_error(L, NET_REFUSED); + return 2; + } +} + +/*-------------------------------------------------------------------------*\ +* Receives data from a UDP socket +* Lua Input: sock [, wanted] +* sock: client socket created by the connect function +* wanted: the number of bytes expected (default: UDPMAX) +* Lua Returns +* On success: datagram received +* On error: nil, followed by an error message +\*-------------------------------------------------------------------------*/ +static int table_udpreceive(lua_State *L) +{ + p_sock sock = pop_sock(L); + size_t wanted = (size_t) luaL_opt_number(L, 2, UDPMAX); + unsigned char buffer[UDPMAX]; + int got; + tm_markstart(sock); + if (tm_timedout(sock, TM_RECEIVE)) { + lua_pushnil(L); + push_error(L, NET_TIMEOUT); + return 2; + } + got = recv(sock->sock, buffer, min(wanted, sizeof(buffer)), 0); + if (got >= 0) { + lua_pushlstring(L, buffer, got); + return 1; + } else { + lua_pushnil(L); + push_error(L, NET_REFUSED); + return 2; + } +} + +/*-------------------------------------------------------------------------*\ +* Receive data from a TCP socket * Lua Input: sock [pat_1, pat_2 ... pat_n] * sock: client socket created by the connect function * pat_i: may be one of the following @@ -509,60 +731,106 @@ static int table_send(lua_State *L) * On error: all strings for which there was no error, followed by one * nil value for the remaining strings, followed by an error code \*-------------------------------------------------------------------------*/ -static int table_receive(lua_State *L) +static int table_tcpreceive(lua_State *L) { - static const char *const modenames[] = {"*l", "*lu", "*a", NULL}; - const char *mode; - int err = NET_DONE; - int arg; - p_sock sock = pop_sock(L); - int top = lua_gettop(L); - tm_markstart(sock); - /* push default pattern if need be */ - if (top < 2) { - lua_pushstring(L, "*l"); - top++; - } - /* receive all patterns */ - for (arg = 2; arg <= top; arg++) { - /* if one pattern failed, we just skip all other patterns */ - if (err != NET_DONE) { - lua_pushnil(L); - continue; - } - if (lua_isnumber(L, arg)) { - int size = (int) lua_tonumber(L, arg); - err = receive_raw(L, sock, size); - } else { - mode = luaL_opt_string(L, arg, NULL); - /* get next pattern */ - switch (luaL_findstring(mode, modenames)) { - /* DOS line mode */ - case 0: - err = receive_dosline(L, sock); - break; - /* Unix line mode */ - case 1: - err = receive_unixline(L, sock); - break; - /* until closed mode */ - case 2: - err = receive_all(L, sock); - break; - /* else it is an error */ - default: - luaL_arg_check(L, 0, arg, "invalid receive pattern"); - break; - } - } - } - /* last return is an error code */ - push_error(L, err); + static const char *const modenames[] = {"*l", "*lu", "*a", NULL}; + const char *mode; + int err = NET_DONE; + int arg; + p_sock sock = pop_sock(L); + int top = lua_gettop(L); + tm_markstart(sock); + /* push default pattern if need be */ + if (top < 2) { + lua_pushstring(L, "*l"); + top++; + } + /* make sure we have enough stack space */ + luaL_checkstack(L, top+LUA_MINSTACK, "too many arguments"); + /* receive all patterns */ + for (arg = 2; arg <= top; arg++) { + /* if one pattern failed, we just skip all other patterns */ + if (err != NET_DONE) { + lua_pushnil(L); + continue; + } + if (lua_isnumber(L, arg)) { + int size = (int) lua_tonumber(L, arg); + err = receive_raw(L, sock, size); + } else { + mode = luaL_opt_string(L, arg, NULL); + /* get next pattern */ + switch (luaL_findstring(mode, modenames)) { + /* DOS line mode */ + case 0: + err = receive_dosline(L, sock); + break; + /* Unix line mode */ + case 1: + err = receive_unixline(L, sock); + break; + /* until closed mode */ + case 2: + err = receive_all(L, sock); + break; + /* else it is an error */ + default: + luaL_arg_check(L, 0, arg, "invalid receive pattern"); + break; + } + } + } + /* last return is an error code */ + push_error(L, err); #ifdef _DEBUG - /* push time elapsed during operation as the last return value */ - lua_pushnumber(L, (sock->tm_end - sock->tm_start)/1000.0); + /* push time elapsed during operation as the last return value */ + lua_pushnumber(L, (sock->tm_end - sock->tm_start)/1000.0); #endif - return lua_gettop(L) - top; + return lua_gettop(L) - top; +} + +/*-------------------------------------------------------------------------*\ +* Retrieves socket peer name +* Lua Input: sock +* sock: socket +* Lua Returns +* On success: ip address and port of peer +* On error: nil +\*-------------------------------------------------------------------------*/ +static int table_getpeername(lua_State *L) +{ + p_sock sock = pop_sock(L); + struct sockaddr_in peer; + size_t peer_len = sizeof(peer); + if (getpeername(sock->sock, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + return 1; + } + lua_pushstring(L, inet_ntoa(peer.sin_addr)); + lua_pushnumber(L, ntohs(peer.sin_port)); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Retrieves socket local name +* Lua Input: sock +* sock: socket +* Lua Returns +* On success: local ip address and port +* On error: nil +\*-------------------------------------------------------------------------*/ +static int table_getsockname(lua_State *L) +{ + p_sock sock = pop_sock(L); + struct sockaddr_in local; + size_t local_len = sizeof(local); + if (getsockname(sock->sock, (SA *) &local, &local_len) < 0) { + lua_pushnil(L); + return 1; + } + lua_pushstring(L, inet_ntoa(local.sin_addr)); + lua_pushnumber(L, ntohs(local.sin_port)); + return 2; } /*-------------------------------------------------------------------------*\ @@ -572,25 +840,68 @@ static int table_receive(lua_State *L) \*-------------------------------------------------------------------------*/ static int table_close(lua_State *L) { - /* close socket and set value to -1 so that pop_socket can later - ** detect the use of a closed socket */ - p_sock sock = pop_sock(L); - closesocket(sock->sock); - sock->sock = -1; - return 0; + /* close socket and set value to INVALID_SOCKET so that + ** pop_socket can later detect the use of a closed socket */ + p_sock sock = pop_sock(L); + closesocket(sock->sock); + sock->sock = INVALID_SOCKET; + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Tests if we can immediately read or write on a socket +* Lua Input +* sock: socket to be closed +* operation: operation to query "*r", "*s" +* Lua Returns +* 1 if operation will be accepted, nil otherwise +\*-------------------------------------------------------------------------*/ +static int table_poll(lua_State *L) +{ + p_sock sock = pop_sock(L); + const char *op = luaL_check_string(L, 2); + int tm_block = sock->tm_block; + int tm_return = sock->tm_return; + if (!*op || *op != '*') lua_error(L, "invalid poll pattern"); + op++; + tm_markstart(sock); + switch (*op) { + case 'r': + sock->tm_block = sock->tm_return = 0; + if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) + lua_pushnil(L); + else lua_pushnumber(L, 1); + sock->tm_block = tm_block; + sock->tm_return = tm_return; + break; + case 's': + sock->tm_block = sock->tm_return = 0; + if (tm_timedout(sock, TM_SEND)) lua_pushnil(L); + else lua_pushnumber(L, 1); + sock->tm_block = tm_block; + sock->tm_return = tm_return; + break; + default: + lua_error(L, "invalid poll pattern"); + break; + } + return 1; } /*-------------------------------------------------------------------------*\ * Garbage collection fallback for the socket objects. This function * makes sure that all collected sockets are closed. \*-------------------------------------------------------------------------*/ -static int gc_sock(lua_State *L) +static int gc_table(lua_State *L) { - p_tags tags = pop_tags(L); - p_sock sock = get_selfsock(L, tags); - /* sock might have been closed */ - if (sock->sock >= 0) closesocket(sock->sock); - return 0; + p_tags tags = pop_tags(L); + p_sock sock = get_selfsock(L, tags, NULL); + /* sock might have been closed before */ + if (sock->sock != INVALID_SOCKET) { + closesocket(sock->sock); + sock->sock = INVALID_SOCKET; + } + return 0; } /*=========================================================================*\ @@ -605,150 +916,210 @@ static int gc_sock(lua_State *L) static void handle_sigpipe(void); static void handle_sigpipe(void) { - struct sigaction new; - memset(&new, 0, sizeof(new)); - new.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &new, NULL); + struct sigaction new; + memset(&new, 0, sizeof(new)); + new.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &new, NULL); } #endif /*-------------------------------------------------------------------------*\ -* Creates a TCP/IP socket. +* Tries to create a TCP socket and connect to remote address (address, port) * Input -* sock: structure to receive new socket +* address: host name or ip address +* port: port number to bind to * Returns -* 1 if successfull, 0 in case or error +* NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ -static int create_tcpsocket(p_sock sock) +const char *tcp_tryconnect(p_sock sock, const char *address, + unsigned short port) { - sock->sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock->sock < 0) return 0; -#ifdef _DEBUG -/* this allow us to re-bind onto an address even if there is still -** a TIME_WAIT condition. debugging is much more confortable, because -** we don't get "address already in use" errors all the time we -** re-run the program before the OS is ready. in real life, though -** there could be data pending on the socket and this could lead to -** some weird errors. */ -{ - int val = 1; - setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, (char *) &val, - sizeof(val)); -} -#endif - return 1; + struct sockaddr_in remote; + memset(&remote, 0, sizeof(remote)); + if (inet_aton(address, &remote.sin_addr)) { + remote.sin_family = AF_INET; + remote.sin_port = htons(port); + sock->sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock->sock == INVALID_SOCKET) return socket_strerror(); + if (connect(sock->sock, (SA *) &remote, sizeof(remote)) < 0) { + closesocket(sock->sock); + sock->sock = INVALID_SOCKET; + return connect_strerror(); + } + /* go ahead and try by hostname resolution */ + } else { + struct hostent *hp = gethostbyname(address); + struct in_addr **addr; + if (!hp) return host_strerror(); + addr = (struct in_addr **) hp->h_addr_list; + for (; *addr != NULL; addr++) { + memcpy(&remote.sin_addr, *addr, sizeof(struct in_addr)); + remote.sin_family = AF_INET; + remote.sin_port = htons(port); + sock->sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock->sock == INVALID_SOCKET) return socket_strerror(); + if (connect(sock->sock, (SA *) &remote, sizeof(remote)) == 0) + break; + closesocket(sock->sock); + sock->sock = INVALID_SOCKET; + memset(&remote, 0, sizeof(remote)); + } + } + if (sock->sock == INVALID_SOCKET) return connect_strerror(); + return NULL; } /*-------------------------------------------------------------------------*\ -* Fills a sockaddr structure according to a given host name of ip -* address and a port number. +* Sets the SO_REUSEADDR socket option * Input -* address: pointer to sockaddr structure to be filled -* hostname: host name or ip address -* port: port number -* Returns -* 1 in case of success, 0 otherwise +* sock: socket to set option \*-------------------------------------------------------------------------*/ -static int fill_sockaddr(struct sockaddr_in *address, const char *hostname, - unsigned short port) +void set_reuseaddr(p_sock sock) { - struct hostent *host = NULL; - unsigned long addr = inet_addr(hostname); - memset(address, 0, sizeof(struct sockaddr_in)); - if (strcmp(hostname, "*")) { - /* BSD says we could have used gethostbyname even if the hostname is - ** in ip address form, but WinSock2 says we can't. Therefore we - ** choose a method that works on both plataforms */ - if (addr == INADDR_NONE) host = gethostbyname(hostname); - else host = gethostbyaddr((char * ) &addr, sizeof(unsigned long), - AF_INET); - if (!host) - return 0; - memcpy(&(address->sin_addr), host->h_addr, (unsigned) host->h_length); - } else { - address->sin_addr.s_addr = htonl(INADDR_ANY); - } - address->sin_family = AF_INET; - address->sin_port = htons(port); - return 1; + int val = 1; + setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)); } /*-------------------------------------------------------------------------*\ -* Creates a t_sock structure with default values for a client sock. -* Pushes the Lua table with sock fields and appropriate methods +* Tries to create a TCP socket and bind it to (address, port) * Input -* tags: tags structure +* address: host name or ip address +* port: port number to bind to +* backlog: backlog to set * Returns -* pointer to allocated t_sock structure, NULL in case of error +* NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ -static p_sock push_clienttable(lua_State *L, p_tags tags) +const char *tcp_trybind(p_sock sock, const char *address, + unsigned short port, int backlog) { - static struct luaL_reg funcs[] = { - {"send", table_send}, - {"receive", table_receive}, - {"close", table_close}, - {"timeout", table_timeout}, - }; - int i; - p_sock sock; - lua_newtable(L); lua_settag(L, tags->table); - lua_pushstring(L, P_SOCK); - sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); - if (!sock) lua_error(L, "out of memory"); - lua_settag(L, tags->client); - lua_settable(L, -3); - sock->sock = -1; - sock->tm_block = -1; - sock->tm_return = -1; - sock->bf_first = sock->bf_last = 0; - for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { - lua_pushstring(L, funcs[i].name); - lua_pushusertag(L, sock, tags->client); - lua_pushcclosure(L, funcs[i].func, 1); - lua_settable(L, -3); - } - return sock; + struct sockaddr_in local; + memset(&local, 0, sizeof(local)); + local.sin_port = htons(port); + local.sin_family = AF_INET; + local.sin_addr.s_addr = htonl(INADDR_ANY); + sock->sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock->sock == INVALID_SOCKET) return socket_strerror(); + set_reuseaddr(sock); + /* address is either wildcard or a valid ip address */ + if (!strcmp(address, "*") || inet_aton(address, &local.sin_addr)) { + if (bind(sock->sock, (SA *) &local, sizeof(local)) < 0) { + closesocket(sock->sock); + sock->sock = INVALID_SOCKET; + return bind_strerror(); + } + /* otherwise, proceed with domain name resolution */ + } else { + struct hostent *hp = gethostbyname(address); + struct in_addr **addr; + if (!hp) return host_strerror(); + addr = (struct in_addr **) hp->h_addr_list; + for (; *addr != NULL; addr++) { + memcpy(&local.sin_addr, *addr, sizeof(struct in_addr)); + if (bind(sock->sock, (SA *) &local, sizeof(local)) < 0) { + closesocket(sock->sock); + sock->sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock->sock == INVALID_SOCKET) return socket_strerror(); + set_reuseaddr(sock); + } else break; + } + if (*addr == NULL) return bind_strerror(); + } + /* set connection queue length */ + if (listen(sock->sock, backlog) < 0) { + closesocket(sock->sock); + sock->sock = INVALID_SOCKET; + return "listen error"; + } + /* no errors found */ + return NULL; } /*-------------------------------------------------------------------------*\ -* Creates a t_sock structure with default values for a server sock. -* Pushes the Lua table with sock fields and appropriate methods +* Tries to bind the UDP socket to (address, port) * Input -* tags: tags structure +* address: host name or ip address +* port: port number to bind to * Returns -* pointer to allocated t_sock structure, NULL in case of error +* NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ -static p_sock push_servertable(lua_State *L, p_tags tags) +const char *udp_setsockname(p_sock sock, const char *address, + unsigned short port) { - static struct luaL_reg funcs[] = { - {"listen", table_listen}, - {"close", table_close}, - }; - int i; - p_sock sock; - lua_newtable(L); lua_settag(L, tags->table); - lua_pushstring(L, P_SOCK); - sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); - if (!sock) lua_error(L, "out of memory"); - lua_settag(L, tags->server); - lua_settable(L, -3); - if (!create_tcpsocket(sock)) return NULL; - sock->tm_block = -1; - sock->tm_return = -1; - sock->bf_first = sock->bf_last = 0; - for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { - lua_pushstring(L, funcs[i].name); - lua_pushusertag(L, sock, tags->client); - lua_pushcclosure(L, funcs[i].func, 1); - lua_settable(L, -3); - } - /* the accept method is different, it needs the tags closure too */ - lua_pushstring(L, "accept"); - lua_pushuserdata(L, tags); - lua_pushusertag(L, sock, tags->client); - lua_pushcclosure(L, table_accept, 2); - lua_settable(L, -3); - return sock; + struct sockaddr_in local; + memset(&local, 0, sizeof(local)); + local.sin_port = htons(port); + local.sin_family = AF_INET; + local.sin_addr.s_addr = htonl(INADDR_ANY); + set_reuseaddr(sock); + /* address is either wildcard or a valid ip address */ + if (!strcmp(address, "*") || inet_aton(address, &local.sin_addr)) { + if (bind(sock->sock, (SA *) &local, sizeof(local)) < 0) { + closesocket(sock->sock); + sock->sock = INVALID_SOCKET; + return bind_strerror(); + } + /* otherwise, proceed with domain name resolution */ + } else { + struct hostent *hp = gethostbyname(address); + struct in_addr **addr; + if (!hp) return host_strerror(); + addr = (struct in_addr **) hp->h_addr_list; + for (; *addr != NULL; addr++) { + memcpy(&local.sin_addr, *addr, sizeof(struct in_addr)); + if (bind(sock->sock, (SA *) &local, sizeof(local)) < 0) { + closesocket(sock->sock); + sock->sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock->sock == INVALID_SOCKET) return socket_strerror(); + set_reuseaddr(sock); + } else break; + } + if (*addr == NULL) return bind_strerror(); + } + /* no errors found */ + return NULL; +} + +/*-------------------------------------------------------------------------*\ +* Tries to connect a UDP to remote address (address, port) +* Input +* address: host name or ip address +* port: port number to bind to +* Returns +* NULL in case of success, error message otherwise +\*-------------------------------------------------------------------------*/ +const char *udp_setpeername(p_sock sock, const char *address, + unsigned short port) +{ + struct sockaddr_in local; + memset(&local, 0, sizeof(local)); + local.sin_port = htons(port); + local.sin_family = AF_INET; + local.sin_addr.s_addr = htonl(INADDR_ANY); + /* address is a valid ip address */ + if (inet_aton(address, &local.sin_addr)) { + if (connect(sock->sock, (SA *) &local, sizeof(local)) < 0) { + closesocket(sock->sock); + sock->sock = INVALID_SOCKET; + return connect_strerror(); + } + /* otherwise, proceed with domain name resolution */ + } else { + struct hostent *hp = gethostbyname(address); + struct in_addr **addr; + if (!hp) return host_strerror(); + addr = (struct in_addr **) hp->h_addr_list; + for (; *addr != NULL; addr++) { + memcpy(&local.sin_addr, *addr, sizeof(struct in_addr)); + if (connect(sock->sock, (SA *) &local, sizeof(local)) < 0) { + closesocket(sock->sock); + sock->sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock->sock == INVALID_SOCKET) return socket_strerror(); + } else break; + } + if (*addr == NULL) return connect_strerror(); + } + /* no errors found */ + return NULL; } /*=========================================================================*\ @@ -764,23 +1135,23 @@ static p_sock push_servertable(lua_State *L, p_tags tags) \*-------------------------------------------------------------------------*/ static int tm_gettimeleft(p_sock sock) { - /* no timeout */ - if (sock->tm_block < 0 && sock->tm_return < 0) - return -1; - /* there is no block timeout, we use the return timeout */ - else if (sock->tm_block < 0) - return max(sock->tm_return - tm_gettime() + sock->tm_start, 0); - /* there is no return timeout, we use the block timeout */ - else if (sock->tm_return < 0) - return sock->tm_block; - /* both timeouts are specified */ - else return min(sock->tm_block, - max(sock->tm_return - tm_gettime() + sock->tm_start, 0)); + /* no timeout */ + if (sock->tm_block < 0 && sock->tm_return < 0) + return -1; + /* there is no block timeout, we use the return timeout */ + else if (sock->tm_block < 0) + return max(sock->tm_return - tm_gettime() + sock->tm_start, 0); + /* there is no return timeout, we use the block timeout */ + else if (sock->tm_return < 0) + return sock->tm_block; + /* both timeouts are specified */ + else return min(sock->tm_block, + max(sock->tm_return - tm_gettime() + sock->tm_start, 0)); } /*-------------------------------------------------------------------------*\ * Determines if we have a timeout condition or if we can proceed with -* an IO write operation. +* an IO operation. * Input * sock: socket structure being used in operation * mode: TM_RECEIVE or TM_SEND @@ -789,30 +1160,29 @@ static int tm_gettimeleft(p_sock sock) \*-------------------------------------------------------------------------*/ static int tm_timedout(p_sock sock, int mode) { - fd_set fds; - int ret, delta; - fd_set *preadfds = NULL, *pwritefds = NULL; - struct timeval tm; - struct timeval *ptm = NULL; - /* find out how much time we have left, in ms */ - int ms = tm_gettimeleft(sock); - /* fill file descriptor set */ - FD_ZERO(&fds); FD_SET(sock->sock, &fds); - /* fill timeval structure */ - tm.tv_sec = ms / 1000; - tm.tv_usec = (ms % 1000) * 1000; - /* define function parameters */ - if (ms > 0) ptm = &tm; /* ptm == NULL when we don't have timeout */ - if (mode == TM_RECEIVE) preadfds = &fds; - else pwritefds = &fds; - delta = tm_gettime(); - /* see if we can read or write or if we timedout */ - ret = select(sock->sock+1, preadfds, pwritefds, NULL, ptm); + fd_set fds; + int ret; + fd_set *preadfds = NULL, *pwritefds = NULL; + struct timeval tm; + struct timeval *ptm = NULL; + /* find out how much time we have left, in ms */ + int ms = tm_gettimeleft(sock); + /* fill file descriptor set */ + FD_ZERO(&fds); FD_SET(sock->sock, &fds); + /* fill timeval structure */ + tm.tv_sec = ms / 1000; + tm.tv_usec = (ms % 1000) * 1000; + /* define function parameters */ + if (ms >= 0) ptm = &tm; /* ptm == NULL when we don't have timeout */ + if (mode == TM_RECEIVE) preadfds = &fds; + else pwritefds = &fds; + /* see if we can read, write or if we timedout */ + ret = select(sock->sock+1, preadfds, pwritefds, NULL, ptm); #ifdef _DEBUG - /* store end time for this operation next call to OS */ - sock->tm_end = tm_gettime(); + /* store end time for this operation next call to OS */ + sock->tm_end = tm_gettime(); #endif - return ret <= 0; + return ret <= 0; } /*-------------------------------------------------------------------------*\ @@ -822,9 +1192,9 @@ static int tm_timedout(p_sock sock, int mode) \*-------------------------------------------------------------------------*/ static void tm_markstart(p_sock sock) { - sock->tm_start = tm_gettime(); + sock->tm_start = tm_gettime(); #ifdef _DEBUG - sock->tm_end = sock->tm_start; + sock->tm_end = sock->tm_start; #endif } @@ -836,10 +1206,10 @@ static void tm_markstart(p_sock sock) static int tm_gettime(void) { #ifdef _WIN32 - return GetTickCount(); + return GetTickCount(); #else - struct tms t; - return (times(&t)*1000)/CLK_TCK; + struct tms t; + return (times(&t)*1000)/CLK_TCK; #endif } @@ -855,7 +1225,7 @@ static int tm_gettime(void) \*-------------------------------------------------------------------------*/ static int bf_isempty(p_sock sock) { - return sock->bf_first >= sock->bf_last; + return sock->bf_first >= sock->bf_last; } /*-------------------------------------------------------------------------*\ @@ -866,13 +1236,13 @@ static int bf_isempty(p_sock sock) \*-------------------------------------------------------------------------*/ static void bf_skip(p_sock sock, int length) { - sock->bf_first += length; - if (bf_isempty(sock)) sock->bf_first = sock->bf_last = 0; + sock->bf_first += length; + if (bf_isempty(sock)) sock->bf_first = sock->bf_last = 0; } /*-------------------------------------------------------------------------*\ * Return any data avilable in buffer, or get more data from transport layer -* if there is none. +* if buffer is empty. * Input * sock: socket structure being used in operation * Output @@ -882,18 +1252,19 @@ static void bf_skip(p_sock sock, int length) \*-------------------------------------------------------------------------*/ static const unsigned char *bf_receive(p_sock sock, int *length) { - if (bf_isempty(sock)) { - int got = recv(sock->sock, sock->bf_buffer, LUASOCKET_BUFFERSIZE, 0); - sock->bf_first = 0; - if (got >= 0) sock->bf_last = got; - else sock->bf_last = 0; - } - *length = sock->bf_last - sock->bf_first; - return sock->bf_buffer + sock->bf_first; + if (bf_isempty(sock)) { + int got = recv(sock->sock, sock->bf_buffer, LUASOCKET_BUFFERSIZE, 0); + sock->bf_first = 0; + if (got >= 0) sock->bf_last = got; + else sock->bf_last = 0; + } + *length = sock->bf_last - sock->bf_first; + return sock->bf_buffer + sock->bf_first; } /*=========================================================================*\ * These are the function that are called for each I/O pattern +* The read patterns leave their result on the Lua stack \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Sends a raw block of data through a socket. The operations are all @@ -909,26 +1280,26 @@ static const unsigned char *bf_receive(p_sock sock, int *length) \*-------------------------------------------------------------------------*/ static int send_raw(p_sock sock, const char *data, int wanted, int *total) { - int put = 0; - *total = 0; - while (wanted > 0) { - if (tm_timedout(sock, TM_SEND)) return NET_TIMEOUT; - put = send(sock->sock, data, wanted, 0); - if (put <= 0) { + int put = 0; + *total = 0; + while (wanted > 0) { + if (tm_timedout(sock, TM_SEND)) return NET_TIMEOUT; + put = send(sock->sock, data, wanted, 0); + if (put <= 0) { #ifdef WIN32 - /* a bug in WinSock forces us to do a busy wait until we manage - ** to write, because select returns immediately even though it - ** should have blocked us */ - if (WSAGetLastError() == WSAEWOULDBLOCK) - continue; + /* a bug in WinSock forces us to do a busy wait until we manage + ** to write, because select returns immediately even though it + ** should have blocked us until we could write... */ + if (WSAGetLastError() == WSAEWOULDBLOCK) + continue; #endif - return NET_CLOSED; - } - wanted -= put; - data += put; - *total += put; - } - return NET_DONE; + return NET_CLOSED; + } + wanted -= put; + data += put; + *total += put; + } + return NET_DONE; } /*-------------------------------------------------------------------------*\ @@ -942,27 +1313,27 @@ static int send_raw(p_sock sock, const char *data, int wanted, int *total) \*-------------------------------------------------------------------------*/ static int receive_raw(lua_State *L, p_sock sock, int wanted) { - int got = 0; - const unsigned char *buffer = NULL; - luaL_Buffer b; - luaL_buffinit(L, &b); - while (wanted > 0) { - if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { - luaL_pushresult(&b); - return NET_TIMEOUT; - } - buffer = bf_receive(sock, &got); - if (got <= 0) { - luaL_pushresult(&b); - return NET_CLOSED; - } - got = min(got, wanted); - luaL_addlstring(&b, buffer, got); - bf_skip(sock, got); - wanted -= got; - } - luaL_pushresult(&b); - return NET_DONE; + int got = 0; + const unsigned char *buffer = NULL; + luaL_Buffer b; + luaL_buffinit(L, &b); + while (wanted > 0) { + if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { + luaL_pushresult(&b); + return NET_TIMEOUT; + } + buffer = bf_receive(sock, &got); + if (got <= 0) { + luaL_pushresult(&b); + return NET_CLOSED; + } + got = min(got, wanted); + luaL_addlstring(&b, buffer, got); + bf_skip(sock, got); + wanted -= got; + } + luaL_pushresult(&b); + return NET_DONE; } /*-------------------------------------------------------------------------*\ @@ -970,27 +1341,27 @@ static int receive_raw(lua_State *L, p_sock sock, int wanted) * Input * sock: socket structure being used in operation * Result -* operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED +* operation error code. NET_DONE, NET_TIMEOUT \*-------------------------------------------------------------------------*/ static int receive_all(lua_State *L, p_sock sock) { - int got = 0; - const unsigned char *buffer = NULL; - luaL_Buffer b; - luaL_buffinit(L, &b); - for ( ;; ) { - if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { - luaL_pushresult(&b); - return NET_TIMEOUT; - } - buffer = bf_receive(sock, &got); - if (got <= 0) { - luaL_pushresult(&b); - return NET_DONE; - } - luaL_addlstring(&b, buffer, got); - bf_skip(sock, got); - } + int got = 0; + const unsigned char *buffer = NULL; + luaL_Buffer b; + luaL_buffinit(L, &b); + for ( ;; ) { + if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { + luaL_pushresult(&b); + return NET_TIMEOUT; + } + buffer = bf_receive(sock, &got); + if (got <= 0) { + luaL_pushresult(&b); + return NET_DONE; + } + luaL_addlstring(&b, buffer, got); + bf_skip(sock, got); + } } /*-------------------------------------------------------------------------*\ @@ -1005,37 +1376,37 @@ static int receive_all(lua_State *L, p_sock sock) \*-------------------------------------------------------------------------*/ static int receive_dosline(lua_State *L, p_sock sock) { - int got = 0; - const unsigned char *buffer = NULL; - luaL_Buffer b; - luaL_buffinit(L, &b); - for ( ;; ) { - if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { - luaL_pushresult(&b); - return NET_TIMEOUT; - } - buffer = bf_receive(sock, &got); - if (got > 0) { - int len = 0, end = 1; - while (len < got) { - if (buffer[len] == '\n') { /* found eol */ - if (len > 0 && buffer[len-1] == '\r') { - end++; len--; - } - luaL_addlstring(&b, buffer, len); - bf_skip(sock, len + end); /* skip '\r\n' in stream */ - luaL_pushresult(&b); - return NET_DONE; - } - len++; - } - luaL_addlstring(&b, buffer, got); - bf_skip(sock, got); - } else { - luaL_pushresult(&b); - return NET_CLOSED; - } - } + int got = 0; + const unsigned char *buffer = NULL; + luaL_Buffer b; + luaL_buffinit(L, &b); + for ( ;; ) { + if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { + luaL_pushresult(&b); + return NET_TIMEOUT; + } + buffer = bf_receive(sock, &got); + if (got > 0) { + int len = 0, end = 1; + while (len < got) { + if (buffer[len] == '\n') { /* found eol */ + if (len > 0 && buffer[len-1] == '\r') { + end++; len--; + } + luaL_addlstring(&b, buffer, len); + bf_skip(sock, len + end); /* skip '\r\n' in stream */ + luaL_pushresult(&b); + return NET_DONE; + } + len++; + } + luaL_addlstring(&b, buffer, got); + bf_skip(sock, got); + } else { + luaL_pushresult(&b); + return NET_CLOSED; + } + } } /*-------------------------------------------------------------------------*\ @@ -1049,34 +1420,34 @@ static int receive_dosline(lua_State *L, p_sock sock) \*-------------------------------------------------------------------------*/ static int receive_unixline(lua_State *L, p_sock sock) { - int got = 0; - const unsigned char *buffer = NULL; - luaL_Buffer b; - luaL_buffinit(L, &b); - for ( ;; ) { - if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { - luaL_pushresult(&b); - return NET_TIMEOUT; - } - buffer = bf_receive(sock, &got); - if (got > 0) { - int len = 0; - while (len < got) { - if (buffer[len] == '\n') { /* found eol */ - luaL_addlstring(&b, buffer, len); - bf_skip(sock, len + 1); /* skip '\n' in stream */ - luaL_pushresult(&b); - return NET_DONE; - } - len++; - } - luaL_addlstring(&b, buffer, got); - bf_skip(sock, got); - } else { - luaL_pushresult(&b); - return NET_CLOSED; - } - } + int got = 0; + const unsigned char *buffer = NULL; + luaL_Buffer b; + luaL_buffinit(L, &b); + for ( ;; ) { + if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { + luaL_pushresult(&b); + return NET_TIMEOUT; + } + buffer = bf_receive(sock, &got); + if (got > 0) { + int len = 0; + while (len < got) { + if (buffer[len] == '\n') { /* found eol */ + luaL_addlstring(&b, buffer, len); + bf_skip(sock, len + 1); /* skip '\n' in stream */ + luaL_pushresult(&b); + return NET_DONE; + } + len++; + } + luaL_addlstring(&b, buffer, got); + bf_skip(sock, got); + } else { + luaL_pushresult(&b); + return NET_CLOSED; + } + } } /*=========================================================================*\ @@ -1088,116 +1459,381 @@ static int receive_unixline(lua_State *L, p_sock sock) \*-------------------------------------------------------------------------*/ void lua_socketlibopen(lua_State *L) { - static struct luaL_reg funcs[] = { - {"connect", global_connect}, - {"bind", global_bind}, - {"toip", global_toip}, - }; - int i; - /* declare new Lua tags for used userdata values */ - p_tags tags = (p_tags) lua_newuserdata(L, sizeof(t_tags)); - if (!tags) lua_error(L, "out of memory"); - tags->client = lua_newtag(L); - tags->server = lua_newtag(L); - tags->table = lua_newtag(L); - /* global functions exported */ - for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { - lua_pushuserdata(L, tags); - lua_pushcclosure(L, funcs[i].func, 1); - lua_setglobal(L, funcs[i].name); - } - /* socket garbage collection */ - lua_pushuserdata(L, tags); - lua_pushcclosure(L, gc_sock, 1); - lua_settagmethod(L, tags->table, "gc"); + static struct luaL_reg funcs[] = { + {"connect", global_tcpconnect}, + {"udpsocket", global_udpsocket}, + {"bind", global_tcpbind}, + {"toip", global_toip}, + {"tohostname", global_tohostname}, + }; + unsigned int i; + /* declare new Lua tags for used userdata values */ + p_tags tags = (p_tags) lua_newuserdata(L, sizeof(t_tags)); + if (!tags) lua_error(L, "out of memory"); + tags->client = lua_newtag(L); + tags->server = lua_newtag(L); + tags->table = lua_newtag(L); + tags->udp = lua_newtag(L); + /* global functions exported */ + for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { + lua_pushuserdata(L, tags); + lua_pushcclosure(L, funcs[i].func, 1); + lua_setglobal(L, funcs[i].name); + } + /* socket garbage collection */ + lua_pushuserdata(L, tags); + lua_pushcclosure(L, gc_table, 1); + lua_settagmethod(L, tags->table, "gc"); #ifndef LUASOCKET_NOGLOBALS - /* global version of socket table functions */ -{ static struct luaL_reg opt_funcs[] = { - {"send", global_send}, - {"receive", global_receive}, - {"accept", global_accept}, - {"close", global_close}, - {"timeout", global_timeout}, - {"listen", global_listen}, - }; - for (i = 0; i < sizeof(opt_funcs)/sizeof(opt_funcs[0]); i++) { - lua_pushuserdata(L, tags); - lua_pushcclosure(L, opt_funcs[i].func, 1); - lua_setglobal(L, opt_funcs[i].name); - } + /* global version of socket table functions */ +{ static struct luaL_reg opt_funcs[] = { + {"accept", global_tcpaccept}, + {"setpeername", global_udpsetpeername}, + {"setsockname", global_udpsetsockname}, + {"getsockname", global_getsockname}, + {"getpeername", global_getpeername}, + {"sendto", global_udpsendto}, + {"receivefrom", global_udpreceivefrom}, + {"timeout", global_timeout}, + {"send", global_send}, + {"poll", global_poll}, + {"receive", global_receive}, + {"close", global_close}, + }; + for (i = 0; i < sizeof(opt_funcs)/sizeof(opt_funcs[0]); i++) { + lua_pushuserdata(L, tags); + lua_pushcclosure(L, opt_funcs[i].func, 1); + lua_setglobal(L, opt_funcs[i].name); + } } #endif #ifdef WIN32 - /* WinSock needs special initialization */ - winsock_open(); + /* WinSock needs special initialization */ + winsock_open(); #else - /* avoid getting killed by a SIGPIPE signal thrown by send */ - handle_sigpipe(); + /* avoid getting killed by a SIGPIPE signal thrown by send */ + handle_sigpipe(); #endif #ifdef _DEBUG - /* test support functions */ - lua_pushcfunction(L, global_sleep); lua_setglobal(L, "sleep"); - lua_pushcfunction(L, global_time); lua_setglobal(L, "time"); + /* test support functions */ + lua_pushcfunction(L, global_sleep); lua_setglobal(L, "sleep"); + lua_pushcfunction(L, global_time); lua_setglobal(L, "time"); #endif - /* avoid stupid compiler warnings */ - (void) set_blocking; } /*=========================================================================*\ * Optional global version of socket table methods +* Simply push socket object on top or stack and call the table methods. \*=========================================================================*/ #ifndef LUASOCKET_NOGLOBALS -int global_accept(lua_State *L) +int global_tcpaccept(lua_State *L) { - p_tags tags = pop_tags(L); - p_sock sock = get_selfserversock(L, tags); - lua_pushuserdata(L, tags); - lua_pushusertag(L, sock, tags->server); - return table_accept(L); + p_tags tags = pop_tags(L); + p_sock sock = get_selfserversock(L, tags); + lua_pushuserdata(L, tags); + lua_pushusertag(L, sock, tags->server); + return table_tcpaccept(L); } -int global_listen(lua_State *L) +int global_udpsendto(lua_State *L) { - p_tags tags = pop_tags(L); - p_sock sock = get_selfserversock(L, tags); - lua_pushusertag(L, sock, tags->server); - return table_listen(L); + p_tags tags = pop_tags(L); + p_sock sock = get_selfudpsock(L, tags); + lua_pushusertag(L, sock, tags->udp); + return table_udpsendto(L); +} + +int global_udpsetpeername(lua_State *L) +{ + p_tags tags = pop_tags(L); + p_sock sock = get_selfudpsock(L, tags); + lua_pushusertag(L, sock, tags->udp); + return table_udpsetpeername(L); +} + +int global_udpsetsockname(lua_State *L) +{ + p_tags tags = pop_tags(L); + p_sock sock = get_selfudpsock(L, tags); + lua_pushusertag(L, sock, tags->udp); + return table_udpsetsockname(L); +} + +int global_udpreceivefrom(lua_State *L) +{ + p_tags tags = pop_tags(L); + p_sock sock = get_selfudpsock(L, tags); + lua_pushusertag(L, sock, tags->udp); + return table_udpreceivefrom(L); +} + +int global_poll(lua_State *L) +{ + p_tags tags = pop_tags(L); + int tag; + p_sock sock = get_selfsock(L, tags, &tag); + lua_pushusertag(L, sock, tag); + return table_poll(L); } int global_send(lua_State *L) { - p_tags tags = pop_tags(L); - p_sock sock = get_selfclientsock(L, tags); - lua_pushusertag(L, sock, tags->client); - return table_send(L); + p_tags tags = pop_tags(L); + int tag; + p_sock sock = get_selfsock(L, tags, &tag); + if (tag == tags->udp) { + lua_pushusertag(L, sock, tags->udp); + return table_udpsend(L); + } else if (tag == tags->client) { + lua_pushusertag(L, sock, tags->client); + return table_tcpsend(L); + } else if (tag == tags->server) { + lua_error(L, "send on server socket"); + } else + lua_error(L, "invalid socket object"); + /* avoid compiler warnings */ + return 0; } int global_receive(lua_State *L) { - p_tags tags = pop_tags(L); - p_sock sock = get_selfclientsock(L, tags); - lua_pushusertag(L, sock, tags->client); - return table_receive(L); + p_tags tags = pop_tags(L); + int tag; + p_sock sock = get_selfsock(L, tags, &tag); + if (tag == tags->udp) { + lua_pushusertag(L, sock, tags->udp); + return table_udpreceive(L); + } else if (tag == tags->client) { + lua_pushusertag(L, sock, tags->client); + return table_tcpreceive(L); + } else if (tag == tags->server) { + lua_error(L, "receive on server socket"); + } else + lua_error(L, "invalid socket object"); + /* avoid compiler warnings */ + return 0; } int global_timeout(lua_State *L) { - p_tags tags = pop_tags(L); - p_sock sock = get_selfclientsock(L, tags); - lua_pushusertag(L, sock, tags->client); - return table_timeout(L); + p_tags tags = pop_tags(L); + int tag; + p_sock sock = get_selfsock(L, tags, &tag); + lua_pushusertag(L, sock, tag); + return table_timeout(L); +} + +int global_getpeername(lua_State *L) +{ + p_tags tags = pop_tags(L); + int tag; + p_sock sock = get_selfsock(L, tags, &tag); + if (tag == tags->server) lua_error(L, "getpeername on server socket"); + lua_pushusertag(L, sock, tag); + return table_getpeername(L); +} + +int global_getsockname(lua_State *L) +{ + p_tags tags = pop_tags(L); + int tag; + p_sock sock = get_selfsock(L, tags, &tag); + lua_pushusertag(L, sock, tag); + return table_getsockname(L); } int global_close(lua_State *L) { - return gc_sock(L); + /* just call the garbage collection tag method. it knows what to do */ + return gc_table(L); } #endif /*=========================================================================*\ -* Parameter manipulation functions +* Lua Stack manipulation functions \*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a t_sock structure with default values for a client sock. +* Pushes the Lua table with sock fields and appropriate methods +* Input +* tags: tags structure +* Returns +* pointer to allocated t_sock structure, NULL in case of error +\*-------------------------------------------------------------------------*/ +static p_sock push_clienttable(lua_State *L, p_tags tags) +{ + static struct luaL_reg funcs[] = { + {"close", table_close}, + {"getsockname", table_getsockname}, + {"getpeername", table_getpeername}, + {"poll", table_poll}, + {"receive", table_tcpreceive}, + {"send", table_tcpsend}, + {"timeout", table_timeout}, + }; + unsigned int i; + p_sock sock; + lua_newtable(L); lua_settag(L, tags->table); + lua_pushstring(L, P_SOCK); + sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); + if (!sock) return NULL; + lua_settag(L, tags->client); + lua_settable(L, -3); + sock->sock = -1; + sock->is_connected = 0; + sock->tm_block = -1; + sock->tm_return = -1; + sock->bf_first = sock->bf_last = 0; + for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { + lua_pushstring(L, funcs[i].name); + lua_pushusertag(L, sock, tags->client); + lua_pushcclosure(L, funcs[i].func, 1); + lua_settable(L, -3); + } + return sock; +} + +/*-------------------------------------------------------------------------*\ +* Creates a t_sock structure with default values for a server sock. +* Pushes the Lua table with sock fields and appropriate methods +* Input +* tags: tags structure +* Returns +* pointer to allocated t_sock structure, NULL in case of error +\*-------------------------------------------------------------------------*/ +static p_sock push_servertable(lua_State *L, p_tags tags) +{ + static struct luaL_reg funcs[] = { + {"close", table_close}, + {"getsockname", table_getsockname}, + {"poll", table_poll}, + {"timeout", table_timeout}, + }; + unsigned int i; + p_sock sock; + lua_newtable(L); lua_settag(L, tags->table); + lua_pushstring(L, P_SOCK); + sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); + if (!sock) return NULL; + lua_settag(L, tags->server); + lua_settable(L, -3); + sock->sock = INVALID_SOCKET; + sock->tm_block = -1; + sock->tm_return = -1; + sock->bf_first = sock->bf_last = 0; + for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { + lua_pushstring(L, funcs[i].name); + lua_pushusertag(L, sock, tags->client); + lua_pushcclosure(L, funcs[i].func, 1); + lua_settable(L, -3); + } + /* the accept method is different, it needs the tags closure too */ + lua_pushstring(L, "accept"); + lua_pushuserdata(L, tags); + lua_pushusertag(L, sock, tags->client); + lua_pushcclosure(L, table_tcpaccept, 2); + lua_settable(L, -3); + return sock; +} + +/*-------------------------------------------------------------------------*\ +* Creates a t_sock structure with default values for a udp sock. +* Pushes the Lua table with sock fields and appropriate methods +* Input +* tags: tags structure +* Returns +* pointer to allocated t_sock structure, NULL in case of error +\*-------------------------------------------------------------------------*/ +static p_sock push_udptable(lua_State *L, p_tags tags) +{ + static struct luaL_reg funcs[] = { + {"sendto", table_udpsendto}, + {"setpeername", table_udpsetpeername}, + {"setsockname", table_udpsetsockname}, + {"getpeername", table_getpeername}, + {"getsockname", table_getsockname}, + {"poll", table_poll}, + {"receivefrom", table_udpreceivefrom}, + {"receive", table_udpreceive}, + {"send", table_udpsend}, + {"close", table_close}, + {"timeout", table_timeout}, + }; + unsigned int i; + p_sock sock; + lua_newtable(L); lua_settag(L, tags->table); + lua_pushstring(L, P_SOCK); + sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); + if (!sock) { + lua_pushnil(L); + lua_pushstring(L, "out of memory"); + return NULL; + } + lua_settag(L, tags->udp); + lua_settable(L, -3); + sock->sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock->sock == INVALID_SOCKET) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror()); + return NULL; + } + sock->is_connected = 0; + sock->tm_block = -1; + sock->tm_return = -1; + sock->bf_first = sock->bf_last = 0; + for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { + lua_pushstring(L, funcs[i].name); + lua_pushusertag(L, sock, tags->udp); + lua_pushcclosure(L, funcs[i].func, 1); + lua_settable(L, -3); + } + return sock; +} + +/*-------------------------------------------------------------------------*\ +* Passes all resolver information to Lua as a table +* Input +* hp: hostent structure returned by resolver +\*-------------------------------------------------------------------------*/ +static void push_resolved(lua_State *L, struct hostent *hp) +{ + char **alias; + struct in_addr **addr; + int i, resolved; + + lua_newtable(L); resolved = lua_gettop(L); + + lua_pushstring(L, "name"); + lua_pushstring(L, hp->h_name); + lua_settable(L, resolved); + + lua_pushstring(L, "ip"); + lua_pushstring(L, "alias"); + + i = 1; + alias = hp->h_aliases; + lua_newtable(L); + while (*alias) { + lua_pushnumber(L, i); + lua_pushstring(L, *alias); + lua_settable(L, -3); + i++; alias++; + } + lua_settable(L, resolved); + + i = 1; + lua_newtable(L); + addr = (struct in_addr **) hp->h_addr_list; + while (*addr) { + lua_pushnumber(L, i); + lua_pushstring(L, inet_ntoa(**addr)); + lua_settable(L, -3); + i++; addr++; + } + lua_settable(L, resolved); +} + /*-------------------------------------------------------------------------*\ * Passes an error code to Lua. The NET_DONE error is translated to nil. * Input @@ -1205,73 +1841,78 @@ int global_close(lua_State *L) \*-------------------------------------------------------------------------*/ static void push_error(lua_State *L, int err) { - switch (err) { - case NET_DONE: - lua_pushnil(L); - break; - case NET_TIMEOUT: - lua_pushstring(L, "timeout"); - break; - case NET_CLOSED: - lua_pushstring(L, "closed"); - break; - } + switch (err) { + case NET_DONE: + lua_pushnil(L); + break; + case NET_TIMEOUT: + lua_pushstring(L, "timeout"); + break; + case NET_CLOSED: + lua_pushstring(L, "closed"); + break; + case NET_REFUSED: + lua_pushstring(L, "refused"); + break; + } } static p_tags pop_tags(lua_State *L) { - p_tags tags = (p_tags) lua_touserdata(L, -1); - if (!tags) lua_error(L, "invalid closure! (probably misuse of library)"); - lua_pop(L, 1); - return tags; + p_tags tags = (p_tags) lua_touserdata(L, -1); + if (!tags) lua_error(L, "invalid closure! (probably misuse of library)"); + lua_pop(L, 1); + return tags; } static p_sock pop_sock(lua_State *L) { - p_sock sock = (p_sock) lua_touserdata(L, -1); - if (!sock) lua_error(L, "invalid socket object"); - if (sock->sock < 0) lua_error(L, "operation on closed socket"); - lua_pop(L, 1); - return sock; + p_sock sock = (p_sock) lua_touserdata(L, -1); + if (!sock) lua_error(L, "invalid socket object"); + if (sock->sock == INVALID_SOCKET) + lua_error(L, "operation on closed socket"); + lua_pop(L, 1); + return sock; } -static p_sock get_selfsock(lua_State *L, p_tags tags) +static p_sock get_selfsock(lua_State *L, p_tags tags, int *tag) { - p_sock sock; - if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); - lua_pushstring(L, P_SOCK); - lua_gettable(L, 1); - sock = lua_touserdata(L, -1); - if (!sock) lua_error(L, "invalid socket object"); - lua_pop(L, 1); - return sock; + p_sock sock; + if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); + lua_pushstring(L, P_SOCK); + lua_gettable(L, 1); + sock = lua_touserdata(L, -1); + if (!sock) lua_error(L, "invalid socket object"); + if (tag) *tag = lua_tag(L, -1); + lua_pop(L, 1); + return sock; } #ifndef LUASOCKET_NOGLOBALS -static p_sock get_selfclientsock(lua_State *L, p_tags tags) +static p_sock get_selfudpsock(lua_State *L, p_tags tags) { - p_sock sock; - if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); - lua_pushstring(L, P_SOCK); - lua_gettable(L, 1); - sock = lua_touserdata(L, -1); - if (!sock || lua_tag(L, -1) != tags->client) - lua_error(L, "client socket expected"); - lua_pop(L, 1); - return sock; + p_sock sock; + if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); + lua_pushstring(L, P_SOCK); + lua_gettable(L, 1); + sock = lua_touserdata(L, -1); + if (!sock || lua_tag(L, -1) != tags->udp) + lua_error(L, "udp socket expected"); + lua_pop(L, 1); + return sock; } static p_sock get_selfserversock(lua_State *L, p_tags tags) { - p_sock sock; - if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); - lua_pushstring(L, P_SOCK); - lua_gettable(L, 1); - sock = lua_touserdata(L, -1); - if (!sock || lua_tag(L, -1) != tags->server) - lua_error(L, "server socket expected"); - lua_pop(L, 1); - return sock; + p_sock sock; + if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); + lua_pushstring(L, P_SOCK); + lua_gettable(L, 1); + sock = lua_touserdata(L, -1); + if (!sock || lua_tag(L, -1) != tags->server) + lua_error(L, "server socket expected"); + lua_pop(L, 1); + return sock; } #endif @@ -1286,18 +1927,19 @@ static p_sock get_selfserversock(lua_State *L, p_tags tags) \*-------------------------------------------------------------------------*/ static int winsock_open(void) { - WORD wVersionRequested;WSADATA wsaData;int err; - wVersionRequested = MAKEWORD( 2, 0 ); - err = WSAStartup( wVersionRequested, &wsaData ); - if ( err != 0 ) { - return 0; - } - if ( LOBYTE( wsaData.wVersion ) != 2 || - HIBYTE( wsaData.wVersion ) != 0 ) { - WSACleanup( ); - return 0; - } - return 1; + WORD wVersionRequested; + WSADATA wsaData; + int err; + wVersionRequested = MAKEWORD(2, 0); + err = WSAStartup(wVersionRequested, &wsaData ); + if (err != 0) { + return 0; + } + if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0) { + WSACleanup(); + return 0; + } + return 1; } /*-------------------------------------------------------------------------*\ @@ -1305,8 +1947,8 @@ static int winsock_open(void) \*-------------------------------------------------------------------------*/ static void set_blocking(p_sock sock) { - u_long argp = 0; - ioctlsocket(sock->sock, FIONBIO, &argp); + u_long argp = 0; + ioctlsocket(sock->sock, FIONBIO, &argp); } /*-------------------------------------------------------------------------*\ @@ -1314,8 +1956,8 @@ static void set_blocking(p_sock sock) \*-------------------------------------------------------------------------*/ static void set_nonblocking(p_sock sock) { - u_long argp = 1; - ioctlsocket(sock->sock, FIONBIO, &argp); + u_long argp = 1; + ioctlsocket(sock->sock, FIONBIO, &argp); } /*-------------------------------------------------------------------------*\ @@ -1323,27 +1965,27 @@ static void set_nonblocking(p_sock sock) \*-------------------------------------------------------------------------*/ static char *host_strerror(void) { - switch (WSAGetLastError()) { - case HOST_NOT_FOUND: return "host not found"; - case NO_ADDRESS: return "unable to resolve host name"; - case NO_RECOVERY: return "name server error"; - case TRY_AGAIN: return "name server unavailable, try again later."; - default: return "unknown error"; - } + switch (WSAGetLastError()) { + case HOST_NOT_FOUND: return "host not found"; + case NO_ADDRESS: return "unable to resolve host name"; + case NO_RECOVERY: return "name server error"; + case TRY_AGAIN: return "name server unavailable, try again later."; + default: return "unknown error"; + } } /*-------------------------------------------------------------------------*\ * Returns a string describing the last socket manipulation error. \*-------------------------------------------------------------------------*/ -static char *sock_strerror(void) +static char *socket_strerror(void) { - switch (WSAGetLastError()) { - case WSANOTINITIALISED: return "not initialized"; - case WSAENETDOWN: return "network is down"; - case WSAEMFILE: return "descriptor table is full"; - case WSAENOBUFS: return "insufficient buffer space"; - default: return "unknown error"; - } + switch (WSAGetLastError()) { + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAEMFILE: return "descriptor table is full"; + case WSAENOBUFS: return "insufficient buffer space"; + default: return "unknown error"; + } } /*-------------------------------------------------------------------------*\ @@ -1351,16 +1993,16 @@ static char *sock_strerror(void) \*-------------------------------------------------------------------------*/ static char *bind_strerror(void) { - switch (WSAGetLastError()) { - case WSANOTINITIALISED: return "not initialized"; - case WSAENETDOWN: return "network is down"; - case WSAEADDRINUSE: return "address already in use"; - case WSAEINVAL: return "socket already bound"; - case WSAENOBUFS: return "too many connections"; - case WSAEFAULT: return "invalid address"; - case WSAENOTSOCK: return "not a socket descriptor"; - default: return "unknown error"; - } + switch (WSAGetLastError()) { + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAEADDRINUSE: return "address already in use"; + case WSAEINVAL: return "socket already bound"; + case WSAENOBUFS: return "too many connections"; + case WSAEFAULT: return "invalid address"; + case WSAENOTSOCK: return "not a socket descriptor"; + default: return "unknown error"; + } } /*-------------------------------------------------------------------------*\ @@ -1368,15 +2010,15 @@ static char *bind_strerror(void) \*-------------------------------------------------------------------------*/ static char *connect_strerror(void) { - switch (WSAGetLastError()) { - case WSANOTINITIALISED: return "not initialized"; - case WSAENETDOWN: return "network is down"; - case WSAEADDRINUSE: return "address already in use"; - case WSAEADDRNOTAVAIL: return "address unavailable"; - case WSAECONNREFUSED: return "connection refused"; - case WSAENETUNREACH: return "network is unreachable"; - default: return "unknown error"; - } + switch (WSAGetLastError()) { + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAEADDRINUSE: return "address already in use"; + case WSAEADDRNOTAVAIL: return "address unavailable"; + case WSAECONNREFUSED: return "connection refused"; + case WSAENETUNREACH: return "network is unreachable"; + default: return "unknown error"; + } } #else @@ -1388,9 +2030,9 @@ static char *connect_strerror(void) \*-------------------------------------------------------------------------*/ static void set_blocking(p_sock sock) { - int flags = fcntl(sock->sock, F_GETFL, 0); - flags &= (~(O_NONBLOCK)); - fcntl(sock->sock, F_SETFL, flags); + int flags = fcntl(sock->sock, F_GETFL, 0); + flags &= (~(O_NONBLOCK)); + fcntl(sock->sock, F_SETFL, flags); } /*-------------------------------------------------------------------------*\ @@ -1398,9 +2040,9 @@ static void set_blocking(p_sock sock) \*-------------------------------------------------------------------------*/ static void set_nonblocking(p_sock sock) { - int flags = fcntl(sock->sock, F_GETFL, 0); - flags |= O_NONBLOCK; - fcntl(sock->sock, F_SETFL, flags); + int flags = fcntl(sock->sock, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(sock->sock, F_SETFL, flags); } /*-------------------------------------------------------------------------*\ @@ -1408,27 +2050,27 @@ static void set_nonblocking(p_sock sock) \*-------------------------------------------------------------------------*/ static char *host_strerror(void) { - switch (h_errno) { - case HOST_NOT_FOUND: return "host not found"; - case NO_ADDRESS: return "unable to resolve host name"; - case NO_RECOVERY: return "name server error"; - case TRY_AGAIN: return "name server unavailable, try again later"; - default: return "unknown error"; - } + switch (h_errno) { + case HOST_NOT_FOUND: return "host not found"; + case NO_ADDRESS: return "unable to resolve host name"; + case NO_RECOVERY: return "name server error"; + case TRY_AGAIN: return "name server unavailable, try again later"; + default: return "unknown error"; + } } /*-------------------------------------------------------------------------*\ * Returns a string describing the last socket manipulation error. \*-------------------------------------------------------------------------*/ -static char *sock_strerror(void) +static char *socket_strerror(void) { - switch (errno) { - case EACCES: return "access denied"; - case EMFILE: return "descriptor table is full"; - case ENFILE: return "too many open files"; - case ENOBUFS: return "insuffucient buffer space"; - default: return "unknown error"; - } + switch (errno) { + case EACCES: return "access denied"; + case EMFILE: return "descriptor table is full"; + case ENFILE: return "too many open files"; + case ENOBUFS: return "insuffucient buffer space"; + default: return "unknown error"; + } } /*-------------------------------------------------------------------------*\ @@ -1436,16 +2078,16 @@ static char *sock_strerror(void) \*-------------------------------------------------------------------------*/ static char *bind_strerror(void) { - switch (errno) { - case EBADF: return "invalid descriptor"; - case EINVAL: return "socket already bound"; - case EACCES: return "access denied"; - case ENOTSOCK: return "not a socket descriptor"; - case EADDRINUSE: return "address already in use"; - case EADDRNOTAVAIL: return "address unavailable"; - case ENOMEM: return "out of memory"; - default: return "unknown error"; - } + switch (errno) { + case EBADF: return "invalid descriptor"; + case EINVAL: return "socket already bound"; + case EACCES: return "access denied"; + case ENOTSOCK: return "not a socket descriptor"; + case EADDRINUSE: return "address already in use"; + case EADDRNOTAVAIL: return "address unavailable"; + case ENOMEM: return "out of memory"; + default: return "unknown error"; + } } /*-------------------------------------------------------------------------*\ @@ -1453,16 +2095,43 @@ static char *bind_strerror(void) \*-------------------------------------------------------------------------*/ static char *connect_strerror(void) { - switch (errno) { - case EBADF: return "invalid descriptor"; - case ENOTSOCK: return "not a socket descriptor"; - case EADDRNOTAVAIL: return "address not availabe"; - case ETIMEDOUT: return "connection timed out"; - case ECONNREFUSED: return "connection refused"; - case EACCES: return "access denied"; - case ENETUNREACH: return "network is unreachable"; - case EADDRINUSE: return "address already in use"; - default: return "unknown error"; - } + switch (errno) { + case EBADF: return "invalid descriptor"; + case ENOTSOCK: return "not a socket descriptor"; + case EADDRNOTAVAIL: return "address not availabe"; + case ETIMEDOUT: return "connection timed out"; + case ECONNREFUSED: return "connection refused"; + case EACCES: return "access denied"; + case ENETUNREACH: return "network is unreachable"; + case EADDRINUSE: return "address already in use"; + default: return "unknown error"; + } } #endif + +/*-------------------------------------------------------------------------*\ +* Some systems do not provide this so that we provide our own. It's not +* marvelously fast, but it works just fine. +\*-------------------------------------------------------------------------*/ +#ifdef LUASOCKET_ATON +static int inet_aton(const char *cp, struct in_addr *inp) +{ + unsigned int a = 0, b = 0, c = 0, d = 0; + int n = 0, r; + unsigned long int addr = 0; + r = sscanf(cp, "%u.%u.%u.%u%n", &a, &b, &c, &d, &n); + if (r == 0 || n == 0) return 0; + cp += n; + if (*cp) return 0; + if (a > 255 || b > 255 || c > 255 || d > 255) return 0; + if (inp) { + addr += a; addr <<= 8; + addr += b; addr <<= 8; + addr += c; addr <<= 8; + addr += d; + inp->s_addr = htonl(addr); + } + return 1; +} +#endif + From 03e063c21eb153197d56cc82fa0356a15d7a467f Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 25 Jan 2001 21:56:01 +0000 Subject: [PATCH 011/483] Updated for LuaSocket 1.2 Added description for daytimeclnt.lua, echoclnt.lua, echosrvr.lua and tftpclnt.lua. --- samples/README | 47 +++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/samples/README b/samples/README index b891547..5e7b338 100644 --- a/samples/README +++ b/samples/README @@ -1,32 +1,35 @@ -This directory contains some sample programs using LuaSocket as -well as the automatic tests used to make sure the library is -working properly. +This directory contains some sample programs using LuaSocket. This code +is not supported. -The files provided are: + listener.lua -- socket to stdout + talker.lua -- stdin to socket - testsrvr.lua -- test server - testclnt.lua -- test client - testcmd.lua -- test command definitions +listener.lua and talker.lua are about the simplest applications you can +write using LuaSocket. Run 'luasocket listen.lua' and 'luasocket +talk.lua' on different terminals. Whatever you type on talk.lua will be +printed by listen.lua. -To run the automatic tests on your system, make sure to compile -the library with _DEBUG defined (check makefile) and then open two -terminals. Run 'luasocket testsrvr.lua' on one of them and -'luasocket testclnt.lua' on the other. The programs should start -talking to each other. + dict.lua -- dict client - listener.lua -- echo server - talker.lua -- echo tester +The dict.lua module is a cool simple client for the DICT protocol, +written by Luiz Henrique Figueiredo. Just run it and enter a few words +to see it working. -listener.lua and talker.lua are about the simplest applications -you can write using LuaSocket. Run 'luasocket listen.lua' and -'luasocket talk.lua' on different terminals. Whatever you type on -talk.lua will be printed by listen.lua. + daytimeclnt.lua -- day time client - dict.lua -- dict client +Just run the program to retrieve the hour and date in readable form from +any server running an UDP daytime daemon. -The dict.lua module is a cool simple client for the DICT protocol, -written by Luiz Henrique Figueiredo. Just run it and enter a few -words to see it working. + echoclnt.lua -- UDP echo client + echosrvr.lua -- UDP echo server + +These are a UDP echo client/server pair. They work with other client and +servers as well. + + tftpclnt.lua -- Trivial FTP client + +This module implements file retrieval by the TFTP protocol. Its main use +is to test the UDP code, but someone might find it usefull. Good luck, Diego. From 68f51243b38bf1e32d471e9cc9ddf12a25110e80 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 25 Jan 2001 21:57:07 +0000 Subject: [PATCH 012/483] Parameter passing updated. --- samples/listener.lua | 4 ++-- samples/talker.lua | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/listener.lua b/samples/listener.lua index a47d9a3..5e1eb02 100644 --- a/samples/listener.lua +++ b/samples/listener.lua @@ -1,5 +1,5 @@ -host = "localhost" -port = 8080 +host = host or "localhost" +port = host or 8080 if arg then host = arg[1] or host port = arg[2] or port diff --git a/samples/talker.lua b/samples/talker.lua index b3313e6..d66cf66 100644 --- a/samples/talker.lua +++ b/samples/talker.lua @@ -1,5 +1,5 @@ -host = "localhost" -port = 8080 +host = host or "localhost" +port = port or 8080 if arg then host = arg[1] or host port = arg[2] or port From 7096b8df82eebfe857e0043bc8a853353bd78480 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 25 Jan 2001 21:59:39 +0000 Subject: [PATCH 013/483] Initial revision --- samples/daytimeclnt.lua | 14 +++++++ samples/echoclnt.lua | 21 ++++++++++ samples/echosrvr.lua | 22 +++++++++++ test/ftptest.lua | 33 ++++++++++++++++ test/httptest.lua | 85 +++++++++++++++++++++++++++++++++++++++++ test/tftptest.lua | 16 ++++++++ 6 files changed, 191 insertions(+) create mode 100644 samples/daytimeclnt.lua create mode 100644 samples/echoclnt.lua create mode 100644 samples/echosrvr.lua create mode 100644 test/ftptest.lua create mode 100644 test/httptest.lua create mode 100644 test/tftptest.lua diff --git a/samples/daytimeclnt.lua b/samples/daytimeclnt.lua new file mode 100644 index 0000000..94e03d3 --- /dev/null +++ b/samples/daytimeclnt.lua @@ -0,0 +1,14 @@ +host = host or "127.0.0.1" +port = port or 13 +if arg then + host = arg[1] or host + port = arg[2] or port +end +host = toip(host) +udp = udpsocket() +print("Using host '" ..host.. "' and port " ..port.. "...") +err = sendto(udp, "anything", host, port) +if err then print(err) exit() end +dgram, err = receive(udp) +if not dgram then print(err) exit() end +write(dgram) diff --git a/samples/echoclnt.lua b/samples/echoclnt.lua new file mode 100644 index 0000000..d1c56c7 --- /dev/null +++ b/samples/echoclnt.lua @@ -0,0 +1,21 @@ +host = host or "localhost" +port = port or 7 +if arg then + host = arg[1] or host + port = arg[2] or port +end +host = toip(host) +udp, err = udpsocket() +if not udp then print(err) exit() end +err = setpeername(udp, host, port) +if err then print(err) exit() end +print("Using host '" ..host.. "' and port " ..port.. "...") +while 1 do + line = read() + if not line then exit() end + err = send(udp, line) + if err then print(err) exit() end + dgram, err = receive(udp) + if not dgram then print(err) exit() end + print(dgram) +end diff --git a/samples/echosrvr.lua b/samples/echosrvr.lua new file mode 100644 index 0000000..fe7da06 --- /dev/null +++ b/samples/echosrvr.lua @@ -0,0 +1,22 @@ +host = host or "127.0.0.1" +port = port or 7 +if arg then + host = arg[1] or host + port = arg[2] or port +end +print("Binding to host '" ..host.. "' and port " ..port.. "...") +udp, err = udpsocket() +if not udp then print(err) exit() end +err = setsockname(udp, host, port) +if err then print(err) exit() end +timeout(udp, 5) +ip, port = getsockname(udp) +print("Waiting packets on " .. ip .. ":" .. port .. "...") +while 1 do + dgram, ip, port = receivefrom(udp) + if not dgram then print(ip) + else + print("Echoing from " .. ip .. ":" .. port) + sendto(udp, dgram, ip, port) + end +end diff --git a/test/ftptest.lua b/test/ftptest.lua new file mode 100644 index 0000000..85dc16b --- /dev/null +++ b/test/ftptest.lua @@ -0,0 +1,33 @@ +assert(dofile("../lua/ftp.lua")) +assert(dofile("auxiliar.lua")) + +pdir = "/home/i/diego/public/html/luasocket/test/" +ldir = "/home/luasocket/" +adir = "/home/ftp/test/" + +-- needs an accound luasocket:password +-- and a copy of /home/i/diego/public/html/luasocket/test in ~ftp/test + +print("testing authenticated upload") +bf = readfile(pdir .. "index.html") +e = ftp_put("ftp://luasocket:password@localhost/index.html", bf, "b") +assert(not e, e) +assert(compare(ldir .. "index.html", bf), "files differ") +remove(ldir .. "index.html") + +print("testing authenticated download") +f, e = ftp_get("ftp://luasocket:password@localhost/test/index.html", "b") +assert(f, e) +assert(compare(pdir .. "index.html", f), "files differ") + +print("testing anonymous download") +f, e = ftp_get("ftp://localhost/test/index.html", "b") +assert(f, e) +assert(compare(adir .. "index.html", f), "files differ") + +print("testing directory listing") +f, e = ftp_get("ftp://localhost/test/") +assert(f, e) +assert(f == "index.html\r\n", "files differ") + +print("passed all tests") diff --git a/test/httptest.lua b/test/httptest.lua new file mode 100644 index 0000000..b88475d --- /dev/null +++ b/test/httptest.lua @@ -0,0 +1,85 @@ +-- load http +assert(dofile("../lua/http.lua")) +assert(dofile("../lua/base64.lua")) +assert(dofile("auxiliar.lua")) + +-- needs Alias from /home/i/diego/public/html/luasocket/test to +-- /luasocket-test +-- needs ScriptAlias from /home/i/diego/public/html/luasocket/test/cgi-bin +-- to /luasocket-cgi-bin/ + +function join(s, e) + return tostring(s) .. ":" .. tostring(e) +end + +function status(s) + local code + _,_, code = strfind(s, "(%d%d%d)") + return tonumber(code) +end + +pdir = pdir or "/home/i/diego/public/html/luasocket/test/" +host = host or "localhost" + +print("testing document retrieval") +url = "http://" .. host .. "/luasocket-test/index.html" +f, m, s, e = http_get(url) +assert(f and m and s and not e, join(s, e)) +assert(compare(pdir .. "index.html", f), "documents differ") + +print("testing HTTP redirection") +url = "http://" .. host .. "/luasocket-test" +f, m, s, e = http_get(url) +assert(f and m and s and not e, join(s, e)) +assert(compare(pdir .. "index.html", f), "documents differ") + +print("testing cgi output retrieval (probably chunked...)") +url = "http://" .. host .. "/luasocket-cgi-bin/cat-index-html" +f, m, s, e = http_get(url) +assert(f and m and s and not e, join(s, e)) +assert(compare(pdir .. "index.html", f), "documents differ") + +print("testing post method") +url = "http://" .. host .. "/luasocket-cgi-bin/cat" +rf = strrep("!@#$!@#%", 80000) +f, m, s, e = http_post(url, rf) +assert(f and m and s and not e) +assert(rf == f, "files differ") + +print("testing automatic auth failure") +url = "http://really:wrong@" .. host .. "/luasocket-test/auth/index.html" +f, m, s, e = http_get(url) +assert(f and m and s and not e and status(s) == 401) + +write("testing host not found: ") +url = "http://wronghost/luasocket-test/index.html" +f, m, s, e = http_get(url) +assert(not f and not m and not s and e) +print(e) + +write("testing auth failure: ") +url = "http://" .. host .. "/luasocket-test/auth/index.html" +f, m, s, e = http_get(url) +assert(f and m and s and not e and status(s) == 401) +print(s) + +write("testing document not found: ") +url = "http://" .. host .. "/luasocket-test/wrongdocument.html" +f, m, s, e = http_get(url) +assert(f and m and s and not e and status(s) == 404) +print(s) + +print("testing manual auth") +url = "http://" .. host .. "/luasocket-test/auth/index.html" +h = {authorization = "Basic " .. base64("luasocket:password")} +f, m, s, e = http_get(url, h) +assert(f and m and s and not e, join(s, e)) +assert(compare(pdir .. "auth/index.html", f), "documents differ") + +print("testing automatic auth") +url = "http://luasocket:password@" .. host .. "/luasocket-test/auth/index.html" +f, m, s, e = http_get(url) +assert(f and m and s and not e, join(s, e)) +assert(compare(pdir .. "auth/index.html", f), "documents differ") + +print("passed all tests") diff --git a/test/tftptest.lua b/test/tftptest.lua new file mode 100644 index 0000000..7fb8253 --- /dev/null +++ b/test/tftptest.lua @@ -0,0 +1,16 @@ +-- load tftpclng.lua +assert(dofile("../examples/tftpclnt.lua")) +assert(dofile("auxiliar.lua")) + +-- needs tftp server running on localhost, with root pointing to +-- /home/i/diego/public/html/luasocket/test + +host = host or "localhost" +print("downloading") +err = tftp_get(host, 69, "test/index.html") +assert(not err, err) +original = readfile("/home/i/diego/public/html/luasocket/test/index.html") +retrieved = readfile("index.html") +remove("index.html") +assert(original == retrieved, "files differ!") +print("passed") From 2bb209ab9eb746fdfb9b8f5c83eee12b40fc211e Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 25 Jan 2001 21:59:59 +0000 Subject: [PATCH 014/483] Updated for LuaSocket 1.2. More tests added. --- test/testclnt.lua | 787 +++++++++++++++++++++++++--------------------- 1 file changed, 428 insertions(+), 359 deletions(-) diff --git a/test/testclnt.lua b/test/testclnt.lua index c1c22bd..8a36512 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -1,359 +1,428 @@ ------------------------------------------------------------------------------ --- LuaSocket automated test module --- client.lua --- This is the client module. It connects with the server module and executes --- all tests. ------------------------------------------------------------------------------ - ------------------------------------------------------------------------------ --- Prints a header to separate the test phases --- Input --- test: test phase name ------------------------------------------------------------------------------ -function new_test(test) - write("----------------------------------------------\n", - test, "\n", - "----------------------------------------------\n") -end - ------------------------------------------------------------------------------ --- Read command definitions and stablish control connection ------------------------------------------------------------------------------ -new_test("initializing...") -dofile("command.lua") -test_debug_mode() -while control == nil do - print("client: trying control connection...") - control, err = connect(HOST, PORT) - if control then - print("client: control connection stablished!") - else - sleep(2) - end -end - ------------------------------------------------------------------------------ --- Make sure server is ready for data transmission ------------------------------------------------------------------------------ -function sync() - send_command(SYNC) - get_command() -end - ------------------------------------------------------------------------------ --- Close and reopen data connection, to get rid of any unread blocks ------------------------------------------------------------------------------ -function reconnect() - if data then - data:close() - send_command(CLOSE) - data = nil - end - while data == nil do - send_command(CONNECT) - data = connect(HOST, PORT) - if not data then - print("client: waiting for data connection.") - sleep(1) - end - end - sync() -end - ------------------------------------------------------------------------------ --- Tests the command connection ------------------------------------------------------------------------------ -function test_command(cmd, par) - local cmd_back, par_back - reconnect() - send_command(COMMAND) - write("testing command ") - print_command(cmd, par) - send_command(cmd, par) - cmd_back, par_back = get_command() - if cmd_back ~= cmd or par_back ~= par then - fail(cmd) - else - pass() - end -end - ------------------------------------------------------------------------------ --- Tests ASCII line transmission --- Input --- len: length of line to be tested ------------------------------------------------------------------------------ -function test_asciiline(len) - local str, str10, back, err - reconnect() - send_command(ECHO_LINE) - str = strrep("x", mod(len, 10)) - str10 = strrep("aZb.c#dAe?", floor(len/10)) - str = str .. str10 - write("testing ", len, " byte(s) line\n") - err = data:send(str, "\n") - if err then fail(err) end - back, err = data:receive() - if err then fail(err) end - if back == str then pass("lines match") - else fail("lines don't match") end -end - ------------------------------------------------------------------------------ --- Tests closed connection detection ------------------------------------------------------------------------------ -function test_closed() - local str = "This is our little test line" - local len = strlen(str) - local back, err, total - reconnect() - print("testing close while reading line") - send_command(ECHO_BLOCK, len) - data:send(str) - send_command(CLOSE) - -- try to get a line - back, err = data:receive() - if not err then fail("shold have gotten 'closed'.") - elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") - elseif str ~= back then fail("didn't receive what i should 'closed'.") - else pass("rightfull 'closed' received") end - reconnect() - print("testing close while reading block") - send_command(ECHO_BLOCK, len) - data:send(str) - send_command(CLOSE) - -- try to get a line - back, err = data:receive(2*len) - if not err then fail("shold have gotten 'closed'.") - elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") - elseif str ~= back then fail("didn't receive what I should.") - else pass("rightfull 'closed' received") end -end - ------------------------------------------------------------------------------ --- Tests binary line transmission --- Input --- len: length of line to be tested ------------------------------------------------------------------------------ -function test_rawline(len) - local str, str10, back, err - reconnect() - send_command(ECHO_LINE) - str = strrep(strchar(47), mod(len, 10)) - str10 = strrep(strchar(120,21,77,4,5,0,7,36,44,100), floor(len/10)) - str = str .. str10 - write("testing ", len, " byte(s) line\n") - err = data:send(str, "\n") - if err then fail(err) end - back, err = data:receive() - if err then fail(err) end - if back == str then pass("lines match") - else fail("lines don't match") end -end - ------------------------------------------------------------------------------ --- Tests block transmission --- Input --- len: length of block to be tested ------------------------------------------------------------------------------ -function test_block(len) - local half = floor(len/2) - local s1, s2, back, err - reconnect() - send_command(ECHO_BLOCK, len) - write("testing ", len, " byte(s) block\n") - s1 = strrep("x", half) - err = data:send(s1) - if err then fail(err) end - sleep(1) - s2 = strrep("y", len-half) - err = data:send(s2) - if err then fail(err) end - back, err = data:receive(len) - if err then fail(err) end - if back == s1..s2 then pass("blocks match") - else fail("blocks don't match") end -end - ------------------------------------------------------------------------------ --- Tests if return-timeout was respected --- delta: time elapsed during transfer --- t: timeout value --- err: error code returned by I/O operation ------------------------------------------------------------------------------ -function returntimed_out(delta, t, err) - if err == "timeout" then - if delta + 0.1 >= t then - pass("got right timeout") - return 1 - else - fail("shouldn't have gotten timeout") - end - elseif delta > t then - fail("should have gotten timeout") - end -end - ------------------------------------------------------------------------------ --- Tests if return-timeout was respected --- delta: time elapsed during transfer --- t: timeout value --- err: error code returned by I/O operation --- o: operation being executed ------------------------------------------------------------------------------ -function blockedtimed_out(t, s, err, o) - if err == "timeout" then - if s >= t then - pass("got right forced timeout") - return 1 - else - pass("got natural cause timeout (may be wrong)") - return 1 - end - elseif s > t then - if o == "send" then - pass("must have been buffered (may be wrong)") - else - fail("should have gotten timeout") - end - end -end - ------------------------------------------------------------------------------ --- Tests blocked-timeout conformance --- Input --- len: length of block to be tested --- t: timeout value --- s: server sleep between transfers ------------------------------------------------------------------------------ -function test_blockedtimeout(len, t, s) - local str, err, back, total - reconnect() - send_command(RECEIVE_BLOCK, len) - send_command(SLEEP, s) - send_command(RECEIVE_BLOCK, len) - write("testing ", len, " bytes, ", t, - "s block timeout, ", s, "s sleep\n") - data:timeout(t) - str = strrep("a", 2*len) - err, total = data:send(str) - if blockedtimed_out(t, s, err, "send") then return end - if err then fail(err) end - send_command(SEND_BLOCK) - send_command(SLEEP, s) - send_command(SEND_BLOCK) - back, err = data:receive(2*len) - if blockedtimed_out(t, s, err, "receive") then return end - if err then fail(err) end - if back == str then pass("blocks match") - else fail("blocks don't match") end -end - ------------------------------------------------------------------------------ --- Tests return-timeout conformance --- Input --- len: length of block to be tested --- t: timeout value --- s: server sleep between transfers ------------------------------------------------------------------------------ -function test_returntimeout(len, t, s) - local str, err, back, delta, total - reconnect() - send_command(RECEIVE_BLOCK, len) - send_command(SLEEP, s) - send_command(RECEIVE_BLOCK, len) - write("testing ", len, " bytes, ", t, - "s return timeout, ", s, "s sleep\n") - data:timeout(t, "return") - str = strrep("a", 2*len) - err, total, delta = data:send(str) - print("delta: " .. delta) - if returntimed_out(delta, t, err) then return end - if err then fail(err) end - send_command(SEND_BLOCK) - send_command(SLEEP, s) - send_command(SEND_BLOCK) - back, err, delta = data:receive(2*len) - print("delta: " .. delta) - if returntimed_out(delta, t, err) then return end - if err then fail(err) end - if back == str then pass("blocks match") - else fail("blocks don't match") end -end - ------------------------------------------------------------------------------ --- Execute all tests ------------------------------------------------------------------------------ -new_test("control connection test") -test_command(EXIT) -test_command(CONNECT) -test_command(CLOSE) -test_command(ECHO_BLOCK, 12234) -test_command(SLEEP, 1111) -test_command(ECHO_LINE) - -new_test("connection close test") -test_closed() - -new_test("binary string test") -test_rawline(1) -test_rawline(17) -test_rawline(200) -test_rawline(3000) -test_rawline(8000) -test_rawline(40000) - -new_test("blocking transfer test") -test_block(1) -test_block(17) -test_block(200) -test_block(3000) -test_block(80000) -test_block(800000) - -new_test("non-blocking transfer test") --- the value is not important, we only want --- to test non-blockin I/O anyways -data:timeout(200) -test_block(1) -test_block(17) -test_block(200) -test_block(3000) -test_block(80000) -test_block(800000) -test_block(8000000) - -new_test("character string test") -test_asciiline(1) -test_asciiline(17) -test_asciiline(200) -test_asciiline(3000) -test_asciiline(8000) -test_asciiline(40000) - -new_test("return timeout test") -test_returntimeout(80, .5, 1) -test_returntimeout(80, 1, 0.5) -test_returntimeout(8000, .5, 0) -test_returntimeout(80000, .5, 0) -test_returntimeout(800000, .5, 0) - -new_test("blocked timeout test") -test_blockedtimeout(80, .5, 1) -test_blockedtimeout(80, 1, 1) -test_blockedtimeout(80, 1.5, 1) -test_blockedtimeout(800, 1, 0) -test_blockedtimeout(8000, 1, 0) -test_blockedtimeout(80000, 1, 0) -test_blockedtimeout(800000, 1, 0) - ------------------------------------------------------------------------------ --- Close connection and exit server. We are done. ------------------------------------------------------------------------------ -new_test("the library has passed all tests") -print("client: closing connection with server") -send_command(CLOSE) -send_command(EXIT) -control:close() -print("client: exiting...") -exit() +----------------------------------------------------------------------------- +-- LuaSocket automated test module +-- client.lua +-- This is the client module. It connects with the server module and executes +-- all tests. +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Prints a header to separate the test phases +-- Input +-- test: test phase name +----------------------------------------------------------------------------- +function new_test(test) + write("----------------------------------------------\n", + test, "\n", + "----------------------------------------------\n") +end + +----------------------------------------------------------------------------- +-- Get host and port from command line +----------------------------------------------------------------------------- +HOST = "127.0.0.1" +PORT = 2020 +if arg then + HOST = arg[1] or HOST + PORT = arg[2] or PORT +end + +----------------------------------------------------------------------------- +-- Read command definitions +----------------------------------------------------------------------------- +assert(dofile("testcmd.lua")) +test_debug_mode() + +----------------------------------------------------------------------------- +-- Start control connection +----------------------------------------------------------------------------- +new_test("initializing...") +while control == nil do + print("client: trying control connection...") + control, err = connect(HOST, PORT) + if control then + print("client: control connection stablished!") + else + sleep(2) + end +end + +----------------------------------------------------------------------------- +-- Make sure server is ready for data transmission +----------------------------------------------------------------------------- +function sync() + send_command(SYNC) + get_command() +end + +----------------------------------------------------------------------------- +-- Close and reopen data connection, to get rid of any unread blocks +----------------------------------------------------------------------------- +function reconnect() + if data then + close(data) + send_command(CLOSE) + data = nil + end + while data == nil do + send_command(CONNECT) + data = connect(HOST, PORT) + if not data then + print("client: waiting for data connection.") + sleep(1) + end + end + sync() +end + +----------------------------------------------------------------------------- +-- Tests the command connection +----------------------------------------------------------------------------- +function test_command(cmd, par) + local cmd_back, par_back + reconnect() + send_command(COMMAND) + write("testing command ") + print_command(cmd, par) + send_command(cmd, par) + cmd_back, par_back = get_command() + if cmd_back ~= cmd or par_back ~= par then + fail(cmd) + else + pass() + end +end + +----------------------------------------------------------------------------- +-- Tests ASCII line transmission +-- Input +-- len: length of line to be tested +----------------------------------------------------------------------------- +function test_asciiline(len) + local str, str10, back, err + reconnect() + send_command(ECHO_LINE) + str = strrep("x", mod(len, 10)) + str10 = strrep("aZb.c#dAe?", floor(len/10)) + str = str .. str10 + write("testing ", len, " byte(s) line\n") + err = send(data, str, "\n") + if err then fail(err) end + back, err = receive(data) + if err then fail(err) end + if back == str then pass("lines match") + else fail("lines don't match") end +end + +----------------------------------------------------------------------------- +-- Tests closed connection detection +----------------------------------------------------------------------------- +function test_closed() + local str = "This is our little test line" + local len = strlen(str) + local back, err, total + reconnect() + print("testing close while reading line") + send_command(ECHO_BLOCK, len) + send(data, str) + send_command(CLOSE) + -- try to get a line + back, err = receive(data) + if not err then fail("shold have gotten 'closed'.") + elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") + elseif str ~= back then fail("didn't receive what i should 'closed'.") + else pass("rightfull 'closed' received") end + reconnect() + print("testing close while reading block") + send_command(ECHO_BLOCK, len) + send(data, str) + send_command(CLOSE) + -- try to get a line + back, err = receive(data, 2*len) + if not err then fail("shold have gotten 'closed'.") + elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") + elseif str ~= back then fail("didn't receive what I should.") + else pass("rightfull 'closed' received") end +end + +----------------------------------------------------------------------------- +-- Tests binary line transmission +-- Input +-- len: length of line to be tested +----------------------------------------------------------------------------- +function test_rawline(len) + local str, str10, back, err + reconnect() + send_command(ECHO_LINE) + str = strrep(strchar(47), mod(len, 10)) + str10 = strrep(strchar(120,21,77,4,5,0,7,36,44,100), floor(len/10)) + str = str .. str10 + write("testing ", len, " byte(s) line\n") + err = send(data, str, "\n") + if err then fail(err) end + back, err = receive(data) + if err then fail(err) end + if back == str then pass("lines match") + else fail("lines don't match") end +end + +----------------------------------------------------------------------------- +-- Tests block transmission +-- Input +-- len: length of block to be tested +----------------------------------------------------------------------------- +function test_block(len) + local half = floor(len/2) + local s1, s2, back, err + reconnect() + send_command(ECHO_BLOCK, len) + write("testing ", len, " byte(s) block\n") + s1 = strrep("x", half) + err = send(data, s1) + if err then fail(err) end + s2 = strrep("y", len-half) + err = send(data, s2) + if err then fail(err) end + back, err = receive(data, len) + if err then fail(err) end + if back == s1..s2 then pass("blocks match") + else fail("blocks don't match") end +end + +----------------------------------------------------------------------------- +-- Tests if return-timeout was respected +-- delta: time elapsed during transfer +-- t: timeout value +-- s: time server slept +-- err: error code returned by I/O operation +-- o: operation being executed +----------------------------------------------------------------------------- +function blockedtimed_out(t, s, err, o) + if err == "timeout" then + if s >= t then + pass("got rightfull forced timeout") + return 1 + else + pass("got natural cause timeout") + return 1 + end + elseif 0.9*s > t then + if o == "send" then + pass("must have been buffered (may be wrong)") + else + fail("should have gotten timeout") + end + end +end + +----------------------------------------------------------------------------- +-- Tests blocked-timeout conformance +-- Input +-- len: length of block to be tested +-- t: timeout value +-- s: server sleep between transfers +----------------------------------------------------------------------------- +function test_blockedtimeout(len, t, s) + local str, err, back, total + reconnect() + send_command(RECEIVE_BLOCK, len) + send_command(SLEEP, s) + send_command(RECEIVE_BLOCK, len) + write("testing ", len, " bytes, ", t, + "s block timeout, ", s, "s sleep\n") + timeout(data, t) + str = strrep("a", 2*len) + err, total = send(data, str) + if blockedtimed_out(t, s, err, "send") then return end + if err then fail(err) end + send_command(SEND_BLOCK) + send_command(SLEEP, s) + send_command(SEND_BLOCK) + back, err = receive(data, 2*len) + if blockedtimed_out(t, s, err, "receive") then return end + if err then fail(err) end + if back == str then pass("blocks match") + else fail("blocks don't match") end +end + +----------------------------------------------------------------------------- +-- Tests if return-timeout was respected +-- delta: time elapsed during transfer +-- t: timeout value +-- err: error code returned by I/O operation +----------------------------------------------------------------------------- +function returntimed_out(delta, t, err) + if err == "timeout" then + if 1.1*delta >= t then + pass("got rightfull timeout") + return 1 + else + fail("shouldn't have gotten timeout") + end + elseif 0.9*delta > t then + fail("should have gotten timeout") + end +end + +----------------------------------------------------------------------------- +-- Tests return-timeout conformance +-- Input +-- len: length of block to be tested +-- t: timeout value +-- s: server sleep between transfers +----------------------------------------------------------------------------- +function test_returntimeout(len, t, s) + local str, err, back, delta, total + reconnect() + send_command(RECEIVE_BLOCK, len) + send_command(SLEEP, s) + send_command(RECEIVE_BLOCK, len) + write("testing ", len, " bytes, ", t, + "s return timeout, ", s, "s sleep\n") + timeout(data, t, "return") + str = strrep("a", 2*len) + err, total, delta = send(data, str) + print("sent in " .. delta .. "s") + if returntimed_out(delta, t, err) then return end + if err then fail("unexpected error: " .. err) end + send_command(SEND_BLOCK) + send_command(SLEEP, s) + send_command(SEND_BLOCK) + back, err, delta = receive(data, 2*len) + print("received in " .. delta .. "s") + if returntimed_out(delta, t, err) then return end + if err then fail("unexpected error: " .. err) end + if back == str then pass("blocks match") + else fail("blocks don't match") end +end + +----------------------------------------------------------------------------- +-- Tests return-timeout conformance +----------------------------------------------------------------------------- +function test_patterns() + local dos_line1 = "this the first dos line" + local dos_line2 = "this is another dos line" + local unix_line1 = "this the first unix line" + local unix_line2 = "this is another unix line" + local block = dos_line1 .. "\r\n" .. dos_line2 .. "\r\n" + reconnect() + block = block .. unix_line1 .. "\n" .. unix_line2 .. "\n" + block = block .. block + send_command(ECHO_BLOCK, strlen(block)) + err = send(data, block) + if err then fail(err) end + local back = receive(data, "*l") + if back ~= dos_line1 then fail("'*l' failed") end + back = receive(data, "*l") + if back ~= dos_line2 then fail("'*l' failed") end + back = receive(data, "*lu") + if back ~= unix_line1 then fail("'*lu' failed") end + back = receive(data, "*lu") + if back ~= unix_line2 then fail("'*lu' failed") end + back = receive(data) + if back ~= dos_line1 then fail("default failed") end + back = receive(data) + if back ~= dos_line2 then fail("default failed") end + back = receive(data, "*lu") + if back ~= unix_line1 then fail("'*lu' failed") end + back = receive(data, "*lu") + if back ~= unix_line2 then fail("'*lu' failed") end + pass("line patterns are ok") + send_command(ECHO_BLOCK, strlen(block)) + err = send(data, block) + if err then fail(err) end + back = receive(data, strlen(block)) + if back ~= block then fail("number failed") end + pass("number is ok") + send_command(ECHO_BLOCK, strlen(block)) + send_command(SLEEP, 1) + send_command(CLOSE) + err = send(data, block) + if err then fail(err) end + back = receive(data, "*a") + if back ~= block then fail("'*a' failed") end + pass("'*a' is ok") +end + +----------------------------------------------------------------------------- +-- Execute all tests +----------------------------------------------------------------------------- +start = time() + +new_test("control connection test") +test_command(EXIT) +test_command(CONNECT) +test_command(CLOSE) +test_command(ECHO_BLOCK, 12234) +test_command(SLEEP, 1111) +test_command(ECHO_LINE) + +new_test("connection close test") +test_closed() + +new_test("read pattern test") +test_patterns() + +new_test("character string test") +test_asciiline(1) +test_asciiline(17) +test_asciiline(200) +test_asciiline(3000) +test_asciiline(80000) +test_asciiline(800000) + +new_test("binary string test") +test_rawline(1) +test_rawline(17) +test_rawline(200) +test_rawline(3000) +test_rawline(8000) +test_rawline(80000) +test_rawline(800000) + +new_test("blocking transfer test") +test_block(1) +test_block(17) +test_block(200) +test_block(3000) +test_block(80000) +test_block(800000) + +new_test("non-blocking transfer test") +-- the value is not important, we only want +-- to test non-blockin I/O anyways +timeout(data, 200) +test_block(1) +test_block(17) +test_block(200) +test_block(3000) +test_block(80000) +test_block(800000) + +new_test("blocked timeout test") +test_blockedtimeout(80, .5, 1) +test_blockedtimeout(80, 1, 1) +test_blockedtimeout(80, 1.5, 1) +test_blockedtimeout(800, 1, 0) +test_blockedtimeout(8000, 1, 1.5) +test_blockedtimeout(80000, 1, 0) +test_blockedtimeout(800000, 0.01, 0) + +new_test("return timeout test") +test_returntimeout(80, 1, 0.5) +test_returntimeout(80, 0.5, 1) +test_returntimeout(8000, .5, 1) +test_returntimeout(80000, 1, 0.5) +test_returntimeout(800000, 1, 0.5) + +----------------------------------------------------------------------------- +-- Close connection and exit server. We are done. +----------------------------------------------------------------------------- +print("client: closing connection with server") +send_command(CLOSE) +send_command(EXIT) +close(control) + +new_test("the library has passed all tests") +print(format("time elapsed: %6.2fs", time() - start)) +print("client: exiting...") +exit() From f6b95052256fa9609a8f8a2c8abfe0af1e18706f Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 25 Jan 2001 22:00:18 +0000 Subject: [PATCH 015/483] Updated for LuaSocket 1.2. --- test/testsrvr.lua | 194 +++++++++++++++++++++++++--------------------- 1 file changed, 104 insertions(+), 90 deletions(-) diff --git a/test/testsrvr.lua b/test/testsrvr.lua index 99ecd2a..d69b5ab 100644 --- a/test/testsrvr.lua +++ b/test/testsrvr.lua @@ -1,90 +1,104 @@ ------------------------------------------------------------------------------ --- LuaSocket automated test module --- server.lua --- This is the server module. It's completely controled by the client module --- by the use of a control connection. ------------------------------------------------------------------------------ - ------------------------------------------------------------------------------ --- Read command definitions ------------------------------------------------------------------------------ -dofile("command.lua") -test_debug_mode() - ------------------------------------------------------------------------------ --- Bind to address and wait for control connection ------------------------------------------------------------------------------ -server, err = bind(HOST, PORT) -if not server then - print(err) - exit(1) -end -print("server: waiting for control connection...") -control = server:accept() -print("server: control connection stablished!") - ------------------------------------------------------------------------------ --- Executes a command, detecting any possible failures --- Input --- cmd: command to be executed --- par: command parameters, if needed ------------------------------------------------------------------------------ -function execute_command(cmd, par) - if cmd == CONNECT then - print("server: waiting for data connection...") - data = server:accept() - if not data then - fail("server: unable to start data connection!") - else - print("server: data connection stablished!") - end - elseif cmd == CLOSE then - print("server: closing connection with client...") - if data then - data:close() - data = nil - end - elseif cmd == ECHO_LINE then - str, err = data:receive() - if err then fail("server: " .. err) end - err = data:send(str, "\n") - if err then fail("server: " .. err) end - elseif cmd == ECHO_BLOCK then - str, err = data:receive(par) - if err then fail("server: " .. err) end - err = data:send(str) - if err then fail("server: " .. err) end - elseif cmd == RECEIVE_BLOCK then - str, err = data:receive(par) - elseif cmd == SEND_BLOCK then - err = data:send(str) - elseif cmd == ECHO_TIMEOUT then - str, err = data:receive(par) - if err then fail("server: " .. err) end - err = data:send(str) - if err then fail("server: " .. err) end - elseif cmd == COMMAND then - cmd, par = get_command() - send_command(cmd, par) - elseif cmd == EXIT then - print("server: exiting...") - exit(0) - elseif cmd == SYNC then - print("server: synchronizing...") - send_command(SYNC) - elseif cmd == SLEEP then - print("server: sleeping for " .. par .. " seconds...") - sleep(par) - print("server: woke up!") - end -end - ------------------------------------------------------------------------------ --- Loop forever, accepting and executing commands ------------------------------------------------------------------------------ -while 1 do - cmd, par = get_command() - if not cmd then fail("server: " .. par) end - print_command(cmd, par) - execute_command(cmd, par) -end +----------------------------------------------------------------------------- +-- LuaSocket automated test module +-- server.lua +-- This is the server module. It's completely controled by the client module +-- by the use of a control connection. +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Read command definitions +----------------------------------------------------------------------------- +assert(dofile("testcmd.lua")) +test_debug_mode() + +----------------------------------------------------------------------------- +-- Get host and port from command line +----------------------------------------------------------------------------- +HOST = "localhost" +PORT = 2020 +if arg then + HOST = arg[1] or HOST + PORT = arg[2] or PORT +end + +----------------------------------------------------------------------------- +-- Start control connection +----------------------------------------------------------------------------- +server, err = bind(HOST, PORT) +if not server then + fail(err) + exit(1) +end +print("server: waiting for control connection...") +control = accept(server) +print("server: control connection stablished!") + +----------------------------------------------------------------------------- +-- Executes a command, detecting any possible failures +-- Input +-- cmd: command to be executed +-- par: command parameters, if needed +----------------------------------------------------------------------------- +function execute_command(cmd, par) + if cmd == CONNECT then + print("server: waiting for data connection...") + data = accept(server) + if not data then + fail("server: unable to start data connection!") + else + print("server: data connection stablished!") + end + elseif cmd == CLOSE then + print("server: closing connection with client...") + if data then + close(data) + data = nil + end + elseif cmd == ECHO_LINE then + str, err = receive(data) + if err then fail("server: " .. err) end + err = send(data, str, "\n") + if err then fail("server: " .. err) end + elseif cmd == ECHO_BLOCK then + str, err = receive(data, par) + print(format("server: received %d bytes", strlen(str))) + if err then fail("server: " .. err) end + print(format("server: sending %d bytes", strlen(str))) + err = send(data, str) + if err then fail("server: " .. err) end + elseif cmd == RECEIVE_BLOCK then + str, err = receive(data, par) + print(format("server: received %d bytes", strlen(str))) + elseif cmd == SEND_BLOCK then + print(format("server: sending %d bytes", strlen(str))) + err = send(data, str) + elseif cmd == ECHO_TIMEOUT then + str, err = receive(data, par) + if err then fail("server: " .. err) end + err = send(data, str) + if err then fail("server: " .. err) end + elseif cmd == COMMAND then + cmd, par = get_command() + send_command(cmd, par) + elseif cmd == EXIT then + print("server: exiting...") + exit(0) + elseif cmd == SYNC then + print("server: synchronizing...") + send_command(SYNC) + elseif cmd == SLEEP then + print("server: sleeping for " .. par .. " seconds...") + sleep(par) + print("server: woke up!") + end +end + +----------------------------------------------------------------------------- +-- Loop forever, accepting and executing commands +----------------------------------------------------------------------------- +while 1 do + cmd, par = get_command() + if not cmd then fail("server: " .. par) end + print_command(cmd, par) + execute_command(cmd, par) +end From bee46b39bf5b29e559b2152e4b32d2f37ee71a8e Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 25 Jan 2001 22:01:37 +0000 Subject: [PATCH 016/483] HTTP is now generic, with function http_request. RFC is more strictly followed. --- src/http.lua | 433 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 262 insertions(+), 171 deletions(-) diff --git a/src/http.lua b/src/http.lua index 8f08725..7212728 100644 --- a/src/http.lua +++ b/src/http.lua @@ -1,5 +1,6 @@ ----------------------------------------------------------------------------- --- Simple HTTP/1.1 support for the Lua language using the LuaSocket toolkit. +-- Full HTTP/1.1 client support for the Lua language using the +-- LuaSocket 1.2 toolkit. -- Author: Diego Nehab -- Date: 26/12/2000 -- Conforming to: RFC 2068 @@ -9,165 +10,171 @@ -- Program constants ----------------------------------------------------------------------------- -- connection timeout in seconds -local TIMEOUT = 60 +local TIMEOUT = 60 -- default port for document retrieval local PORT = 80 -- user agent field sent in request -local USERAGENT = "LuaSocket/HTTP 1.0" +local USERAGENT = "LuaSocket 1.2 HTTP 1.1" ----------------------------------------------------------------------------- --- Tries to get a line from the server or close socket if error +-- Tries to get a pattern from the server and closes socket on error -- sock: socket connected to the server +-- pattern: pattern to receive -- Returns --- line: line received or nil in case of error +-- data: line received or nil in case of error -- err: error message if any ----------------------------------------------------------------------------- -local try_getline = function(sock) - line, err = sock:receive() - if err then - sock:close() - return nil, err - end - return line +local try_get = function(...) + local sock = arg[1] + local data, err = call(sock.receive, arg) + if err then + sock:close() + return nil, err + end + return data end ----------------------------------------------------------------------------- --- Tries to send a line to the server or close socket if error +-- Tries to send data to the server and closes socket on error -- sock: socket connected to the server --- line: line to send +-- data: data to send -- Returns --- err: error message if any +-- err: error message if any, nil if successfull ----------------------------------------------------------------------------- -local try_sendline = function(sock, line) - err = sock:send(line) - if err then sock:close() end - return err +local try_send = function(sock, data) + err = sock:send(data) + if err then sock:close() end + return err end ----------------------------------------------------------------------------- --- Retrieves status from http reply +-- Retrieves status code from http status line -- Input --- reply: http reply string +-- line: http status line -- Returns --- status: integer with status code +-- code: integer with status code ----------------------------------------------------------------------------- -local get_status = function(reply) - local _,_, status = strfind(reply, " (%d%d%d) ") - return tonumber(status) +local get_statuscode = function(line) + local _,_, code = strfind(line, " (%d%d%d) ") + return tonumber(code) end ----------------------------------------------------------------------------- -- Receive server reply messages -- Input --- sock: server socket +-- sock: socket connected to the server -- Returns --- status: server reply status code or nil if error --- reply: full server reply +-- code: server status code or nil if error +-- line: full http status line -- err: error message if any ----------------------------------------------------------------------------- -local get_reply = function(sock) - local reply, err - reply, err = %try_getline(sock) - if not err then return %get_status(reply), reply +local get_status = function(sock) + local line, err + line, err = %try_get(sock) + if not err then return %get_statuscode(line), line else return nil, nil, err end end ----------------------------------------------------------------------------- --- Receive and parse mime headers +-- Receive and parse responce header fields -- Input --- sock: server socket --- mime: a table that might already contain mime headers +-- sock: socket connected to the server +-- headers: a table that might already contain headers -- Returns --- mime: a table with all mime headers in the form +-- headers: a table with all headers fields in the form -- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"} -- all name_i are lowercase -- nil and error message in case of error ----------------------------------------------------------------------------- -local get_mime = function(sock, mime) +local get_headers = function(sock, headers) local line, err local name, value - -- get first line - line, err = %try_getline(sock) - if err then return nil, err end + -- get first line + line, err = %try_get(sock) + if err then return nil, err end -- headers go until a blank line is found while line ~= "" do -- get field-name and value _,_, name, value = strfind(line, "(.-):%s*(.*)") + if not name or not value then + sock:close() + return nil, "malformed reponse headers" + end name = strlower(name) -- get next line (value might be folded) - line, err = %try_getline(sock) - if err then return nil, err end + line, err = %try_get(sock) + if err then return nil, err end -- unfold any folded values - while not err and line ~= "" and (strsub(line, 1, 1) == " ") do + while not err and strfind(line, "^%s") do value = value .. line - line, err = %try_getline(sock) - if err then return nil, err end + line, err = %try_get(sock) + if err then return nil, err end end -- save pair in table - if mime[name] then - -- join any multiple field - mime[name] = mime[name] .. ", " .. value - else - -- new field - mime[name] = value - end + if headers[name] then headers[name] = headers[name] .. ", " .. value + else headers[name] = value end end - return mime + return headers +end + +----------------------------------------------------------------------------- +-- Receives a chunked message body +-- Input +-- sock: socket connected to the server +-- Returns +-- body: a string containing the body of the message +-- nil and error message in case of error +----------------------------------------------------------------------------- +local try_getchunked = function(sock) + local chunk_size, line, err + local body = "" + repeat + -- get chunk size, skip extention + line, err = %try_get(sock) + if err then return nil, err end + chunk_size = tonumber(gsub(line, ";.*", ""), 16) + if not chunk_size then + sock:close() + return nil, "invalid chunk size" + end + -- get chunk + line, err = %try_get(sock, chunk_size) + if err then return nil, err end + -- concatenate new chunk + body = body .. line + -- skip blank line + _, err = %try_get(sock) + if err then return nil, err end + until chunk_size <= 0 + return body end ----------------------------------------------------------------------------- -- Receives http body -- Input --- sock: server socket --- mime: initial mime headers +-- sock: socket connected to the server +-- headers: response header fields -- Returns -- body: a string containing the body of the document -- nil and error message in case of error -- Obs: --- mime: headers might be modified by chunked transfer +-- headers: headers might be modified by chunked transfer ----------------------------------------------------------------------------- -local get_body = function(sock, mime) +local get_body = function(sock, headers) local body, err - if mime["transfer-encoding"] == "chunked" then - local chunk_size, line - body = "" - repeat - -- get chunk size, skip extention - line, err = %try_getline(sock) - if err then return nil, err end - chunk_size = tonumber(gsub(line, ";.*", ""), 16) - if not chunk_size then - sock:close() - return nil, "invalid chunk size" - end - -- get chunk - line, err = sock:receive(chunk_size) - if err then - sock:close() - return nil, err - end - -- concatenate new chunk - body = body .. line - -- skip blank line - _, err = %try_getline(sock) - if err then return nil, err end - until chunk_size <= 0 - -- store extra mime headers - --_, err = %get_mime(sock, mime) - --if err then return nil, err end - elseif mime["content-length"] then - body, err = sock:receive(tonumber(mime["content-length"])) - if err then - sock:close() - return nil, err - end + if headers["transfer-encoding"] == "chunked" then + body, err = %try_getchunked(sock) + if err then return nil, err end + -- store extra entity headers + --_, err = %get_headers(sock, headers) + --if err then return nil, err end + elseif headers["content-length"] then + body, err = %try_get(sock, tonumber(headers["content-length"])) + if err then return nil, err end else -- get it all until connection closes! - body, err = sock:receive("*a") - if err then - sock:close() - return nil, err - end + body, err = %try_get(sock, "*a") + if err then return nil, err end end -- return whole body return body @@ -175,10 +182,9 @@ end ----------------------------------------------------------------------------- -- Parses a url and returns its scheme, user, password, host, port --- and path components, according to RFC 1738, Uniform Resource Locators (URL), --- of December 1994 +-- and path components, according to RFC 1738 -- Input --- url: unique resource locator desired +-- url: uniform resource locator of request -- default: table containing default values to be returned -- Returns -- table with the following fields: @@ -213,47 +219,99 @@ local split_url = function(url, default) end ----------------------------------------------------------------------------- --- Sends a GET message through socket +-- Tries to send request body, using chunked transfer-encoding +-- Apache, for instance, accepts only 8kb of body in a post to a CGI script +-- if we use only the content-length header field... -- Input --- socket: http connection socket --- path: path requested --- mime: mime headers to send in request +-- sock: socket connected to the server +-- body: body to be sent in request -- Returns -- err: nil in case of success, error message otherwise ----------------------------------------------------------------------------- -local send_get = function(sock, path, mime) - local err = %try_sendline(sock, "GET " .. path .. " HTTP/1.1\r\n") - if err then return err end - for i, v in mime do - err = %try_sendline(sock, i .. ": " .. v .. "\r\n") - if err then return err end - end - err = %try_sendline(sock, "\r\n") - return err +local try_sendchunked = function(sock, body) + local wanted = strlen(body) + local first = 1 + local chunk_size + local err + while wanted > 0 do + chunk_size = min(wanted, 1024) + err = %try_send(sock, format("%x\r\n", chunk_size)) + if err then return err end + err = %try_send(sock, strsub(body, first, first + chunk_size - 1)) + if err then return err end + err = %try_send(sock, "\r\n") + if err then return err end + wanted = wanted - chunk_size + first = first + chunk_size + end + err = %try_send(sock, "0\r\n") + return err end ----------------------------------------------------------------------------- --- Converts field names to lowercase +-- Sends a http request message through socket -- Input --- headers: user header fields --- parsed: parsed url components +-- sock: socket connected to the server +-- method: request method to be used +-- path: url path +-- headers: request headers to be sent +-- body: request message body, if any -- Returns --- mime: a table with the same headers, but with lowercase field names +-- err: nil in case of success, error message otherwise ----------------------------------------------------------------------------- -local fill_headers = function(headers, parsed) - local mime = {} - headers = headers or {} - for i,v in headers do - mime[strlower(i)] = v - end - mime["connection"] = "close" - mime["host"] = parsed.host - mime["user-agent"] = %USERAGENT - if parsed.user and parsed.pass then -- Basic Authentication - mime["authorization"] = "Basic ".. - base64(parsed.user .. ":" .. parsed.pass) - end - return mime +local send_request = function(sock, method, path, headers, body) + local err = %try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") + if err then return err end + for i, v in headers do + err = %try_send(sock, i .. ": " .. v .. "\r\n") + if err then return err end + end + err = %try_send(sock, "\r\n") + --if not err and body then err = %try_sendchunked(sock, body) end + if not err and body then err = %try_send(sock, body) end + return err +end + +----------------------------------------------------------------------------- +-- Determines if we should read a message body from the server response +-- Input +-- method: method used in request +-- code: server response status code +-- Returns +-- 1 if a message body should be processed, nil otherwise +----------------------------------------------------------------------------- +function has_responsebody(method, code) + if method == "HEAD" then return nil end + if code == 204 or code == 304 then return nil end + if code >= 100 and code < 200 then return nil end + return 1 +end + +----------------------------------------------------------------------------- +-- Converts field names to lowercase and add message body size specification +-- Input +-- headers: request header fields +-- parsed: parsed url components +-- body: request message body, if any +-- Returns +-- lower: a table with the same headers, but with lowercase field names +----------------------------------------------------------------------------- +local fill_headers = function(headers, parsed, body) + local lower = {} + headers = headers or {} + for i,v in headers do + lower[strlower(i)] = v + end + --if body then lower["transfer-encoding"] = "chunked" end + if body then lower["content-length"] = tostring(strlen(body)) end + lower["connection"] = "close" + lower["host"] = parsed.host + lower["user-agent"] = %USERAGENT + if parsed.user and parsed.pass then -- Basic Authentication + lower["authorization"] = "Basic ".. + base64(parsed.user .. ":" .. parsed.pass) + end + return lower end ----------------------------------------------------------------------------- @@ -262,51 +320,84 @@ end dofile("base64.lua") ----------------------------------------------------------------------------- --- Downloads and receives a http url, with its mime headers +-- Sends a HTTP request and retrieves the server reply -- Input --- url: unique resource locator desired --- headers: headers to send with request --- tried: is this an authentication retry? +-- method: "GET", "PUT", "POST" etc +-- url: target uniform resource locator +-- headers: request headers to send +-- body: request message body -- Returns --- body: document body, if successfull --- mime: headers received with document, if sucessfull --- reply: server reply, if successfull +-- resp_body: response message body, if successfull +-- resp_hdrs: response header fields received, if sucessfull +-- line: server response status line, if successfull +-- err: error message if any +----------------------------------------------------------------------------- +function http_request(method, url, headers, body) + local sock, err + local resp_hdrs, response_body + local line, code + -- get url components + local parsed = %split_url(url, {port = %PORT, path ="/"}) + -- methods are case sensitive + method = strupper(method) + -- fill default headers + headers = %fill_headers(headers, parsed, body) + -- try connection + sock, err = connect(parsed.host, parsed.port) + if not sock then return nil, nil, nil, err end + -- set connection timeout + sock:timeout(%TIMEOUT) + -- send request + err = %send_request(sock, method, parsed.path, headers, body) + if err then return nil, nil, nil, err end + -- get server message + code, line, err = %get_status(sock) + if err then return nil, nil, nil, err end + -- deal with reply + resp_hdrs, err = %get_headers(sock, {}) + if err then return nil, nil, line, err end + -- get body if status and method allow one + if has_responsebody(method, code) then + resp_body, err = %get_body(sock, resp_hdrs) + if err then return nil, resp_hdrs, line, err end + end + sock:close() + -- should we automatically retry? + if (code == 301 or code == 302) then + if (method == "GET" or method == "HEAD") and resp_hdrs["location"] then + return http_request(method, resp_hdrs["location"], headers, body) + else return nil, resp_hdrs, line end + end + return resp_body, resp_hdrs, line +end + +----------------------------------------------------------------------------- +-- Retrieves a URL by the method "GET" +-- Input +-- url: target uniform resource locator +-- headers: request headers to send +-- Returns +-- body: response message body, if successfull +-- headers: response header fields, if sucessfull +-- line: response status line, if successfull -- err: error message, if any ----------------------------------------------------------------------------- function http_get(url, headers) - local sock, err, mime, body, status, reply - -- get url components - local parsed = %split_url(url, {port = %PORT, path ="/"}) - -- fill default headers - headers = %fill_headers(headers, parsed) - -- try connection - sock, err = connect(parsed.host, parsed.port) - if not sock then return nil, nil, nil, err end - -- set connection timeout - sock:timeout(%TIMEOUT) - -- send request - err = %send_get(sock, parsed.path, headers) - if err then return nil, nil, nil, err end - -- get server message - status, reply, err = %get_reply(sock) - if err then return nil, nil, nil, err end - -- get url accordingly - if status == 200 then -- ok, go on and get it - mime, err = %get_mime(sock, {}) - if err then return nil, nil, reply, err end - body, err = %get_body(sock, mime) - if err then return nil, mime, reply, err end - sock:close() - return body, mime, reply - elseif status == 301 then -- moved permanently, try again - mime = %get_mime(sock, {}) - sock:close() - if mime["location"] then return http_get(mime["location"], headers) - else return nil, mime, reply end - elseif status == 401 then - mime, err = %get_mime(sock, {}) - if err then return nil, nil, reply, err end - return nil, mime, reply - end - return nil, nil, reply + return http_request("GET", url, headers) +end + +----------------------------------------------------------------------------- +-- Retrieves a URL by the method "GET" +-- Input +-- url: target uniform resource locator +-- body: request message body +-- headers: request headers to send +-- Returns +-- body: response message body, if successfull +-- headers: response header fields, if sucessfull +-- line: response status line, if successfull +-- err: error message, if any +----------------------------------------------------------------------------- +function http_post(url, body, headers) + return http_request("POST", url, headers, body) end From 273fd0964e4d6fd3f5457d090633596feb6d582a Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 25 Jan 2001 22:02:37 +0000 Subject: [PATCH 017/483] Updated for LuaSocket 1.2 --- README | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README b/README index c4ac6fd..d708668 100644 --- a/README +++ b/README @@ -1,14 +1,13 @@ -This directory contains the implementation of the protocols FTP, HTTP and -SMTP. The files provided are: +This directory contains the implementation of the protocols FTP, HTTP +and SMTP. The files provided are: http.lua -- HTTP protocol implementation base64.lua -- base64 encoding implementation -The module http.lua provides functionality to download an URL from a -HTTP server. The implementation conforms to the HTTP/1.1 standard, RFC -2068. The base64.lua module provides base64 encoding and decoding. The -module is used for the HTTP Basic Authentication Scheme, and conforms to -RFC 1521. +The module http.lua provides general HTTP client support. The +implementation conforms to the HTTP/1.1 standard, RFC 2068. The +base64.lua module provides base64 encoding and decoding. The module is +used for the HTTP Basic Authentication Scheme, and conforms to RFC 1521. smtp.lua -- SMTP protocol implementation @@ -20,5 +19,5 @@ SMTP mail server. The implementation conforms to RFC 821. The module ftp.lua provides functions to download and upload files from and to FTP servers. The implementation conforms to RFC 959. -These implementations are supported. Please send any comments do -diego@tecgraf.puc-rio.br. +These implementations are part of the LuaSocket library and are supported. +Please send any comments to diego@tecgraf.puc-rio.br. From a466bd5d4266047e3a54387a76ad5665055e583a Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 25 Jan 2001 22:03:16 +0000 Subject: [PATCH 018/483] Data connection is now passive. Even minimum FTP servers are usable. --- src/ftp.lua | 133 ++++++++++++++++++++++++++++------------------------ 1 file changed, 72 insertions(+), 61 deletions(-) diff --git a/src/ftp.lua b/src/ftp.lua index b817356..a9dac71 100644 --- a/src/ftp.lua +++ b/src/ftp.lua @@ -1,5 +1,5 @@ ----------------------------------------------------------------------------- --- Simple FTP support for the Lua language using the LuaSocket toolkit. +-- Simple FTP support for the Lua language using the LuaSocket 1.2 toolkit. -- Author: Diego Nehab -- Date: 26/12/2000 -- Conforming to: RFC 959 @@ -170,20 +170,22 @@ end -- Input -- file: abolute path to file -- Returns --- file: filename --- path: table with directories to reach filename --- isdir: is it a directory or a file +-- a table with the following fields +-- name: filename +-- path: directory to file +-- isdir: is it a directory? ----------------------------------------------------------------------------- local split_path = function(file) - local path = {} - local isdir - file = file or "/" - -- directory ends with a '/' - _,_, isdir = strfind(file, "([/])$") - gsub(file, "([^/]+)", function (dir) tinsert(%path, dir) end) - if not isdir then file = tremove(path) - else file = nil end - return file, path, isdir + local parsed = {} + file = gsub(file, "(/)$", function(i) %parsed.isdir = i end) + if not parsed.isdir then + file = gsub(file, "([^/]+)$", function(n) %parsed.name = n end) + end + file = gsub(file, "/$", "") + file = gsub(file, "^/", "") + if file == "" then file = nil end + parsed.path = file + if parsed.path or parsed.name or parsed.isdir then return parsed end end ----------------------------------------------------------------------------- @@ -226,40 +228,44 @@ end -- Change to target directory -- Input -- control: socket for control connection with server --- path: array with directories in order +-- path: directory to change to -- Returns -- code: nil if error -- answer: server answer or error message ----------------------------------------------------------------------------- local cwd = function(control, path) local code, answer = 250, "Home directory used" - for i = 1, getn(path) do - code, answer = %try_command(control, "cwd", path[i], {250}) - if not code then return nil, answer end + if path then + code, answer = %try_command(control, "cwd", path, {250}) end return code, answer end ----------------------------------------------------------------------------- --- Start data connection with server +-- Change to target directory -- Input --- control: control connection with server +-- control: socket for control connection with server -- Returns --- data: socket for data connection with server, nil if error --- answer: server answer or error message +-- server: server socket bound to local address, nil if error +-- answer: error message if any ----------------------------------------------------------------------------- -local start_dataconnection = function(control) - -- ask for passive data connection - local code, answer = %try_command(control, "pasv", nil, {227}) - if not code then return nil, answer end - -- get data connection parameters from server reply - local host, port = %get_pasv(answer) - if not host or not port then return nil, answer end - -- start data connection with given parameters - local data, err = connect(host, port) - if not data then return nil, err end - data:timeout(%TIMEOUT) - return data +local port = function(control) + local code, answer + local server, ctl_ip + ctl_ip, answer = control:getsockname() + server, answer = bind(ctl_ip, 0) + server:timeout(%TIMEOUT) + local ip, p, ph, pl + ip, p = server:getsockname() + pl = mod(p, 256) + ph = (p - pl)/256 + local arg = gsub(format("%s,%d,%d", ip, ph, pl), "%.", ",") + code, answer = %try_command(control, "port", arg, {200}) + if not code then + control:close() + server:close() + return nil, answer + else return server end end ----------------------------------------------------------------------------- @@ -281,22 +287,21 @@ end -- Retrieves file or directory listing -- Input -- control: control connection with server --- data: data connection with server +-- server: server socket bound to local address -- file: file name under current directory -- isdir: is file a directory name? -- Returns -- file: string with file contents, nil if error -- answer: server answer or error message ----------------------------------------------------------------------------- -local retrieve_file = function(control, data, file, isdir) +local retrieve_file = function(control, server, file, isdir) + local data -- ask server for file or directory listing accordingly if isdir then code, answer = %try_command(control, "nlst", file, {150, 125}) else code, answer = %try_command(control, "retr", file, {150, 125}) end - if not code then - control:close() - data:close() - return nil, answer - end + data, answer = server:accept() + server:close() + if not data then return nil, answer end -- download whole file file, err = data:receive("*a") data:close() @@ -314,19 +319,23 @@ end -- Stores a file -- Input -- control: control connection with server --- data: data connection with server +-- server: server socket bound to local address -- file: file name under current directory -- bytes: file contents in string -- Returns --- file: string with file contents, nil if error +-- code: return code, nil if error -- answer: server answer or error message ----------------------------------------------------------------------------- -local store_file = function (control, data, file, bytes) +local store_file = function (control, server, file, bytes) + local data local code, answer = %try_command(control, "stor", file, {150, 125}) if not code then data:close() return nil, answer end + data, answer = server:accept() + server:close() + if not data then return nil, answer end -- send whole file and close connection to mark file end answer = data:send(bytes) data:close() @@ -362,8 +371,8 @@ end -- err: error message if any ----------------------------------------------------------------------------- function ftp_get(url, type) - local control, data, err - local answer, code, server, file, path + local control, server, data, err + local answer, code, server, pfile, file parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) -- start control connection control, err = connect(parsed.host, parsed.port) @@ -376,21 +385,22 @@ function ftp_get(url, type) code, answer = %login(control, parsed.user, parsed.pass) if not code then return nil, answer end -- go to directory - file, path, isdir = %split_path(parsed.path) - code, answer = %cwd(control, path) + pfile = %split_path(parsed.path) + if not pfile then return nil, "invalid path" end + code, answer = %cwd(control, pfile.path) if not code then return nil, answer end -- change to binary type? code, answer = %change_type(control, type) if not code then return nil, answer end - -- start data connection - data, answer = %start_dataconnection(control) - if not data then return nil, answer end + -- setup passive connection + server, answer = %port(control) + if not server then return nil, answer end -- ask server to send file or directory listing - file, answer = %retrieve_file(control, data, file, isdir) + file, answer = %retrieve_file(control, server, pfile.name, pfile.isdir) if not file then return nil, answer end -- disconnect %logout(control) - -- return whatever file we received plus a possible error + -- return whatever file we received plus a possible error message return file, answer end @@ -405,7 +415,7 @@ end ----------------------------------------------------------------------------- function ftp_put(url, bytes, type) local control, data - local answer, code, server, file, path + local answer, code, server, file, pfile parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) -- start control connection control, answer = connect(parsed.host, parsed.port) @@ -418,20 +428,21 @@ function ftp_put(url, bytes, type) code, answer = %login(control, parsed.user, parsed.pass) if not code then return answer end -- go to directory - file, path, isdir = %split_path(parsed.path) - code, answer = %cwd(control, path) + pfile = %split_path(parsed.path) + if not pfile or pfile.isdir then return "invalid path" end + code, answer = %cwd(control, pfile.path) if not code then return answer end -- change to binary type? code, answer = %change_type(control, type) if not code then return answer end - -- start data connection - data, answer = %start_dataconnection(control) - if not data then return answer end - -- ask server to send file or directory listing - code, answer = %store_file(control, data, file, bytes) + -- setup passive connection + server, answer = %port(control) + if not server then return answer end + -- ask server to send file + code, answer = %store_file(control, server, pfile.name, bytes) if not code then return answer end -- disconnect %logout(control) - -- return whatever file we received plus a possible error + -- no errors return nil end From 159823e200e763529b0885715c0624559aacf82a Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Sun, 28 Jan 2001 02:16:20 +0000 Subject: [PATCH 019/483] Added support for the CYGWIN gcc compiler. --- src/luasocket.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index b81bca7..644edd6 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -1205,7 +1205,7 @@ static void tm_markstart(p_sock sock) \*-------------------------------------------------------------------------*/ static int tm_gettime(void) { -#ifdef _WIN32 +#ifdef WIN32 return GetTickCount(); #else struct tms t; @@ -1290,13 +1290,19 @@ static int send_raw(p_sock sock, const char *data, int wanted, int *total) /* a bug in WinSock forces us to do a busy wait until we manage ** to write, because select returns immediately even though it ** should have blocked us until we could write... */ - if (WSAGetLastError() == WSAEWOULDBLOCK) + if (put < 0 && WSAGetLastError() == WSAEWOULDBLOCK) continue; #endif +#ifdef __CYGWIN__ + /* this is for CYGWIN, which is like Unix but with Win32 Bugs */ + if (put < 0 && errno == EWOULDBLOCK) + continue; +#endif + return NET_CLOSED; } wanted -= put; - data += put; + data += put; *total += put; } return NET_DONE; From a221087bc007122c2fe127f68dcba150f69d4365 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Sun, 28 Jan 2001 02:18:24 +0000 Subject: [PATCH 020/483] Relaxed timeout tests. --- test/testclnt.lua | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/test/testclnt.lua b/test/testclnt.lua index 8a36512..97e06f2 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -205,7 +205,7 @@ function blockedtimed_out(t, s, err, o) pass("got natural cause timeout") return 1 end - elseif 0.9*s > t then + elseif s > t then if o == "send" then pass("must have been buffered (may be wrong)") else @@ -252,14 +252,14 @@ end ----------------------------------------------------------------------------- function returntimed_out(delta, t, err) if err == "timeout" then - if 1.1*delta >= t then + if delta >= t then pass("got rightfull timeout") return 1 else fail("shouldn't have gotten timeout") end - elseif 0.9*delta > t then - fail("should have gotten timeout") + elseif delta > t then + pass(format("but took %fs longer than should have", delta - t)) end end @@ -399,20 +399,21 @@ test_block(80000) test_block(800000) new_test("blocked timeout test") -test_blockedtimeout(80, .5, 1) -test_blockedtimeout(80, 1, 1) -test_blockedtimeout(80, 1.5, 1) +test_blockedtimeout(80, 1, 2) +test_blockedtimeout(80, 2, 2) +test_blockedtimeout(80, 3, 2) test_blockedtimeout(800, 1, 0) -test_blockedtimeout(8000, 1, 1.5) -test_blockedtimeout(80000, 1, 0) +test_blockedtimeout(8000, 2, 3) +test_blockedtimeout(80000, 2, 1) test_blockedtimeout(800000, 0.01, 0) new_test("return timeout test") -test_returntimeout(80, 1, 0.5) -test_returntimeout(80, 0.5, 1) -test_returntimeout(8000, .5, 1) -test_returntimeout(80000, 1, 0.5) -test_returntimeout(800000, 1, 0.5) +test_returntimeout(80, 2, 1) +test_returntimeout(80, 1, 2) +test_returntimeout(8000, 1, 2) +test_returntimeout(80000, 2, 1) +test_returntimeout(800000, 0.1, 0) +test_returntimeout(800000, 2, 1) ----------------------------------------------------------------------------- -- Close connection and exit server. We are done. From 297576affae2113dce05970591adab3e75be65c0 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 6 Mar 2001 19:01:44 +0000 Subject: [PATCH 021/483] Updated for release 1.2.1 Buffer size constants are now part of luasocket.h. --- src/luasocket.h | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/luasocket.h b/src/luasocket.h index 793a315..3c6578c 100644 --- a/src/luasocket.h +++ b/src/luasocket.h @@ -7,9 +7,23 @@ #ifndef _LUASOCKET_H_ #define _LUASOCKET_H_ -#define LUASOCKET_VERSION "LuaSocket 1.2" -#define LUASOCKET_BUFFERSIZE 8192 +/* Current luasocket version */ +#define LUASOCKET_VERSION "LuaSocket 1.2.1" +/*-------------------------------------------------------------------------*\ +* These can be changed to according to the applications' needs. +\*-------------------------------------------------------------------------*/ +/* TCP input buffer size */ +#define LUASOCKET_TCPBUFFERSIZE 8192 + +/* The largest datagram handled by LuaSocket */ +#define LUASOCKET_UDPBUFFERSIZE 4096 +/* note that 576 bytes is the maximum safe value */ + +/*-------------------------------------------------------------------------*\ +* Initializes the library interface with Lua and the socket library. +* Defines the symbols exported to Lua. +\*-------------------------------------------------------------------------*/ void lua_socketlibopen(lua_State *L); #endif /* _LUASOCKET_H_ */ From 27371883efefbafe485edd62f771e12beff903d1 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 6 Mar 2001 19:03:10 +0000 Subject: [PATCH 022/483] Removed global version of table methods. Close method is now permitted on closed sockets. Added generalized select support. Removed poll method, replaced by select with advantage. --- src/luasocket.c | 528 ++++++++++++++++++------------------------------ 1 file changed, 200 insertions(+), 328 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index 644edd6..c4f51bd 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -66,13 +66,14 @@ * Datatype compatibilization and some simple changes \*=========================================================================*/ #ifndef WIN32 -#define closesocket close /* WinSock2 has a closesock function instead - ** of using the regular close function */ -#define SOCKET int /* it defines a SOCKET type instead of - ** using an integer file descriptor */ -#define INVALID_SOCKET (-1) /* and uses the this macro to represent and - ** invalid socket */ -#ifndef CLK_TCK /* SunOS, does not define CLK_TCK */ +/* WinSock2 has a closesock function instead of the regular close */ +#define closesocket close +/* it defines a SOCKET type instead of using an integer file descriptor */ +#define SOCKET int +/* and uses the this macro to represent and invalid socket */ +#define INVALID_SOCKET (-1) +/* SunOS, does not define CLK_TCK */ +#ifndef CLK_TCK #define CLK_TCK 60 #endif #endif @@ -102,11 +103,6 @@ \*-------------------------------------------------------------------------*/ #define P_SOCK "(p_sock)sock" -/*-------------------------------------------------------------------------*\ -* The maximum message size handled (576 bytes should be enough...) -\*-------------------------------------------------------------------------*/ -#define UDPMAX 4096 - /*-------------------------------------------------------------------------*\ * Both socket types are stored in the same structure to simplify * implementation. The tag value used is different, though. @@ -120,7 +116,7 @@ typedef struct t_sock { /* return and blocking timeout values (-1 if no limit) */ int tm_return, tm_block; /* buffered I/O storage */ - unsigned char bf_buffer[LUASOCKET_BUFFERSIZE]; + unsigned char bf_buffer[LUASOCKET_TCPBUFFERSIZE]; /* first and last red bytes not yet passed to application */ int bf_first, bf_last; /* is this udp socket in "connected" state? */ @@ -144,11 +140,11 @@ typedef t_tags *p_tags; * Macros and internal declarations \*-------------------------------------------------------------------------*/ /* min and max macros */ -#ifndef min -#define min(x, y) ((x) < (y) ? x : y) +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? x : y) #endif -#ifndef max -#define max(x, y) ((x) > (y) ? x : y) +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? x : y) #endif /* we are lazy.. */ @@ -160,9 +156,10 @@ typedef struct sockaddr SA; /* luasocket global API functions */ static int global_tcpconnect(lua_State *L); static int global_tcpbind(lua_State *L); -static int global_udpsocket(lua_State *L); +static int global_select(lua_State *L); static int global_toip(lua_State *L); static int global_tohostname(lua_State *L); +static int global_udpsocket(lua_State *L); /* luasocket table method API functions */ static int table_tcpaccept(lua_State *L); @@ -173,26 +170,9 @@ static int table_udpreceivefrom(lua_State *L); static int table_udpsetpeername(lua_State *L); static int table_timeout(lua_State *L); static int table_close(lua_State *L); -static int table_poll(lua_State *L); static int table_getpeername(lua_State *L); static int table_getsockname(lua_State *L); -/* luasocket optional global API functions */ -#ifndef LUASOCKET_NOGLOBALS -static int global_tcpaccept(lua_State *L); -static int global_udpsendto(lua_State *L); -static int global_udpreceivefrom(lua_State *L); -static int global_udpsetpeername(lua_State *L); -static int global_udpsetsockname(lua_State *L); -static int global_getsockname(lua_State *L); -static int global_getpeername(lua_State *L); -static int global_send(lua_State *L); -static int global_receive(lua_State *L); -static int global_timeout(lua_State *L); -static int global_close(lua_State *L); -static int global_poll(lua_State *L); -#endif - /* buffered I/O management */ static const unsigned char *bf_receive(p_sock sock, int *length); static void bf_skip(p_sock sock, int length); @@ -214,6 +194,7 @@ static int receive_all(lua_State *L, p_sock sock); /* parameter manipulation functions */ static p_tags pop_tags(lua_State *L); static p_sock pop_sock(lua_State *L); +static p_sock get_sock(lua_State *L, int s, p_tags tags, int *tag); static p_sock get_selfsock(lua_State *L, p_tags tags, int *tag); static p_sock push_servertable(lua_State *L, p_tags tags); static p_sock push_clienttable(lua_State *L, p_tags tags); @@ -249,11 +230,6 @@ static int winsock_open(void); static int inet_aton(const char *cp, struct in_addr *inp); #endif -#ifndef LUASOCKET_NOGLOBALS -static p_sock get_selfserversock(lua_State *L, p_tags tags); -static p_sock get_selfudpsock(lua_State *L, p_tags tags); -#endif - /* tag methods */ static int gc_table(lua_State *L); @@ -359,22 +335,22 @@ static int table_tcpaccept(lua_State *L) p_tags tags = pop_tags(L); p_sock client = push_clienttable(L, tags); tm_markstart(server); - if (tm_gettimeleft(server) >= 0) { - set_nonblocking(server); - do { - if (tm_timedout(server, TM_RECEIVE)) { - lua_pushnil(L); - push_error(L, NET_TIMEOUT); - return 2; - } - client->sock = accept(server->sock, (SA *) &client_addr, - &client_len); - } while (client->sock == INVALID_SOCKET); + if (tm_gettimeleft(server) >= 0) { + set_nonblocking(server); + do { + if (tm_timedout(server, TM_RECEIVE)) { + lua_pushnil(L); + push_error(L, NET_TIMEOUT); + return 2; + } + client->sock = accept(server->sock, (SA *) &client_addr, + &client_len); + } while (client->sock == INVALID_SOCKET); - } else { - set_blocking(server); - client->sock = accept(server->sock, (SA *) &client_addr, &client_len); - } + } else { + set_blocking(server); + client->sock = accept(server->sock, (SA *) &client_addr, &client_len); + } set_nonblocking(client); return 1; } @@ -560,6 +536,118 @@ static int table_udpsendto(lua_State *L) } } +/*-------------------------------------------------------------------------*\ +* Waits for a set of sockets until a condition is met or timeout. +* Lua Input: {input}, {output} [, timeout] +* {input}: table of sockets to be tested for input +* {output}: table of sockets to be tested for output +* timeout: maximum amount of time to wait for condition, in seconds +* Lua Returns: {input}, {output}, err +* {input}: table with sockets ready for input +* {output}: table with sockets ready for output +* err: "timeout" or nil +\*-------------------------------------------------------------------------*/ +int global_select(lua_State *L) +{ + p_tags tags = pop_tags(L); + int ms = lua_isnil(L, 3) ? -1 : (int) (luaL_opt_number(L, 3, -1) * 1000); + fd_set readfds, *prfds = NULL, writefds, *pwfds = NULL; + struct timeval tm, *ptm = NULL; + int ret, s, max = -1; + int byfds, canread, canwrite; + /* reset the file descriptor sets */ + FD_ZERO(&readfds); FD_ZERO(&writefds); + /* all sockets, indexed by socket number, for internal use */ + lua_newtable(L); byfds = lua_gettop(L); + /* readable sockets table to be returned */ + lua_newtable(L); canread = lua_gettop(L); + /* writable sockets table to be returned */ + lua_newtable(L); canwrite = lua_gettop(L); + /* get sockets we will test for readability into fd_set */ + if (!lua_isnil(L, 1)) { + lua_pushnil(L); + while (lua_next(L, 1)) { + if (lua_tag(L, -1) == tags->table) { + p_sock sock = get_sock(L, -1, tags, NULL); + lua_pushnumber(L, sock->sock); + lua_pushvalue(L, -2); + lua_settable(L, byfds); + if (sock->sock > max) max = sock->sock; + /* a socket can have unread data in our internal buffer. in + * that case, we only call select to find out which of the + * other sockets can be written to or read from immediately. */ + if (!bf_isempty(sock)) { + ms = 0; + lua_pushnumber(L, lua_getn(L, canread) + 1); + lua_pushvalue(L, -2); + lua_settable(L, canread); + } else { + FD_SET(sock->sock, &readfds); + prfds = &readfds; + } + } + /* get rid of lua_next value and expose index */ + lua_pop(L, 1); + } + } + /* get sockets we will test for writability into fd_set */ + if (!lua_isnil(L, 2)) { + lua_pushnil(L); + while (lua_next(L, 2)) { + if (lua_tag(L, -1) == tags->table) { + p_sock sock = get_sock(L, -1, tags, NULL); + lua_pushnumber(L, sock->sock); + lua_pushvalue(L, -2); + lua_settable(L, byfds); + if (sock->sock > max) max = sock->sock; + FD_SET(sock->sock, &writefds); + pwfds = &writefds; + } + /* get rid of lua_next value and expose index */ + lua_pop(L, 1); + } + } + max++; + /* configure timeout value */ + if (ms >= 0) { + ptm = &tm; /* ptm == NULL when we don't have timeout */ + /* fill timeval structure */ + tm.tv_sec = ms / 1000; + tm.tv_usec = (ms % 1000) * 1000; + } + /* see if we can read, write or if we timedout */ + ret = select(max, prfds, pwfds, NULL, ptm); + /* did we timeout? */ + if (ret <= 0 && ms > 0) { + push_error(L, NET_TIMEOUT); + return 3; + } + /* collect readable sockets */ + if (prfds) { + for (s = 0; s < max; s++) { + if (FD_ISSET(s, prfds)) { + lua_pushnumber(L, lua_getn(L, canread) + 1); + lua_pushnumber(L, s); + lua_gettable(L, byfds); + lua_settable(L, canread); + } + } + } + /* collect writable sockets */ + if (pwfds) { + for (s = 0; s < max; s++) { + if (FD_ISSET(s, pwfds)) { + lua_pushnumber(L, lua_getn(L, canwrite) + 1); + lua_pushnumber(L, s); + lua_gettable(L, byfds); + lua_settable(L, canwrite); + } + } + } + lua_pushnil(L); + return 3; +} + /*-------------------------------------------------------------------------*\ * Returns the list of ip addresses associated with a host name * Lua Input: address @@ -581,7 +669,7 @@ static int global_toip(lua_State *L) lua_pushstring(L, host_strerror()); return 2; } - addr = *((struct in_addr *) hp->h_addr); + addr = *((struct in_addr *) hp->h_addr); lua_pushstring(L, inet_ntoa(addr)); push_resolved(L, hp); return 2; @@ -609,7 +697,7 @@ static int global_tohostname(lua_State *L) return 2; } lua_pushstring(L, hp->h_name); - push_resolved(L, hp); + push_resolved(L, hp); return 2; } @@ -649,7 +737,7 @@ static int table_udpsend(lua_State *L) * Receives a datagram from a UDP socket * Lua Input: sock [, wanted] * sock: client socket created by the connect function -* wanted: the number of bytes expected (default: UDPMAX) +* wanted: the number of bytes expected (default: LUASOCKET_UDPBUFFERSIZE) * Lua Returns * On success: datagram received, ip and port of sender * On error: nil, followed by an error message @@ -657,10 +745,10 @@ static int table_udpsend(lua_State *L) static int table_udpreceivefrom(lua_State *L) { p_sock sock = pop_sock(L); - size_t wanted = (int) luaL_opt_number(L, 2, UDPMAX); + size_t wanted = (int) luaL_opt_number(L, 2, LUASOCKET_UDPBUFFERSIZE); struct sockaddr_in peer; size_t peer_len = sizeof(peer); - unsigned char buffer[UDPMAX]; + unsigned char buffer[LUASOCKET_UDPBUFFERSIZE]; int got; if (sock->is_connected) lua_error(L, "receivefrom on connected socket"); tm_markstart(sock); @@ -669,7 +757,7 @@ static int table_udpreceivefrom(lua_State *L) push_error(L, NET_TIMEOUT); return 2; } - wanted = min(wanted, sizeof(buffer)); + wanted = MIN(wanted, sizeof(buffer)); got = recvfrom(sock->sock, buffer, wanted, 0, (SA *) &peer, &peer_len); if (got >= 0) { lua_pushlstring(L, buffer, got); @@ -687,7 +775,7 @@ static int table_udpreceivefrom(lua_State *L) * Receives data from a UDP socket * Lua Input: sock [, wanted] * sock: client socket created by the connect function -* wanted: the number of bytes expected (default: UDPMAX) +* wanted: the number of bytes expected (default: LUASOCKET_UDPBUFFERSIZE) * Lua Returns * On success: datagram received * On error: nil, followed by an error message @@ -695,8 +783,8 @@ static int table_udpreceivefrom(lua_State *L) static int table_udpreceive(lua_State *L) { p_sock sock = pop_sock(L); - size_t wanted = (size_t) luaL_opt_number(L, 2, UDPMAX); - unsigned char buffer[UDPMAX]; + size_t wanted = (size_t) luaL_opt_number(L, 2, LUASOCKET_UDPBUFFERSIZE); + unsigned char buffer[LUASOCKET_UDPBUFFERSIZE]; int got; tm_markstart(sock); if (tm_timedout(sock, TM_RECEIVE)) { @@ -704,7 +792,7 @@ static int table_udpreceive(lua_State *L) push_error(L, NET_TIMEOUT); return 2; } - got = recv(sock->sock, buffer, min(wanted, sizeof(buffer)), 0); + got = recv(sock->sock, buffer, MIN(wanted, sizeof(buffer)), 0); if (got >= 0) { lua_pushlstring(L, buffer, got); return 1; @@ -745,11 +833,11 @@ static int table_tcpreceive(lua_State *L) lua_pushstring(L, "*l"); top++; } - /* make sure we have enough stack space */ - luaL_checkstack(L, top+LUA_MINSTACK, "too many arguments"); + /* make sure we have enough stack space */ + luaL_checkstack(L, top+LUA_MINSTACK, "too many arguments"); /* receive all patterns */ for (arg = 2; arg <= top; arg++) { - /* if one pattern failed, we just skip all other patterns */ + /* if one pattern fails, we just skip all other patterns */ if (err != NET_DONE) { lua_pushnil(L); continue; @@ -841,53 +929,14 @@ static int table_getsockname(lua_State *L) static int table_close(lua_State *L) { /* close socket and set value to INVALID_SOCKET so that - ** pop_socket can later detect the use of a closed socket */ - p_sock sock = pop_sock(L); - closesocket(sock->sock); + ** pop_sock can later detect the use of a closed socket */ + p_sock sock = (p_sock) lua_touserdata(L, -1); + if (!sock) lua_error(L, "invalid socket object"); + if (sock->sock != INVALID_SOCKET) closesocket(sock->sock); sock->sock = INVALID_SOCKET; return 0; } -/*-------------------------------------------------------------------------*\ -* Tests if we can immediately read or write on a socket -* Lua Input -* sock: socket to be closed -* operation: operation to query "*r", "*s" -* Lua Returns -* 1 if operation will be accepted, nil otherwise -\*-------------------------------------------------------------------------*/ -static int table_poll(lua_State *L) -{ - p_sock sock = pop_sock(L); - const char *op = luaL_check_string(L, 2); - int tm_block = sock->tm_block; - int tm_return = sock->tm_return; - if (!*op || *op != '*') lua_error(L, "invalid poll pattern"); - op++; - tm_markstart(sock); - switch (*op) { - case 'r': - sock->tm_block = sock->tm_return = 0; - if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) - lua_pushnil(L); - else lua_pushnumber(L, 1); - sock->tm_block = tm_block; - sock->tm_return = tm_return; - break; - case 's': - sock->tm_block = sock->tm_return = 0; - if (tm_timedout(sock, TM_SEND)) lua_pushnil(L); - else lua_pushnumber(L, 1); - sock->tm_block = tm_block; - sock->tm_return = tm_return; - break; - default: - lua_error(L, "invalid poll pattern"); - break; - } - return 1; -} - /*-------------------------------------------------------------------------*\ * Garbage collection fallback for the socket objects. This function * makes sure that all collected sockets are closed. @@ -959,7 +1008,7 @@ const char *tcp_tryconnect(p_sock sock, const char *address, sock->sock = socket(AF_INET, SOCK_STREAM, 0); if (sock->sock == INVALID_SOCKET) return socket_strerror(); if (connect(sock->sock, (SA *) &remote, sizeof(remote)) == 0) - break; + break; closesocket(sock->sock); sock->sock = INVALID_SOCKET; memset(&remote, 0, sizeof(remote)); @@ -1140,13 +1189,13 @@ static int tm_gettimeleft(p_sock sock) return -1; /* there is no block timeout, we use the return timeout */ else if (sock->tm_block < 0) - return max(sock->tm_return - tm_gettime() + sock->tm_start, 0); + return MAX(sock->tm_return - tm_gettime() + sock->tm_start, 0); /* there is no return timeout, we use the block timeout */ else if (sock->tm_return < 0) return sock->tm_block; /* both timeouts are specified */ - else return min(sock->tm_block, - max(sock->tm_return - tm_gettime() + sock->tm_start, 0)); + else return MIN(sock->tm_block, + MAX(sock->tm_return - tm_gettime() + sock->tm_start, 0)); } /*-------------------------------------------------------------------------*\ @@ -1253,7 +1302,7 @@ static void bf_skip(p_sock sock, int length) static const unsigned char *bf_receive(p_sock sock, int *length) { if (bf_isempty(sock)) { - int got = recv(sock->sock, sock->bf_buffer, LUASOCKET_BUFFERSIZE, 0); + int got = recv(sock->sock, sock->bf_buffer, LUASOCKET_TCPBUFFERSIZE, 0); sock->bf_first = 0; if (got >= 0) sock->bf_last = got; else sock->bf_last = 0; @@ -1294,15 +1343,15 @@ static int send_raw(p_sock sock, const char *data, int wanted, int *total) continue; #endif #ifdef __CYGWIN__ - /* this is for CYGWIN, which is like Unix but with Win32 Bugs */ - if (put < 0 && errno == EWOULDBLOCK) - continue; + /* this is for CYGWIN, which is like Unix but with Win32 Bugs */ + if (put < 0 && errno == EWOULDBLOCK) + continue; #endif - + return NET_CLOSED; } wanted -= put; - data += put; + data += put; *total += put; } return NET_DONE; @@ -1333,7 +1382,7 @@ static int receive_raw(lua_State *L, p_sock sock, int wanted) luaL_pushresult(&b); return NET_CLOSED; } - got = min(got, wanted); + got = MIN(got, wanted); luaL_addlstring(&b, buffer, got); bf_skip(sock, got); wanted -= got; @@ -1466,11 +1515,12 @@ static int receive_unixline(lua_State *L, p_sock sock) void lua_socketlibopen(lua_State *L) { static struct luaL_reg funcs[] = { - {"connect", global_tcpconnect}, - {"udpsocket", global_udpsocket}, {"bind", global_tcpbind}, + {"connect", global_tcpconnect}, + {"select", global_select}, {"toip", global_toip}, {"tohostname", global_tohostname}, + {"udpsocket", global_udpsocket}, }; unsigned int i; /* declare new Lua tags for used userdata values */ @@ -1490,30 +1540,6 @@ void lua_socketlibopen(lua_State *L) lua_pushuserdata(L, tags); lua_pushcclosure(L, gc_table, 1); lua_settagmethod(L, tags->table, "gc"); - -#ifndef LUASOCKET_NOGLOBALS - /* global version of socket table functions */ -{ static struct luaL_reg opt_funcs[] = { - {"accept", global_tcpaccept}, - {"setpeername", global_udpsetpeername}, - {"setsockname", global_udpsetsockname}, - {"getsockname", global_getsockname}, - {"getpeername", global_getpeername}, - {"sendto", global_udpsendto}, - {"receivefrom", global_udpreceivefrom}, - {"timeout", global_timeout}, - {"send", global_send}, - {"poll", global_poll}, - {"receive", global_receive}, - {"close", global_close}, - }; - for (i = 0; i < sizeof(opt_funcs)/sizeof(opt_funcs[0]); i++) { - lua_pushuserdata(L, tags); - lua_pushcclosure(L, opt_funcs[i].func, 1); - lua_setglobal(L, opt_funcs[i].name); - } -} -#endif #ifdef WIN32 /* WinSock needs special initialization */ winsock_open(); @@ -1528,134 +1554,6 @@ void lua_socketlibopen(lua_State *L) #endif } -/*=========================================================================*\ -* Optional global version of socket table methods -* Simply push socket object on top or stack and call the table methods. -\*=========================================================================*/ -#ifndef LUASOCKET_NOGLOBALS -int global_tcpaccept(lua_State *L) -{ - p_tags tags = pop_tags(L); - p_sock sock = get_selfserversock(L, tags); - lua_pushuserdata(L, tags); - lua_pushusertag(L, sock, tags->server); - return table_tcpaccept(L); -} - -int global_udpsendto(lua_State *L) -{ - p_tags tags = pop_tags(L); - p_sock sock = get_selfudpsock(L, tags); - lua_pushusertag(L, sock, tags->udp); - return table_udpsendto(L); -} - -int global_udpsetpeername(lua_State *L) -{ - p_tags tags = pop_tags(L); - p_sock sock = get_selfudpsock(L, tags); - lua_pushusertag(L, sock, tags->udp); - return table_udpsetpeername(L); -} - -int global_udpsetsockname(lua_State *L) -{ - p_tags tags = pop_tags(L); - p_sock sock = get_selfudpsock(L, tags); - lua_pushusertag(L, sock, tags->udp); - return table_udpsetsockname(L); -} - -int global_udpreceivefrom(lua_State *L) -{ - p_tags tags = pop_tags(L); - p_sock sock = get_selfudpsock(L, tags); - lua_pushusertag(L, sock, tags->udp); - return table_udpreceivefrom(L); -} - -int global_poll(lua_State *L) -{ - p_tags tags = pop_tags(L); - int tag; - p_sock sock = get_selfsock(L, tags, &tag); - lua_pushusertag(L, sock, tag); - return table_poll(L); -} - -int global_send(lua_State *L) -{ - p_tags tags = pop_tags(L); - int tag; - p_sock sock = get_selfsock(L, tags, &tag); - if (tag == tags->udp) { - lua_pushusertag(L, sock, tags->udp); - return table_udpsend(L); - } else if (tag == tags->client) { - lua_pushusertag(L, sock, tags->client); - return table_tcpsend(L); - } else if (tag == tags->server) { - lua_error(L, "send on server socket"); - } else - lua_error(L, "invalid socket object"); - /* avoid compiler warnings */ - return 0; -} - -int global_receive(lua_State *L) -{ - p_tags tags = pop_tags(L); - int tag; - p_sock sock = get_selfsock(L, tags, &tag); - if (tag == tags->udp) { - lua_pushusertag(L, sock, tags->udp); - return table_udpreceive(L); - } else if (tag == tags->client) { - lua_pushusertag(L, sock, tags->client); - return table_tcpreceive(L); - } else if (tag == tags->server) { - lua_error(L, "receive on server socket"); - } else - lua_error(L, "invalid socket object"); - /* avoid compiler warnings */ - return 0; -} - -int global_timeout(lua_State *L) -{ - p_tags tags = pop_tags(L); - int tag; - p_sock sock = get_selfsock(L, tags, &tag); - lua_pushusertag(L, sock, tag); - return table_timeout(L); -} - -int global_getpeername(lua_State *L) -{ - p_tags tags = pop_tags(L); - int tag; - p_sock sock = get_selfsock(L, tags, &tag); - if (tag == tags->server) lua_error(L, "getpeername on server socket"); - lua_pushusertag(L, sock, tag); - return table_getpeername(L); -} - -int global_getsockname(lua_State *L) -{ - p_tags tags = pop_tags(L); - int tag; - p_sock sock = get_selfsock(L, tags, &tag); - lua_pushusertag(L, sock, tag); - return table_getsockname(L); -} - -int global_close(lua_State *L) -{ - /* just call the garbage collection tag method. it knows what to do */ - return gc_table(L); -} -#endif - /*=========================================================================*\ * Lua Stack manipulation functions \*=========================================================================*/ @@ -1673,7 +1571,6 @@ static p_sock push_clienttable(lua_State *L, p_tags tags) {"close", table_close}, {"getsockname", table_getsockname}, {"getpeername", table_getpeername}, - {"poll", table_poll}, {"receive", table_tcpreceive}, {"send", table_tcpsend}, {"timeout", table_timeout}, @@ -1713,7 +1610,6 @@ static p_sock push_servertable(lua_State *L, p_tags tags) static struct luaL_reg funcs[] = { {"close", table_close}, {"getsockname", table_getsockname}, - {"poll", table_poll}, {"timeout", table_timeout}, }; unsigned int i; @@ -1759,7 +1655,6 @@ static p_sock push_udptable(lua_State *L, p_tags tags) {"setsockname", table_udpsetsockname}, {"getpeername", table_getpeername}, {"getsockname", table_getsockname}, - {"poll", table_poll}, {"receivefrom", table_udpreceivefrom}, {"receive", table_udpreceive}, {"send", table_udpsend}, @@ -1804,40 +1699,40 @@ static p_sock push_udptable(lua_State *L, p_tags tags) \*-------------------------------------------------------------------------*/ static void push_resolved(lua_State *L, struct hostent *hp) { - char **alias; - struct in_addr **addr; - int i, resolved; + char **alias; + struct in_addr **addr; + int i, resolved; lua_newtable(L); resolved = lua_gettop(L); - lua_pushstring(L, "name"); - lua_pushstring(L, hp->h_name); - lua_settable(L, resolved); + lua_pushstring(L, "name"); + lua_pushstring(L, hp->h_name); + lua_settable(L, resolved); lua_pushstring(L, "ip"); lua_pushstring(L, "alias"); - i = 1; - alias = hp->h_aliases; + i = 1; + alias = hp->h_aliases; lua_newtable(L); - while (*alias) { - lua_pushnumber(L, i); - lua_pushstring(L, *alias); - lua_settable(L, -3); - i++; alias++; - } - lua_settable(L, resolved); + while (*alias) { + lua_pushnumber(L, i); + lua_pushstring(L, *alias); + lua_settable(L, -3); + i++; alias++; + } + lua_settable(L, resolved); - i = 1; + i = 1; lua_newtable(L); - addr = (struct in_addr **) hp->h_addr_list; + addr = (struct in_addr **) hp->h_addr_list; while (*addr) { - lua_pushnumber(L, i); - lua_pushstring(L, inet_ntoa(**addr)); - lua_settable(L, -3); + lua_pushnumber(L, i); + lua_pushstring(L, inet_ntoa(**addr)); + lua_settable(L, -3); i++; addr++; } - lua_settable(L, resolved); + lua_settable(L, resolved); } /*-------------------------------------------------------------------------*\ @@ -1881,12 +1776,12 @@ static p_sock pop_sock(lua_State *L) return sock; } -static p_sock get_selfsock(lua_State *L, p_tags tags, int *tag) +static p_sock get_sock(lua_State *L, int s, p_tags tags, int *tag) { p_sock sock; - if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); + if (lua_tag(L, s) != tags->table) lua_error(L, "invalid socket object"); lua_pushstring(L, P_SOCK); - lua_gettable(L, 1); + lua_gettable(L, s > 0 ? s : s-1); sock = lua_touserdata(L, -1); if (!sock) lua_error(L, "invalid socket object"); if (tag) *tag = lua_tag(L, -1); @@ -1894,34 +1789,11 @@ static p_sock get_selfsock(lua_State *L, p_tags tags, int *tag) return sock; } -#ifndef LUASOCKET_NOGLOBALS -static p_sock get_selfudpsock(lua_State *L, p_tags tags) +static p_sock get_selfsock(lua_State *L, p_tags tags, int *tag) { - p_sock sock; - if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); - lua_pushstring(L, P_SOCK); - lua_gettable(L, 1); - sock = lua_touserdata(L, -1); - if (!sock || lua_tag(L, -1) != tags->udp) - lua_error(L, "udp socket expected"); - lua_pop(L, 1); - return sock; + return get_sock(L, 1, tags, tag); } -static p_sock get_selfserversock(lua_State *L, p_tags tags) -{ - p_sock sock; - if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); - lua_pushstring(L, P_SOCK); - lua_gettable(L, 1); - sock = lua_touserdata(L, -1); - if (!sock || lua_tag(L, -1) != tags->server) - lua_error(L, "server socket expected"); - lua_pop(L, 1); - return sock; -} -#endif - /*=========================================================================*\ * WinSock2 specific functions. \*=========================================================================*/ From 29226588da627587cacc40605b24c3eea01c2a8e Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 6 Mar 2001 19:23:21 +0000 Subject: [PATCH 023/483] Updated to remove use of global methods. Conforming to LuaSocket release 1.2.1 --- samples/daytimeclnt.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/daytimeclnt.lua b/samples/daytimeclnt.lua index 94e03d3..1107c6a 100644 --- a/samples/daytimeclnt.lua +++ b/samples/daytimeclnt.lua @@ -7,8 +7,8 @@ end host = toip(host) udp = udpsocket() print("Using host '" ..host.. "' and port " ..port.. "...") -err = sendto(udp, "anything", host, port) +err = udp:sendto("anything", host, port) if err then print(err) exit() end -dgram, err = receive(udp) +dgram, err = udp:receive() if not dgram then print(err) exit() end write(dgram) From 2c9008772ef9c015569204bede90152ed975d0cc Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 6 Mar 2001 19:46:42 +0000 Subject: [PATCH 024/483] Updated to remove use of global methods. Conforming to LuaSocket release 1.2.1 --- etc/dict.lua | 12 ++++++------ samples/echoclnt.lua | 8 ++++---- samples/echosrvr.lua | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/etc/dict.lua b/etc/dict.lua index 683cb45..c620cc4 100644 --- a/etc/dict.lua +++ b/etc/dict.lua @@ -17,13 +17,13 @@ while 1 do if w=="=" then w=read"*l" verbose(">>>",w,"\n") - send(s,w,"\r\n") + s:send(w,"\r\n") else verbose(">>> looking up `",w,"'\n") - send(s,"DEFINE wn ",w,"\r\n") + s:send("DEFINE wn ",w,"\r\n") end while 1 do - local l=receive(s) + local l=s:receive() if l==nil then break end if strfind(l,"^[0-9]") then write("<<< ",l,"\n") @@ -34,6 +34,6 @@ while 1 do end end -send(s,"QUIT\r\n") -verbose("<<< ",receive(s),"\n") -close(s) +s:send("QUIT\r\n") +verbose("<<< ",s:receive(),"\n") +s:close() diff --git a/samples/echoclnt.lua b/samples/echoclnt.lua index d1c56c7..043b2f0 100644 --- a/samples/echoclnt.lua +++ b/samples/echoclnt.lua @@ -7,15 +7,15 @@ end host = toip(host) udp, err = udpsocket() if not udp then print(err) exit() end -err = setpeername(udp, host, port) +err = udp:setpeername(host, port) if err then print(err) exit() end -print("Using host '" ..host.. "' and port " ..port.. "...") +print("Using host '" ..host.. "' and port " .. port .. "...") while 1 do line = read() if not line then exit() end - err = send(udp, line) + err = udp:send(line) if err then print(err) exit() end - dgram, err = receive(udp) + dgram, err = udp:receive() if not dgram then print(err) exit() end print(dgram) end diff --git a/samples/echosrvr.lua b/samples/echosrvr.lua index fe7da06..330f9e6 100644 --- a/samples/echosrvr.lua +++ b/samples/echosrvr.lua @@ -7,16 +7,16 @@ end print("Binding to host '" ..host.. "' and port " ..port.. "...") udp, err = udpsocket() if not udp then print(err) exit() end -err = setsockname(udp, host, port) +err = udp:setsockname(host, port) if err then print(err) exit() end -timeout(udp, 5) -ip, port = getsockname(udp) +udp:timeout(5) +ip, port = udp:getsockname() print("Waiting packets on " .. ip .. ":" .. port .. "...") while 1 do - dgram, ip, port = receivefrom(udp) + dgram, ip, port = udp:receivefrom() if not dgram then print(ip) else print("Echoing from " .. ip .. ":" .. port) - sendto(udp, dgram, ip, port) + udp:sendto(dgram, ip, port) end end From 22a5d3f669c2ad57b422b4cfa8e06cb4713aa12f Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 6 Mar 2001 19:54:57 +0000 Subject: [PATCH 025/483] Uptated for LuaSocket 1.2. The version released was incompatible... --- samples/listener.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/listener.lua b/samples/listener.lua index 5e1eb02..f9ee3bf 100644 --- a/samples/listener.lua +++ b/samples/listener.lua @@ -1,15 +1,16 @@ host = host or "localhost" -port = host or 8080 +port = port or 8080 if arg then host = arg[1] or host port = arg[2] or port end print("Binding to host '" ..host.. "' and port " ..port.. "...") -s, i, p, e = bind(host, port) +s, e = bind(host, port) if not s then print(e) exit() end +i, p = s:getsockname() print("Waiting connection from talker on " .. i .. ":" .. p .. "...") c, e = s:accept() if not c then From bbb4b3e2c19376ee124033b68f699d44f48bee52 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 6 Mar 2001 20:16:17 +0000 Subject: [PATCH 026/483] Updated to remove use of global methods. Conforming to LuaSocket release 1.2.1 --- test/testclnt.lua | 66 +++++++++++++++++++++++------------------------ test/testsrvr.lua | 22 ++++++++-------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/test/testclnt.lua b/test/testclnt.lua index 97e06f2..8ad0f38 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -59,7 +59,7 @@ end ----------------------------------------------------------------------------- function reconnect() if data then - close(data) + data:close() send_command(CLOSE) data = nil end @@ -105,9 +105,9 @@ function test_asciiline(len) str10 = strrep("aZb.c#dAe?", floor(len/10)) str = str .. str10 write("testing ", len, " byte(s) line\n") - err = send(data, str, "\n") + err = data:send(str, "\n") if err then fail(err) end - back, err = receive(data) + back, err = data:receive() if err then fail(err) end if back == str then pass("lines match") else fail("lines don't match") end @@ -123,10 +123,10 @@ function test_closed() reconnect() print("testing close while reading line") send_command(ECHO_BLOCK, len) - send(data, str) + data:send(str) send_command(CLOSE) -- try to get a line - back, err = receive(data) + back, err = data:receive() if not err then fail("shold have gotten 'closed'.") elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") elseif str ~= back then fail("didn't receive what i should 'closed'.") @@ -134,10 +134,10 @@ function test_closed() reconnect() print("testing close while reading block") send_command(ECHO_BLOCK, len) - send(data, str) + data:send(str) send_command(CLOSE) -- try to get a line - back, err = receive(data, 2*len) + back, err = data:receive(2*len) if not err then fail("shold have gotten 'closed'.") elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") elseif str ~= back then fail("didn't receive what I should.") @@ -157,9 +157,9 @@ function test_rawline(len) str10 = strrep(strchar(120,21,77,4,5,0,7,36,44,100), floor(len/10)) str = str .. str10 write("testing ", len, " byte(s) line\n") - err = send(data, str, "\n") + err = data:send(str, "\n") if err then fail(err) end - back, err = receive(data) + back, err = data:receive() if err then fail(err) end if back == str then pass("lines match") else fail("lines don't match") end @@ -177,12 +177,12 @@ function test_block(len) send_command(ECHO_BLOCK, len) write("testing ", len, " byte(s) block\n") s1 = strrep("x", half) - err = send(data, s1) + err = data:send(s1) if err then fail(err) end s2 = strrep("y", len-half) - err = send(data, s2) + err = data:send(s2) if err then fail(err) end - back, err = receive(data, len) + back, err = data:receive(len) if err then fail(err) end if back == s1..s2 then pass("blocks match") else fail("blocks don't match") end @@ -229,15 +229,15 @@ function test_blockedtimeout(len, t, s) send_command(RECEIVE_BLOCK, len) write("testing ", len, " bytes, ", t, "s block timeout, ", s, "s sleep\n") - timeout(data, t) + data:timeout(t) str = strrep("a", 2*len) - err, total = send(data, str) + err, total = data:send(str) if blockedtimed_out(t, s, err, "send") then return end if err then fail(err) end send_command(SEND_BLOCK) send_command(SLEEP, s) send_command(SEND_BLOCK) - back, err = receive(data, 2*len) + back, err = data:receive(2*len) if blockedtimed_out(t, s, err, "receive") then return end if err then fail(err) end if back == str then pass("blocks match") @@ -278,16 +278,16 @@ function test_returntimeout(len, t, s) send_command(RECEIVE_BLOCK, len) write("testing ", len, " bytes, ", t, "s return timeout, ", s, "s sleep\n") - timeout(data, t, "return") + data:timeout(t, "return") str = strrep("a", 2*len) - err, total, delta = send(data, str) + err, total, delta = data:send(str) print("sent in " .. delta .. "s") if returntimed_out(delta, t, err) then return end if err then fail("unexpected error: " .. err) end send_command(SEND_BLOCK) send_command(SLEEP, s) send_command(SEND_BLOCK) - back, err, delta = receive(data, 2*len) + back, err, delta = data:receive(2*len) print("received in " .. delta .. "s") if returntimed_out(delta, t, err) then return end if err then fail("unexpected error: " .. err) end @@ -308,37 +308,37 @@ function test_patterns() block = block .. unix_line1 .. "\n" .. unix_line2 .. "\n" block = block .. block send_command(ECHO_BLOCK, strlen(block)) - err = send(data, block) + err = data:send(block) if err then fail(err) end - local back = receive(data, "*l") + local back = data:receive("*l") if back ~= dos_line1 then fail("'*l' failed") end - back = receive(data, "*l") + back = data:receive("*l") if back ~= dos_line2 then fail("'*l' failed") end - back = receive(data, "*lu") + back = data:receive("*lu") if back ~= unix_line1 then fail("'*lu' failed") end - back = receive(data, "*lu") + back = data:receive("*lu") if back ~= unix_line2 then fail("'*lu' failed") end - back = receive(data) + back = data:receive() if back ~= dos_line1 then fail("default failed") end - back = receive(data) + back = data:receive() if back ~= dos_line2 then fail("default failed") end - back = receive(data, "*lu") + back = data:receive("*lu") if back ~= unix_line1 then fail("'*lu' failed") end - back = receive(data, "*lu") + back = data:receive("*lu") if back ~= unix_line2 then fail("'*lu' failed") end pass("line patterns are ok") send_command(ECHO_BLOCK, strlen(block)) - err = send(data, block) + err = data:send(block) if err then fail(err) end - back = receive(data, strlen(block)) + back = data:receive(strlen(block)) if back ~= block then fail("number failed") end pass("number is ok") send_command(ECHO_BLOCK, strlen(block)) send_command(SLEEP, 1) send_command(CLOSE) - err = send(data, block) + err = data:send(block) if err then fail(err) end - back = receive(data, "*a") + back = data:receive("*a") if back ~= block then fail("'*a' failed") end pass("'*a' is ok") end @@ -390,7 +390,7 @@ test_block(800000) new_test("non-blocking transfer test") -- the value is not important, we only want -- to test non-blockin I/O anyways -timeout(data, 200) +data:timeout(200) test_block(1) test_block(17) test_block(200) @@ -421,7 +421,7 @@ test_returntimeout(800000, 2, 1) print("client: closing connection with server") send_command(CLOSE) send_command(EXIT) -close(control) +control:close() new_test("the library has passed all tests") print(format("time elapsed: %6.2fs", time() - start)) diff --git a/test/testsrvr.lua b/test/testsrvr.lua index d69b5ab..efa5991 100644 --- a/test/testsrvr.lua +++ b/test/testsrvr.lua @@ -30,7 +30,7 @@ if not server then exit(1) end print("server: waiting for control connection...") -control = accept(server) +control = server:accept() print("server: control connection stablished!") ----------------------------------------------------------------------------- @@ -42,7 +42,7 @@ print("server: control connection stablished!") function execute_command(cmd, par) if cmd == CONNECT then print("server: waiting for data connection...") - data = accept(server) + data = server:accept() if not data then fail("server: unable to start data connection!") else @@ -51,31 +51,31 @@ function execute_command(cmd, par) elseif cmd == CLOSE then print("server: closing connection with client...") if data then - close(data) + data:close() data = nil end elseif cmd == ECHO_LINE then - str, err = receive(data) + str, err = data:receive() if err then fail("server: " .. err) end - err = send(data, str, "\n") + err = data:send(str, "\n") if err then fail("server: " .. err) end elseif cmd == ECHO_BLOCK then - str, err = receive(data, par) + str, err = data:receive(par) print(format("server: received %d bytes", strlen(str))) if err then fail("server: " .. err) end print(format("server: sending %d bytes", strlen(str))) - err = send(data, str) + err = data:send(str) if err then fail("server: " .. err) end elseif cmd == RECEIVE_BLOCK then - str, err = receive(data, par) + str, err = data:receive(par) print(format("server: received %d bytes", strlen(str))) elseif cmd == SEND_BLOCK then print(format("server: sending %d bytes", strlen(str))) - err = send(data, str) + err = data:send(str) elseif cmd == ECHO_TIMEOUT then - str, err = receive(data, par) + str, err = data:receive(par) if err then fail("server: " .. err) end - err = send(data, str) + err = data:send(str) if err then fail("server: " .. err) end elseif cmd == COMMAND then cmd, par = get_command() From 22396d34f5443eb7f342b25783cbd771d2493ded Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 7 Mar 2001 22:38:54 +0000 Subject: [PATCH 027/483] Updated for release 1.2.1. Added '*w' pattern test. --- test/testclnt.lua | 70 +++++++++++++++++++++++++++++++---------------- test/testsrvr.lua | 12 +------- 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/test/testclnt.lua b/test/testclnt.lua index 8ad0f38..ca67761 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -1,31 +1,10 @@ ----------------------------------------------------------------------------- -- LuaSocket automated test module --- client.lua +-- testclnt.lua -- This is the client module. It connects with the server module and executes -- all tests. ----------------------------------------------------------------------------- ------------------------------------------------------------------------------ --- Prints a header to separate the test phases --- Input --- test: test phase name ------------------------------------------------------------------------------ -function new_test(test) - write("----------------------------------------------\n", - test, "\n", - "----------------------------------------------\n") -end - ------------------------------------------------------------------------------ --- Get host and port from command line ------------------------------------------------------------------------------ -HOST = "127.0.0.1" -PORT = 2020 -if arg then - HOST = arg[1] or HOST - PORT = arg[2] or PORT -end - ----------------------------------------------------------------------------- -- Read command definitions ----------------------------------------------------------------------------- @@ -113,6 +92,27 @@ function test_asciiline(len) else fail("lines don't match") end end +----------------------------------------------------------------------------- +-- Tests multiple pattern transmission +-- Input +-- len: length of line to be tested +----------------------------------------------------------------------------- +function test_multiple() + local p1 = "unix line\n" + local p2 = "dos line\r\n" + local p3 = "raw bytes" + local bp1, bp2, bp3 + reconnect() + send_command(ECHO_BLOCK, strlen(p1)+strlen(p2)+strlen(p3)) + err = data:send(p1, p2, p3) + if err then fail(err) end + bp1, bp2, bp3, err = data:receive("*lu", "*l", strlen(p3)) + if err then fail(err) end + if bp1.."\n" == p1 and bp2.."\r\n" == p2 and bp3 == p3 then + pass("patterns match") + else fail("patterns don't match") end +end + ----------------------------------------------------------------------------- -- Tests closed connection detection ----------------------------------------------------------------------------- @@ -295,9 +295,27 @@ function test_returntimeout(len, t, s) else fail("blocks don't match") end end + + + ----------------------------------------------------------------------------- --- Tests return-timeout conformance +-- Tests read patterns ----------------------------------------------------------------------------- +function test_word() + local b1 = " \t one two three \n this_is_a_very" + local b2 = "_big_word " + send_command(ECHO_BLOCK, strlen(b1)+strlen(b2)) + err = data:send(b1, b2) + local a1, a2, a3, a4 + a1, a2, a3, a4, err = data:receive("*w", "*w", "*w", "*w") + if err then fail(err) end + _, err = data:receive(1) -- get last space + if err then fail(err) end + if a1 ~= "one" or a2 ~= "two" or a3 ~= "three" or + a4 ~= "this_is_a_very_big_word" then fail("'*w' failed") end + pass("'*w' is ok") +end + function test_patterns() local dos_line1 = "this the first dos line" local dos_line2 = "this is another dos line" @@ -333,6 +351,7 @@ function test_patterns() back = data:receive(strlen(block)) if back ~= block then fail("number failed") end pass("number is ok") + test_word() send_command(ECHO_BLOCK, strlen(block)) send_command(SLEEP, 1) send_command(CLOSE) @@ -356,12 +375,16 @@ test_command(ECHO_BLOCK, 12234) test_command(SLEEP, 1111) test_command(ECHO_LINE) +--a = [[ new_test("connection close test") test_closed() new_test("read pattern test") test_patterns() +new_test("multiple pattern test") +test_multiple() + new_test("character string test") test_asciiline(1) test_asciiline(17) @@ -414,6 +437,7 @@ test_returntimeout(8000, 1, 2) test_returntimeout(80000, 2, 1) test_returntimeout(800000, 0.1, 0) test_returntimeout(800000, 2, 1) +--]] ----------------------------------------------------------------------------- -- Close connection and exit server. We are done. diff --git a/test/testsrvr.lua b/test/testsrvr.lua index efa5991..4530014 100644 --- a/test/testsrvr.lua +++ b/test/testsrvr.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- LuaSocket automated test module --- server.lua +-- testsrvr.lua -- This is the server module. It's completely controled by the client module -- by the use of a control connection. ----------------------------------------------------------------------------- @@ -11,16 +11,6 @@ assert(dofile("testcmd.lua")) test_debug_mode() ------------------------------------------------------------------------------ --- Get host and port from command line ------------------------------------------------------------------------------ -HOST = "localhost" -PORT = 2020 -if arg then - HOST = arg[1] or HOST - PORT = arg[2] or PORT -end - ----------------------------------------------------------------------------- -- Start control connection ----------------------------------------------------------------------------- From 33817f147b37d744846c864f5d59c4ea67a2b930 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 12 Mar 2001 19:38:39 +0000 Subject: [PATCH 028/483] Set timeout on data socket, to avoid hangs due to no keepalive packets. --- test/testsrvr.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/testsrvr.lua b/test/testsrvr.lua index 4530014..a11927f 100644 --- a/test/testsrvr.lua +++ b/test/testsrvr.lua @@ -8,6 +8,7 @@ ----------------------------------------------------------------------------- -- Read command definitions ----------------------------------------------------------------------------- +HOST = HOST or "*" assert(dofile("testcmd.lua")) test_debug_mode() @@ -33,6 +34,7 @@ function execute_command(cmd, par) if cmd == CONNECT then print("server: waiting for data connection...") data = server:accept() + data:timeout(10) if not data then fail("server: unable to start data connection!") else From 8ed1b05f86a941d0fe9acc16758eb885847b43e5 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 12 Mar 2001 19:40:29 +0000 Subject: [PATCH 029/483] Corrected command line parameter bug... --- test/testclnt.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/test/testclnt.lua b/test/testclnt.lua index ca67761..1efb51d 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -8,6 +8,7 @@ ----------------------------------------------------------------------------- -- Read command definitions ----------------------------------------------------------------------------- +HOST = HOST or "localhost" assert(dofile("testcmd.lua")) test_debug_mode() From 3f52ed5c533d644ebe49c11905f7f0ee2feaa118 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 12 Mar 2001 19:42:46 +0000 Subject: [PATCH 030/483] Initial revision --- samples/tinyirc.lua | 61 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 samples/tinyirc.lua diff --git a/samples/tinyirc.lua b/samples/tinyirc.lua new file mode 100644 index 0000000..992b235 --- /dev/null +++ b/samples/tinyirc.lua @@ -0,0 +1,61 @@ +function set_add(set, sock) + tinsert(set, sock) +end + +function set_remove(set, sock) + for i = 1, getn(set) do + if set[i] == sock then + tremove(set, i) + break + end + end +end + +host = host or "*" +port1 = port1 or 8080 +port2 = port2 or 8081 +if arg then + host = arg[1] or host + port1 = arg[2] or port1 + port2 = arg[3] or port2 +end + +server1 = bind(host, port1) +server1:timeout(1) +server1.is_server = 1 +server2 = bind(host, port2) +server2:timeout(1) +server2.is_server = 1 + +set = {server1, server2} + +while 1 do + local r, s, e, l + r, _, e = select(set, nil) + for i, v in r do + if v.is_server then + s = v:accept() + if s then + s:timeout(1) + set_add(set, s) + write("Added new client. ", getn(set)-2, " total.\n") + end + else + l, e = v:receive() + if e then + v:close() + set_remove(set, v) + write("Removed client. ", getn(set)-2, " total.\n") + end + write("Broadcasting line '", tostring(l), "'.\n") + _, s, e = select(nil, set, 1) + if not e then + for i,v in s do + v:send(l, "\r\n") + end + else + write("No one ready to listen!!!\n") + end + end + end +end From 98a7e91de5ec69dc451e3a1f19ec47d29028af5a Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 12 Mar 2001 20:00:47 +0000 Subject: [PATCH 031/483] Connection closed detection was improved. Client numbers are now tracked and sent along with broadcasted line. --- samples/tinyirc.lua | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/samples/tinyirc.lua b/samples/tinyirc.lua index 992b235..f38ab73 100644 --- a/samples/tinyirc.lua +++ b/samples/tinyirc.lua @@ -28,34 +28,42 @@ server2:timeout(1) server2.is_server = 1 set = {server1, server2} +number = 1 while 1 do - local r, s, e, l + local r, s, e, l, n r, _, e = select(set, nil) for i, v in r do if v.is_server then s = v:accept() if s then s:timeout(1) + s.number = number + number = number + 1 set_add(set, s) - write("Added new client. ", getn(set)-2, " total.\n") + write("Added client number ", s.number, ". ", + getn(set)-2, " total.\n") end else l, e = v:receive() + n = v.number if e then v:close() set_remove(set, v) - write("Removed client. ", getn(set)-2, " total.\n") - end - write("Broadcasting line '", tostring(l), "'.\n") - _, s, e = select(nil, set, 1) - if not e then - for i,v in s do - v:send(l, "\r\n") - end + write("Removed client number ", n, ". ", + getn(set)-2, " total.\n") else - write("No one ready to listen!!!\n") - end + write("Broadcasting line '", tostring(n), "> ", + tostring(l), "'.\n") + _, s, e = select(nil, set, 1) + if not e then + for i,v in s do + v:send(tostring(n), "> ", l, "\r\n") + end + else + write("No one ready to listen!!!\n") + end + end end end end From 794418cd7b18a812c5b0b9b91eea2fecc2c6387d Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 12 Mar 2001 20:02:21 +0000 Subject: [PATCH 032/483] Added broadcast.lua description. --- samples/README | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/samples/README b/samples/README index 5e7b338..a273af8 100644 --- a/samples/README +++ b/samples/README @@ -31,5 +31,14 @@ servers as well. This module implements file retrieval by the TFTP protocol. Its main use is to test the UDP code, but someone might find it usefull. + broadcast.lua -- Broadcast telnet server + +This is a simple server that waits simultaneously on two server sockets for +telnet connections. Everything it receives from the telnet clients is +broadcast to every other connected client. It tests the select function and +shows how to create a simple server whith LuaSocket. Just run broadcast.lua +and then open as many telnet connections as you want to ports 8080 and +8081. + Good luck, Diego. From f643710fa23c0d1f55a1ca3af432c3654da2e0da Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 12 Mar 2001 20:04:25 +0000 Subject: [PATCH 033/483] Found a new way to define global version of table methods using only ~15 lines of code. So, they are back. Added '*w' word receive pattern. --- src/luasocket.c | 366 +++++++++++++++++++++++++++++------------------- 1 file changed, 222 insertions(+), 144 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index c4f51bd..1f9780d 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -9,9 +9,9 @@ * of the IPv4 Socket layer available to Lua scripts. * The Lua interface to TCP/IP follows the BSD TCP/IP API closely, * trying to simplify all tasks involved in setting up a client connection -* and simple server connections. +* and server connections. * The provided IO routines, send and receive, follow the Lua style, being -* very similar to the read and write functions found in that language. +* very similar to the standard Lua read and write functions. * The module implements both a BSD bind and a Winsock2 bind, and has * been tested on several Unix flavors, as well as Windows 98 and NT. \*=========================================================================*/ @@ -19,16 +19,13 @@ /*=========================================================================*\ * Common include files \*=========================================================================*/ -#include #include #include -#include #include -#include +#include #include #include -#include #include "luasocket.h" @@ -161,6 +158,10 @@ static int global_toip(lua_State *L); static int global_tohostname(lua_State *L); static int global_udpsocket(lua_State *L); +#ifndef LUASOCKET_NOGLOBALS +static int global_calltable(lua_State *L); +#endif + /* luasocket table method API functions */ static int table_tcpaccept(lua_State *L); static int table_tcpsend(lua_State *L); @@ -187,6 +188,7 @@ static void tm_markstart(p_sock sock); /* I/O */ static int send_raw(p_sock sock, const char *data, int wanted, int *err); static int receive_raw(lua_State *L, p_sock sock, int wanted); +static int receive_word(lua_State *L, p_sock sock); static int receive_dosline(lua_State *L, p_sock sock); static int receive_unixline(lua_State *L, p_sock sock); static int receive_all(lua_State *L, p_sock sock); @@ -536,6 +538,21 @@ static int table_udpsendto(lua_State *L) } } +/*-------------------------------------------------------------------------*\ +* Global function that calls corresponding table method. +\*-------------------------------------------------------------------------*/ +#ifndef LUASOCKET_NOGLOBALS +int global_calltable(lua_State *L) +{ + p_tags tags = pop_tags(L); + if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); + lua_gettable(L, 1); + lua_insert(L, 1); + lua_call(L, lua_gettop(L)-1, LUA_MULTRET); + return lua_gettop(L); +} +#endif + /*-------------------------------------------------------------------------*\ * Waits for a set of sockets until a condition is met or timeout. * Lua Input: {input}, {output} [, timeout] @@ -549,103 +566,105 @@ static int table_udpsendto(lua_State *L) \*-------------------------------------------------------------------------*/ int global_select(lua_State *L) { - p_tags tags = pop_tags(L); - int ms = lua_isnil(L, 3) ? -1 : (int) (luaL_opt_number(L, 3, -1) * 1000); + p_tags tags = pop_tags(L); + int ms = lua_isnil(L, 3) ? -1 : (int) (luaL_opt_number(L, 3, -1) * 1000); fd_set readfds, *prfds = NULL, writefds, *pwfds = NULL; struct timeval tm, *ptm = NULL; - int ret, s, max = -1; - int byfds, canread, canwrite; - /* reset the file descriptor sets */ + int ret; + unsigned max = 0; + SOCKET s; + int byfds, canread, canwrite; + /* reset the file descriptor sets */ FD_ZERO(&readfds); FD_ZERO(&writefds); - /* all sockets, indexed by socket number, for internal use */ - lua_newtable(L); byfds = lua_gettop(L); - /* readable sockets table to be returned */ - lua_newtable(L); canread = lua_gettop(L); - /* writable sockets table to be returned */ - lua_newtable(L); canwrite = lua_gettop(L); - /* get sockets we will test for readability into fd_set */ - if (!lua_isnil(L, 1)) { - lua_pushnil(L); - while (lua_next(L, 1)) { - if (lua_tag(L, -1) == tags->table) { - p_sock sock = get_sock(L, -1, tags, NULL); - lua_pushnumber(L, sock->sock); - lua_pushvalue(L, -2); - lua_settable(L, byfds); - if (sock->sock > max) max = sock->sock; - /* a socket can have unread data in our internal buffer. in - * that case, we only call select to find out which of the - * other sockets can be written to or read from immediately. */ - if (!bf_isempty(sock)) { - ms = 0; - lua_pushnumber(L, lua_getn(L, canread) + 1); - lua_pushvalue(L, -2); - lua_settable(L, canread); - } else { - FD_SET(sock->sock, &readfds); - prfds = &readfds; - } - } - /* get rid of lua_next value and expose index */ - lua_pop(L, 1); - } - } - /* get sockets we will test for writability into fd_set */ - if (!lua_isnil(L, 2)) { - lua_pushnil(L); - while (lua_next(L, 2)) { - if (lua_tag(L, -1) == tags->table) { - p_sock sock = get_sock(L, -1, tags, NULL); - lua_pushnumber(L, sock->sock); - lua_pushvalue(L, -2); - lua_settable(L, byfds); - if (sock->sock > max) max = sock->sock; - FD_SET(sock->sock, &writefds); - pwfds = &writefds; - } - /* get rid of lua_next value and expose index */ - lua_pop(L, 1); - } - } - max++; - /* configure timeout value */ + /* all sockets, indexed by socket number, for internal use */ + lua_newtable(L); byfds = lua_gettop(L); + /* readable sockets table to be returned */ + lua_newtable(L); canread = lua_gettop(L); + /* writable sockets table to be returned */ + lua_newtable(L); canwrite = lua_gettop(L); + /* get sockets we will test for readability into fd_set */ + if (!lua_isnil(L, 1)) { + lua_pushnil(L); + while (lua_next(L, 1)) { + if (lua_tag(L, -1) == tags->table) { + p_sock sock = get_sock(L, -1, tags, NULL); + lua_pushnumber(L, sock->sock); + lua_pushvalue(L, -2); + lua_settable(L, byfds); + if (sock->sock > max) max = sock->sock; + /* a socket can have unread data in our internal buffer. in + * that case, we only call select to find out which of the + * other sockets can be written to or read from immediately. */ + if (!bf_isempty(sock)) { + ms = 0; + lua_pushnumber(L, lua_getn(L, canread) + 1); + lua_pushvalue(L, -2); + lua_settable(L, canread); + } else { + FD_SET(sock->sock, &readfds); + prfds = &readfds; + } + } + /* get rid of lua_next value and expose index */ + lua_pop(L, 1); + } + } + /* get sockets we will test for writability into fd_set */ + if (!lua_isnil(L, 2)) { + lua_pushnil(L); + while (lua_next(L, 2)) { + if (lua_tag(L, -1) == tags->table) { + p_sock sock = get_sock(L, -1, tags, NULL); + lua_pushnumber(L, sock->sock); + lua_pushvalue(L, -2); + lua_settable(L, byfds); + if (sock->sock > max) max = sock->sock; + FD_SET(sock->sock, &writefds); + pwfds = &writefds; + } + /* get rid of lua_next value and expose index */ + lua_pop(L, 1); + } + } + max++; + /* configure timeout value */ if (ms >= 0) { - ptm = &tm; /* ptm == NULL when we don't have timeout */ - /* fill timeval structure */ - tm.tv_sec = ms / 1000; - tm.tv_usec = (ms % 1000) * 1000; - } + ptm = &tm; /* ptm == NULL when we don't have timeout */ + /* fill timeval structure */ + tm.tv_sec = ms / 1000; + tm.tv_usec = (ms % 1000) * 1000; + } /* see if we can read, write or if we timedout */ ret = select(max, prfds, pwfds, NULL, ptm); - /* did we timeout? */ - if (ret <= 0 && ms > 0) { - push_error(L, NET_TIMEOUT); - return 3; - } - /* collect readable sockets */ - if (prfds) { - for (s = 0; s < max; s++) { - if (FD_ISSET(s, prfds)) { - lua_pushnumber(L, lua_getn(L, canread) + 1); - lua_pushnumber(L, s); - lua_gettable(L, byfds); - lua_settable(L, canread); - } - } - } - /* collect writable sockets */ - if (pwfds) { - for (s = 0; s < max; s++) { - if (FD_ISSET(s, pwfds)) { - lua_pushnumber(L, lua_getn(L, canwrite) + 1); - lua_pushnumber(L, s); - lua_gettable(L, byfds); - lua_settable(L, canwrite); - } - } - } - lua_pushnil(L); - return 3; + /* did we timeout? */ + if (ret <= 0 && ms > 0) { + push_error(L, NET_TIMEOUT); + return 3; + } + /* collect readable sockets */ + if (prfds) { + for (s = 0; s < max; s++) { + if (FD_ISSET(s, prfds)) { + lua_pushnumber(L, lua_getn(L, canread) + 1); + lua_pushnumber(L, s); + lua_gettable(L, byfds); + lua_settable(L, canread); + } + } + } + /* collect writable sockets */ + if (pwfds) { + for (s = 0; s < max; s++) { + if (FD_ISSET(s, pwfds)) { + lua_pushnumber(L, lua_getn(L, canwrite) + 1); + lua_pushnumber(L, s); + lua_gettable(L, byfds); + lua_settable(L, canwrite); + } + } + } + lua_pushnil(L); + return 3; } /*-------------------------------------------------------------------------*\ @@ -821,7 +840,7 @@ static int table_udpreceive(lua_State *L) \*-------------------------------------------------------------------------*/ static int table_tcpreceive(lua_State *L) { - static const char *const modenames[] = {"*l", "*lu", "*a", NULL}; + static const char *const modenames[] = {"*l", "*lu", "*a", "*w", NULL}; const char *mode; int err = NET_DONE; int arg; @@ -850,17 +869,13 @@ static int table_tcpreceive(lua_State *L) /* get next pattern */ switch (luaL_findstring(mode, modenames)) { /* DOS line mode */ - case 0: - err = receive_dosline(L, sock); - break; + case 0: err = receive_dosline(L, sock); break; /* Unix line mode */ - case 1: - err = receive_unixline(L, sock); - break; + case 1: err = receive_unixline(L, sock); break; /* until closed mode */ - case 2: - err = receive_all(L, sock); - break; + case 2: err = receive_all(L, sock); break; + /* word */ + case 3: err = receive_word(L, sock); break; /* else it is an error */ default: luaL_arg_check(L, 0, arg, "invalid receive pattern"); @@ -1431,7 +1446,7 @@ static int receive_all(lua_State *L, p_sock sock) \*-------------------------------------------------------------------------*/ static int receive_dosline(lua_State *L, p_sock sock) { - int got = 0; + int got, pos; const unsigned char *buffer = NULL; luaL_Buffer b; luaL_buffinit(L, &b); @@ -1441,26 +1456,21 @@ static int receive_dosline(lua_State *L, p_sock sock) return NET_TIMEOUT; } buffer = bf_receive(sock, &got); - if (got > 0) { - int len = 0, end = 1; - while (len < got) { - if (buffer[len] == '\n') { /* found eol */ - if (len > 0 && buffer[len-1] == '\r') { - end++; len--; - } - luaL_addlstring(&b, buffer, len); - bf_skip(sock, len + end); /* skip '\r\n' in stream */ - luaL_pushresult(&b); - return NET_DONE; - } - len++; - } - luaL_addlstring(&b, buffer, got); - bf_skip(sock, got); - } else { - luaL_pushresult(&b); + if (got <= 0) { + luaL_pushresult(&b); return NET_CLOSED; - } + } + pos = 0; + while (pos < got && buffer[pos] != '\n') { + /* we ignore all \r's */ + if (buffer[pos] != '\r') luaL_putchar(&b, buffer[pos]); + pos++; + } + if (pos < got) { + luaL_pushresult(&b); + bf_skip(sock, pos+1); /* skip '\n' too */ + return NET_DONE; + } else bf_skip(sock, pos); } } @@ -1475,7 +1485,7 @@ static int receive_dosline(lua_State *L, p_sock sock) \*-------------------------------------------------------------------------*/ static int receive_unixline(lua_State *L, p_sock sock) { - int got = 0; + int got, pos; const unsigned char *buffer = NULL; luaL_Buffer b; luaL_buffinit(L, &b); @@ -1485,23 +1495,75 @@ static int receive_unixline(lua_State *L, p_sock sock) return NET_TIMEOUT; } buffer = bf_receive(sock, &got); - if (got > 0) { - int len = 0; - while (len < got) { - if (buffer[len] == '\n') { /* found eol */ - luaL_addlstring(&b, buffer, len); - bf_skip(sock, len + 1); /* skip '\n' in stream */ - luaL_pushresult(&b); - return NET_DONE; - } - len++; - } - luaL_addlstring(&b, buffer, got); - bf_skip(sock, got); - } else { + if (got <= 0) { + luaL_pushresult(&b); + return NET_CLOSED; + } + pos = 0; + while (pos < got && buffer[pos] != '\n') pos++; + luaL_addlstring(&b, buffer, pos); + if (pos < got) { + luaL_pushresult(&b); + bf_skip(sock, pos+1); /* skip '\n' too */ + return NET_DONE; + } else bf_skip(sock, pos); + } +} + +/*-------------------------------------------------------------------------*\ +* Reads a word (maximal sequence of non--white-space characters), skipping +* white-spaces if needed. +* Input +* sock: socket structure being used in operation +* Result +* operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED +\*-------------------------------------------------------------------------*/ +static int receive_word(lua_State *L, p_sock sock) +{ + int pos, got; + const unsigned char *buffer = NULL; + luaL_Buffer b; + luaL_buffinit(L, &b); + /* skip leading white-spaces */ + for ( ;; ) { + if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { + lua_pushstring(L, ""); + return NET_TIMEOUT; + } + buffer = bf_receive(sock, &got); + if (got <= 0) { + lua_pushstring(L, ""); + return NET_CLOSED; + } + pos = 0; + while (pos < got && isspace(buffer[pos])) pos++; + bf_skip(sock, pos); + if (pos < got) { + buffer += pos; + got -= pos; + pos = 0; + break; + } + } + /* capture word */ + for ( ;; ) { + while (pos < got && !isspace(buffer[pos])) pos++; + luaL_addlstring(&b, buffer, pos); + bf_skip(sock, pos); + if (pos < got) { + luaL_pushresult(&b); + return NET_DONE; + } + if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { + luaL_pushresult(&b); + return NET_TIMEOUT; + } + buffer = bf_receive(sock, &got); + if (got <= 0) { luaL_pushresult(&b); return NET_CLOSED; } + pos = 0; } } @@ -1514,7 +1576,7 @@ static int receive_unixline(lua_State *L, p_sock sock) \*-------------------------------------------------------------------------*/ void lua_socketlibopen(lua_State *L) { - static struct luaL_reg funcs[] = { + struct luaL_reg funcs[] = { {"bind", global_tcpbind}, {"connect", global_tcpconnect}, {"select", global_select}, @@ -1552,6 +1614,22 @@ void lua_socketlibopen(lua_State *L) lua_pushcfunction(L, global_sleep); lua_setglobal(L, "sleep"); lua_pushcfunction(L, global_time); lua_setglobal(L, "time"); #endif +#ifndef LUASOCKET_NOGLOBALS + { + char *global[] = { + "accept", "close", "getpeername", + "getsockname", "receive", "send", + "receivefrom", "sendto" + }; + unsigned int i; + for (i = 0; i < sizeof(global)/sizeof(char *); i++) { + lua_pushstring(L, global[i]); + lua_pushuserdata(L, tags); + lua_pushcclosure(L, global_calltable, 2); + lua_setglobal(L, global[i]); + } + } +#endif } /*=========================================================================*\ @@ -1583,7 +1661,7 @@ static p_sock push_clienttable(lua_State *L, p_tags tags) if (!sock) return NULL; lua_settag(L, tags->client); lua_settable(L, -3); - sock->sock = -1; + sock->sock = INVALID_SOCKET; sock->is_connected = 0; sock->tm_block = -1; sock->tm_return = -1; From 366fb989f301f88de6cd8516a83b86f54157a126 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 12 Mar 2001 20:10:39 +0000 Subject: [PATCH 034/483] Updated for release 1.2.1. --- makefile.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makefile.dist b/makefile.dist index 1d8df38..2db8f75 100644 --- a/makefile.dist +++ b/makefile.dist @@ -2,7 +2,7 @@ # Distribution makefile #-------------------------------------------------------------------------- -DIST = luasocket-1.2 +DIST = luasocket-1.2.1 SRC = ~diego/tec/luasocket From bd0bf459793be5616a16f3c54fe93654a836b756 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 27 Mar 2001 19:25:11 +0000 Subject: [PATCH 035/483] BUG: multi-line replies were not supported. Error logic simplified. --- src/smtp.lua | 653 +++++++++++++++++++++++++-------------------------- 1 file changed, 315 insertions(+), 338 deletions(-) diff --git a/src/smtp.lua b/src/smtp.lua index f9ed64c..7f9af3d 100644 --- a/src/smtp.lua +++ b/src/smtp.lua @@ -1,338 +1,315 @@ ------------------------------------------------------------------------------ --- Simple SMTP support for the Lua language using the LuaSocket toolkit. --- Author: Diego Nehab --- Date: 26/12/2000 --- Conforming to: RFC 821 ------------------------------------------------------------------------------ - ------------------------------------------------------------------------------ --- Program constants ------------------------------------------------------------------------------ --- timeout in secconds before we give up waiting -local TIMEOUT = 180 --- port used for connection -local PORT = 25 --- domain used in HELO command. If we are under a CGI, try to get from --- environment -local DOMAIN = getenv("SERVER_NAME") -if not DOMAIN then - DOMAIN = "localhost" -end - ------------------------------------------------------------------------------ --- Tries to send DOS mode lines. Closes socket on error. --- Input --- sock: server socket --- line: string to be sent --- Returns --- err: message in case of error, nil if successfull ------------------------------------------------------------------------------ -local puts = function(sock, line) - local err = sock:send(line .. "\r\n") - if err then sock:close() end - return err -end - ------------------------------------------------------------------------------ --- Tries to receive DOS mode lines. Closes socket on error. --- Input --- sock: server socket --- Returns --- line: received string if successfull, nil in case of error --- err: error message if any ------------------------------------------------------------------------------ -local gets = function(sock) - local line, err = sock:receive("*l") - if err then - sock:close() - return nil, err - end - return line -end - ------------------------------------------------------------------------------ --- Gets a reply from the server and close connection if it is wrong --- Input --- sock: server socket --- accept: acceptable errorcodes --- Returns --- code: server reply code. nil if error --- line: complete server reply message or error message ------------------------------------------------------------------------------ -local get_reply = function(sock, accept) - local line, err = %gets(sock) - if line then - if type(accept) ~= "table" then accept = {accept} end - local _,_, code = strfind(line, "^(%d%d%d)") - if not code then return nil, line end - code = tonumber(code) - for i = 1, getn(accept) do - if code == accept[i] then return code, line end - end - sock:close() - return nil, line - end - return nil, err -end - ------------------------------------------------------------------------------ --- Sends a command to the server --- Input --- sock: server socket --- command: command to be sent --- param: command parameters if any --- Returns --- err: error message if any ------------------------------------------------------------------------------ -local send_command = function(sock, command, param) - local line - if param then line = command .. " " .. param - else line = command end - return %puts(sock, line) -end - ------------------------------------------------------------------------------ --- Gets the initial server greeting --- Input --- sock: server socket --- Returns --- code: server status code, nil if error --- answer: complete server reply ------------------------------------------------------------------------------ -local get_helo = function(sock) - return %get_reply(sock, 220) -end - ------------------------------------------------------------------------------ --- Sends initial client greeting --- Input --- sock: server socket --- Returns --- code: server status code, nil if error --- answer: complete server reply ------------------------------------------------------------------------------ -local send_helo = function(sock) - local err = %send_command(sock, "HELO", %DOMAIN) - if not err then - return %get_reply(sock, 250) - else return nil, err end -end - ------------------------------------------------------------------------------ --- Sends mime headers --- Input --- sock: server socket --- mime: table with mime headers to be sent --- Returns --- err: error message if any ------------------------------------------------------------------------------ -local send_mime = function(sock, mime) - local err - mime = mime or {} - -- send all headers - for name,value in mime do - err = sock:send(name .. ": " .. value .. "\r\n") - if err then - sock:close() - return err - end - end - -- end mime part - err = sock:send("\r\n") - if err then sock:close() end - return err -end - ------------------------------------------------------------------------------ --- Sends connection termination command --- Input --- sock: server socket --- Returns --- code: server status code, nil if error --- answer: complete server reply ------------------------------------------------------------------------------ -local send_quit = function(sock) - local code, answer - local err = %send_command(sock, "QUIT") - if not err then - code, answer = %get_reply(sock, 221) - sock:close() - return code, answer - else return nil, err end -end - ------------------------------------------------------------------------------ --- Sends sender command --- Input --- sock: server socket --- sender: e-mail of sender --- Returns --- code: server status code, nil if error --- answer: complete server reply ------------------------------------------------------------------------------ -local send_mail = function(sock, sender) - local param = format("FROM:<%s>", sender) - local err = %send_command(sock, "MAIL", param) - if not err then - return %get_reply(sock, 250) - else return nil, err end -end - ------------------------------------------------------------------------------ --- Sends message mime headers and body --- Input --- sock: server socket --- mime: table containing all mime headers to be sent --- body: message body --- Returns --- code: server status code, nil if error --- answer: complete server reply ------------------------------------------------------------------------------ -local send_data = function (sock, mime, body) - local err = %send_command(sock, "DATA") - if not err then - local code, answer = %get_reply(sock, 354) - if not code then return nil, answer end - -- avoid premature end in message body - body = gsub(body or "", "\n%.", "\n%.%.") - -- mark end of message body - body = body .. "\r\n." - err = %send_mime(sock, mime) - if err then return nil, err end - err = %puts(sock, body) - return %get_reply(sock, 250) - else return nil, err end -end - ------------------------------------------------------------------------------ --- Sends recipient list command --- Input --- sock: server socket --- rcpt: lua table with recipient list --- Returns --- code: server status code, nil if error --- answer: complete server reply ------------------------------------------------------------------------------ -local send_rcpt = function(sock, rcpt) - local err, code, answer - if type(rcpt) ~= "table" then rcpt = {rcpt} end - for i = 1, getn(rcpt) do - err = %send_command(sock, "RCPT", format("TO:<%s>", rcpt[i])) - if not err then - code, answer = %get_reply(sock, {250, 251}) - if not code then return code, answer end - else return nil, err end - end - return code, answer -end - ------------------------------------------------------------------------------ --- Sends verify recipient command --- Input --- sock: server socket --- user: user to be verified --- Returns --- code: server status code, nil if error --- answer: complete server reply ------------------------------------------------------------------------------ -local send_vrfy = function (sock, user) - local err = %send_command(sock, "VRFY", format("<%s>", user)) - if not err then - return %get_reply(sock, {250, 251}) - else return nil, err end -end - ------------------------------------------------------------------------------ --- Connection oriented mail functions ------------------------------------------------------------------------------ -function smtp_connect(server) - local code, answer - -- connect to server - local sock, err = connect(server, %PORT) - if not sock then return nil, err end - sock:timeout(%TIMEOUT) - -- initial server greeting - code, answer = %get_helo(sock) - if not code then return nil, answer end - -- HELO - code, answer = %send_helo(sock) - if not code then return nil, answer end - return sock -end - -function smtp_send(sock, from, rcpt, mime, body) - local code, answer - -- MAIL - code, answer = %send_mail(sock, from) - if not code then return nil, answer end - -- RCPT - code, answer = %send_rcpt(sock, rcpt) - if not code then return nil, answer end - -- DATA - return %send_data(sock, mime, body) -end - -function smtp_close(sock) - -- QUIT - return %send_quit(sock) -end - ------------------------------------------------------------------------------ --- Main mail function --- Input --- from: message sender --- rcpt: table containing message recipients --- mime: table containing mime headers --- body: message body --- server: smtp server to be used --- Returns --- nil if successfull, error message in case of error ------------------------------------------------------------------------------ -function smtp_mail(from, rcpt, mime, body, server) - local sock, err = smtp_connect(server) - if not sock then return err end - local code, answer = smtp_send(sock, from, rcpt, mime, body) - if not code then return answer end - code, answer = smtp_close(sock) - if not code then return answer - else return nil end -end - ---=========================================================================== --- Compatibility functions ---=========================================================================== ------------------------------------------------------------------------------ --- Converts a comma separated list into a Lua table with one entry for each --- list element. --- Input --- str: string containing the list to be converted --- tab: table to be filled with entries --- Returns --- a table t, where t.n is the number of elements with an entry t[i] --- for each element ------------------------------------------------------------------------------ -local fill = function(str, tab) - gsub(str, "([^%s,]+)", function (w) tinsert(%tab, w) end) - return tab -end - ------------------------------------------------------------------------------ --- Client mail function, implementing CGILUA 3.2 interface ------------------------------------------------------------------------------ -function mail(msg) - local rcpt = {} - local mime = {} - mime["Subject"] = msg.subject - mime["To"] = msg.to - mime["From"] = msg.from - %fill(msg.to, rcpt) - if msg.cc then - %fill(msg.cc, rcpt) - mime["Cc"] = msg.cc - end - if msg.bcc then - %fill(msg.bcc, rcpt) - end - rcpt.n = nil - return %smtp_mail(msg.from, rcpt, mime, msg.message, msg.mailserver) -end +----------------------------------------------------------------------------- +-- Simple SMTP support for the Lua language using the LuaSocket toolkit. +-- Author: Diego Nehab +-- Date: 26/12/2000 +-- Conforming to: RFC 821 +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout in secconds before we give up waiting +local TIMEOUT = 180 +-- port used for connection +local PORT = 25 +-- domain used in HELO command. If we are under a CGI, try to get from +-- environment +local DOMAIN = getenv("SERVER_NAME") +if not DOMAIN then + DOMAIN = "localhost" +end + +----------------------------------------------------------------------------- +-- Tries to send DOS mode lines. Closes socket on error. +-- Input +-- sock: server socket +-- line: string to be sent +-- Returns +-- err: message in case of error, nil if successfull +----------------------------------------------------------------------------- +local try_send = function(sock, line) + local err = sock:send(line .. "\r\n") +print(line) + if err then sock:close() end + return err +end + +----------------------------------------------------------------------------- +-- Gets command reply, (accepts multiple-line replies) +-- Input +-- control: control connection socket +-- Returns +-- answer: whole server reply, nil if error +-- code: reply status code or error message +----------------------------------------------------------------------------- +local get_answer = function(control) + local code, lastcode, sep + local line, err = control:receive() + local answer = line + if err then return nil, err end +print(line) + _,_, code, sep = strfind(line, "^(%d%d%d)(.)") + if not code or not sep then return nil, answer end + if sep == "-" then -- answer is multiline + repeat + line, err = control:receive() + if err then return nil, err end +print(line) + _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)") + answer = answer .. "\n" .. line + until code == lastcode and sep == " " -- answer ends with same code + end + return answer, tonumber(code) +end + +----------------------------------------------------------------------------- +-- Checks if a message reply code is correct. Closes control connection +-- if not. +-- Input +-- control: control connection socket +-- success: table with successfull reply status code +-- Returns +-- code: reply code or nil in case of error +-- answer: complete server answer or system error message +----------------------------------------------------------------------------- +local check_answer = function(control, success) + local answer, code = %get_answer(control) + if not answer then + control:close() + return nil, code + end + if type(success) ~= "table" then success = {success} end + for i = 1, getn(success) do + if code == success[i] then + return code, answer + end + end + control:close() + return nil, answer +end + +----------------------------------------------------------------------------- +-- Sends a command to the server (closes sock on error) +-- Input +-- sock: server socket +-- command: command to be sent +-- param: command parameters if any +-- Returns +-- err: error message if any +----------------------------------------------------------------------------- +local send_command = function(sock, command, param) + local line + if param then line = command .. " " .. param + else line = command end + return %try_send(sock, line) +end + +----------------------------------------------------------------------------- +-- Sends initial client greeting +-- Input +-- sock: server socket +-- Returns +-- code: server code if ok, nil if error +-- answer: complete server reply +----------------------------------------------------------------------------- +local send_helo = function(sock) + local err = %send_command(sock, "HELO", %DOMAIN) + if err then return nil, err end + return %check_answer(sock, 250) +end + +----------------------------------------------------------------------------- +-- Sends mime headers +-- Input +-- sock: server socket +-- mime: table with mime headers to be sent +-- Returns +-- err: error message if any +----------------------------------------------------------------------------- +local send_mime = function(sock, mime) + local err + mime = mime or {} + -- send all headers + for name,value in mime do + err = sock:send(name .. ": " .. value .. "\r\n") + if err then + sock:close() + return err + end + end + -- end mime part + err = sock:send("\r\n") + if err then sock:close() end + return err +end + +----------------------------------------------------------------------------- +-- Sends connection termination command +-- Input +-- sock: server socket +-- Returns +-- code: server status code, nil if error +-- answer: complete server reply or error message +----------------------------------------------------------------------------- +local send_quit = function(sock) + local err = %send_command(sock, "QUIT") + if err then return nil, err end + local code, answer = %check_answer(sock, 221) + sock:close() + return code, answer +end + +----------------------------------------------------------------------------- +-- Sends sender command +-- Input +-- sock: server socket +-- sender: e-mail of sender +-- Returns +-- code: server status code, nil if error +-- answer: complete server reply or error message +----------------------------------------------------------------------------- +local send_mail = function(sock, sender) + local param = format("FROM:<%s>", sender) + local err = %send_command(sock, "MAIL", param) + if err then return nil, err end + return %check_answer(sock, 250) +end + +----------------------------------------------------------------------------- +-- Sends message mime headers and body +-- Input +-- sock: server socket +-- mime: table containing all mime headers to be sent +-- body: message body +-- Returns +-- code: server status code, nil if error +-- answer: complete server reply or error message +----------------------------------------------------------------------------- +local send_data = function (sock, mime, body) + local err = %send_command(sock, "DATA") + if err then return nil, err end + local code, answer = %check_answer(sock, 354) + if not code then return nil, answer end + -- avoid premature end in message body + body = gsub(body or "", "\n%.", "\n%.%.") + -- mark end of message body + body = body .. "\r\n." + err = %send_mime(sock, mime) + if err then return nil, err end + err = %try_send(sock, body) + return %check_answer(sock, 250) +end + +----------------------------------------------------------------------------- +-- Sends recipient list command +-- Input +-- sock: server socket +-- rcpt: lua table with recipient list +-- Returns +-- code: server status code, nil if error +-- answer: complete server reply +----------------------------------------------------------------------------- +local send_rcpt = function(sock, rcpt) + local err, code, answer + if type(rcpt) ~= "table" then rcpt = {rcpt} end + for i = 1, getn(rcpt) do + err = %send_command(sock, "RCPT", format("TO:<%s>", rcpt[i])) + if err then return nil, err end + code, answer = %check_answer(sock, {250, 251}) + if not code then return code, answer end + end + return code, answer +end + +----------------------------------------------------------------------------- +-- Connection oriented mail functions +----------------------------------------------------------------------------- +function smtp_connect(server) + local code, answer + -- connect to server + local sock, err = connect(server, %PORT) + if not sock then return nil, err end + sock:timeout(%TIMEOUT) + -- initial server greeting + code, answer = %check_answer(sock, 220) + if not code then return nil, answer end + -- HELO + code, answer = %send_helo(sock) + if not code then return nil, answer end + return sock +end + +function smtp_send(sock, from, rcpt, mime, body) + local code, answer + -- MAIL + code, answer = %send_mail(sock, from) + if not code then return nil, answer end + -- RCPT + code, answer = %send_rcpt(sock, rcpt) + if not code then return nil, answer end + -- DATA + return %send_data(sock, mime, body) +end + +function smtp_close(sock) + -- QUIT + return %send_quit(sock) +end + +----------------------------------------------------------------------------- +-- Main mail function +-- Input +-- from: message sender +-- rcpt: table containing message recipients +-- mime: table containing mime headers +-- body: message body +-- server: smtp server to be used +-- Returns +-- nil if successfull, error message in case of error +----------------------------------------------------------------------------- +function smtp_mail(from, rcpt, mime, body, server) + local sock, err = smtp_connect(server) + if not sock then return err end + local code, answer = smtp_send(sock, from, rcpt, mime, body) + if not code then return answer end + code, answer = smtp_close(sock) + if code then return nil end + return answer +end + +--=========================================================================== +-- Compatibility functions +--=========================================================================== +----------------------------------------------------------------------------- +-- Converts a comma separated list into a Lua table with one entry for each +-- list element. +-- Input +-- str: string containing the list to be converted +-- tab: table to be filled with entries +-- Returns +-- a table t, where t.n is the number of elements with an entry t[i] +-- for each element +----------------------------------------------------------------------------- +local fill = function(str, tab) + gsub(str, "([^%s,]+)", function (w) tinsert(%tab, w) end) + return tab +end + +----------------------------------------------------------------------------- +-- Client mail function, implementing CGILUA 3.2 interface +----------------------------------------------------------------------------- +function mail(msg) + local rcpt = {} + local mime = {} + mime["Subject"] = msg.subject + mime["To"] = msg.to + mime["From"] = msg.from + %fill(msg.to, rcpt) + if msg.cc then + %fill(msg.cc, rcpt) + mime["Cc"] = msg.cc + end + if msg.bcc then %fill(msg.bcc, rcpt) end + rcpt.n = nil + return %smtp_mail(msg.from, rcpt, mime, msg.message, msg.mailserver) +end From 561177a1dd111f05aa3edc4d0b89bf1341627027 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 16 Apr 2001 19:56:33 +0000 Subject: [PATCH 036/483] Some internal functions were not static. Correct select bug that would crash on closed sockets. --- src/luasocket.c | 69 ++++++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index 1f9780d..6512108 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -211,13 +211,13 @@ static char *socket_strerror(void); static char *connect_strerror(void); /* socket auxiliary functions */ -const char *tcp_trybind(p_sock sock, const char *address, +static const char *tcp_trybind(p_sock sock, const char *address, unsigned short port, int backlog); -const char *tcp_tryconnect(p_sock sock, const char *address, +static const char *tcp_tryconnect(p_sock sock, const char *address, unsigned short port); -const char *udp_setpeername(p_sock sock, const char *address, +static const char *udp_setpeername(p_sock sock, const char *address, unsigned short port); -const char *udp_setsockname(p_sock sock, const char *address, +static const char *udp_setsockname(p_sock sock, const char *address, unsigned short port); static void set_reuseaddr(p_sock sock); static void set_blocking(p_sock sock); @@ -586,24 +586,27 @@ int global_select(lua_State *L) if (!lua_isnil(L, 1)) { lua_pushnil(L); while (lua_next(L, 1)) { - if (lua_tag(L, -1) == tags->table) { + if (lua_tag(L, -1) == tags->table) { /* skip strange fields */ p_sock sock = get_sock(L, -1, tags, NULL); - lua_pushnumber(L, sock->sock); - lua_pushvalue(L, -2); - lua_settable(L, byfds); - if (sock->sock > max) max = sock->sock; - /* a socket can have unread data in our internal buffer. in - * that case, we only call select to find out which of the - * other sockets can be written to or read from immediately. */ - if (!bf_isempty(sock)) { - ms = 0; - lua_pushnumber(L, lua_getn(L, canread) + 1); - lua_pushvalue(L, -2); - lua_settable(L, canread); - } else { - FD_SET(sock->sock, &readfds); - prfds = &readfds; - } + if (sock->sock != INVALID_SOCKET) { /* skip closed sockets */ + lua_pushnumber(L, sock->sock); + lua_pushvalue(L, -2); + lua_settable(L, byfds); + if (sock->sock > max) max = sock->sock; + /* a socket can have unread data in our internal + buffer. in that case, we only call select to find + out which of the other sockets can be written to + or read from immediately. */ + if (!bf_isempty(sock)) { + ms = 0; + lua_pushnumber(L, lua_getn(L, canread) + 1); + lua_pushvalue(L, -2); + lua_settable(L, canread); + } else { + FD_SET(sock->sock, &readfds); + prfds = &readfds; + } + } } /* get rid of lua_next value and expose index */ lua_pop(L, 1); @@ -613,14 +616,16 @@ int global_select(lua_State *L) if (!lua_isnil(L, 2)) { lua_pushnil(L); while (lua_next(L, 2)) { - if (lua_tag(L, -1) == tags->table) { + if (lua_tag(L, -1) == tags->table) { /* skip strange fields */ p_sock sock = get_sock(L, -1, tags, NULL); - lua_pushnumber(L, sock->sock); - lua_pushvalue(L, -2); - lua_settable(L, byfds); - if (sock->sock > max) max = sock->sock; - FD_SET(sock->sock, &writefds); - pwfds = &writefds; + if (sock->sock != INVALID_SOCKET) { /* skip closed sockets */ + lua_pushnumber(L, sock->sock); + lua_pushvalue(L, -2); + lua_settable(L, byfds); + if (sock->sock > max) max = sock->sock; + FD_SET(sock->sock, &writefds); + pwfds = &writefds; + } } /* get rid of lua_next value and expose index */ lua_pop(L, 1); @@ -995,7 +1000,7 @@ static void handle_sigpipe(void) * Returns * NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ -const char *tcp_tryconnect(p_sock sock, const char *address, +static const char *tcp_tryconnect(p_sock sock, const char *address, unsigned short port) { struct sockaddr_in remote; @@ -1053,7 +1058,7 @@ void set_reuseaddr(p_sock sock) * Returns * NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ -const char *tcp_trybind(p_sock sock, const char *address, +static const char *tcp_trybind(p_sock sock, const char *address, unsigned short port, int backlog) { struct sockaddr_in local; @@ -1106,7 +1111,7 @@ const char *tcp_trybind(p_sock sock, const char *address, * Returns * NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ -const char *udp_setsockname(p_sock sock, const char *address, +static const char *udp_setsockname(p_sock sock, const char *address, unsigned short port) { struct sockaddr_in local; @@ -1151,7 +1156,7 @@ const char *udp_setsockname(p_sock sock, const char *address, * Returns * NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ -const char *udp_setpeername(p_sock sock, const char *address, +static const char *udp_setpeername(p_sock sock, const char *address, unsigned short port) { struct sockaddr_in local; From 8c6473577da993fb95ccd705214aa9dc11e8ca82 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 23 Apr 2001 22:37:55 +0000 Subject: [PATCH 037/483] Added support to UDP socket options. --- src/luasocket.c | 216 +++++++++++++++++++++++++++++++----------------- 1 file changed, 139 insertions(+), 77 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index 6512108..f144fce 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -159,7 +159,7 @@ static int global_tohostname(lua_State *L); static int global_udpsocket(lua_State *L); #ifndef LUASOCKET_NOGLOBALS -static int global_calltable(lua_State *L); +static int global_callfromtable(lua_State *L); #endif /* luasocket table method API functions */ @@ -219,6 +219,7 @@ static const char *udp_setpeername(p_sock sock, const char *address, unsigned short port); static const char *udp_setsockname(p_sock sock, const char *address, unsigned short port); +static int set_option(lua_State *L, p_sock sock); static void set_reuseaddr(p_sock sock); static void set_blocking(p_sock sock); static void set_nonblocking(p_sock sock); @@ -305,8 +306,6 @@ static int global_tcpconnect(lua_State *L) /*-------------------------------------------------------------------------*\ * Creates a udp socket object and returns it to the Lua script. -* The timeout values are initialized as -1 so that the socket will block -* at any IO operation. * Lua Returns * On success: udp socket * On error: nil, followed by an error message @@ -314,8 +313,16 @@ static int global_tcpconnect(lua_State *L) static int global_udpsocket(lua_State *L) { p_tags tags = pop_tags(L); + int top = lua_gettop(L); p_sock sock = push_udptable(L, tags); if (!sock) return 2; + if (top >= 1 && lua_istable(L, 1)) { + lua_pushnil(L); + while (lua_next(L, 1)) { + if (!set_option(L, sock)) lua_error(L, "invalid socket option"); + lua_pop(L, 1); + } + } return 1; } @@ -483,8 +490,8 @@ static int table_tcpsend(lua_State *L) for (arg = 2; arg <= top; arg++) { /* skip self table */ int sent; size_t wanted; - const char *data = luaL_opt_lstr(L, arg, NULL, &wanted); - if (!data || err != NET_DONE) break; + const char *data = luaL_opt_lstr(L, arg, NULL, &wanted); + if (!data || err != NET_DONE) break; err = send_raw(sock, data, wanted, &sent); total += sent; } @@ -542,14 +549,14 @@ static int table_udpsendto(lua_State *L) * Global function that calls corresponding table method. \*-------------------------------------------------------------------------*/ #ifndef LUASOCKET_NOGLOBALS -int global_calltable(lua_State *L) +int global_callfromtable(lua_State *L) { - p_tags tags = pop_tags(L); - if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); - lua_gettable(L, 1); - lua_insert(L, 1); - lua_call(L, lua_gettop(L)-1, LUA_MULTRET); - return lua_gettop(L); + p_tags tags = pop_tags(L); + if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); + lua_gettable(L, 1); + lua_insert(L, 1); + lua_call(L, lua_gettop(L)-1, LUA_MULTRET); + return lua_gettop(L); } #endif @@ -571,8 +578,8 @@ int global_select(lua_State *L) fd_set readfds, *prfds = NULL, writefds, *pwfds = NULL; struct timeval tm, *ptm = NULL; int ret; - unsigned max = 0; - SOCKET s; + unsigned max = 0; + SOCKET s; int byfds, canread, canwrite; /* reset the file descriptor sets */ FD_ZERO(&readfds); FD_ZERO(&writefds); @@ -588,25 +595,25 @@ int global_select(lua_State *L) while (lua_next(L, 1)) { if (lua_tag(L, -1) == tags->table) { /* skip strange fields */ p_sock sock = get_sock(L, -1, tags, NULL); - if (sock->sock != INVALID_SOCKET) { /* skip closed sockets */ - lua_pushnumber(L, sock->sock); - lua_pushvalue(L, -2); - lua_settable(L, byfds); - if (sock->sock > max) max = sock->sock; - /* a socket can have unread data in our internal - buffer. in that case, we only call select to find - out which of the other sockets can be written to - or read from immediately. */ - if (!bf_isempty(sock)) { - ms = 0; - lua_pushnumber(L, lua_getn(L, canread) + 1); - lua_pushvalue(L, -2); - lua_settable(L, canread); - } else { - FD_SET(sock->sock, &readfds); - prfds = &readfds; - } - } + if (sock->sock != INVALID_SOCKET) { /* skip closed sockets */ + lua_pushnumber(L, sock->sock); + lua_pushvalue(L, -2); + lua_settable(L, byfds); + if (sock->sock > max) max = sock->sock; + /* a socket can have unread data in our internal + buffer. in that case, we only call select to find + out which of the other sockets can be written to + or read from immediately. */ + if (!bf_isempty(sock)) { + ms = 0; + lua_pushnumber(L, lua_getn(L, canread) + 1); + lua_pushvalue(L, -2); + lua_settable(L, canread); + } else { + FD_SET(sock->sock, &readfds); + prfds = &readfds; + } + } } /* get rid of lua_next value and expose index */ lua_pop(L, 1); @@ -618,14 +625,14 @@ int global_select(lua_State *L) while (lua_next(L, 2)) { if (lua_tag(L, -1) == tags->table) { /* skip strange fields */ p_sock sock = get_sock(L, -1, tags, NULL); - if (sock->sock != INVALID_SOCKET) { /* skip closed sockets */ - lua_pushnumber(L, sock->sock); - lua_pushvalue(L, -2); - lua_settable(L, byfds); - if (sock->sock > max) max = sock->sock; - FD_SET(sock->sock, &writefds); - pwfds = &writefds; - } + if (sock->sock != INVALID_SOCKET) { /* skip closed sockets */ + lua_pushnumber(L, sock->sock); + lua_pushvalue(L, -2); + lua_settable(L, byfds); + if (sock->sock > max) max = sock->sock; + FD_SET(sock->sock, &writefds); + pwfds = &writefds; + } } /* get rid of lua_next value and expose index */ lua_pop(L, 1); @@ -1049,6 +1056,61 @@ void set_reuseaddr(p_sock sock) setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)); } +/*-------------------------------------------------------------------------*\ +* Set socket options from a table on top of Lua stack. +* Supports SO_KEEPALIVE, SO_DONTROUTE, SO_BROADCAST, and SO_LINGER options. +* Input +* L: Lua state to use +* sock: socket to set option +* Returns +* 1 if successful, 0 otherwise +\*-------------------------------------------------------------------------*/ +static int set_option(lua_State *L, p_sock sock) +{ + static const char *const optionnames[] = { + "SO_KEEPALIVE", "SO_DONTROUTE", "SO_BROADCAST", "SO_LINGER", NULL + }; + const char *option = lua_tostring(L, -2); + int err; + switch (luaL_findstring(option, optionnames)) { + case 0: { + int bool = (int) lua_tonumber(L, -1); + err = setsockopt(sock->sock, SOL_SOCKET, SO_KEEPALIVE, &bool, + sizeof(bool)); + return err >= 0; + } + case 1: { + int bool = (int) lua_tonumber(L, -1); + err = setsockopt(sock->sock, SOL_SOCKET, SO_DONTROUTE, &bool, + sizeof(bool)); + return err >= 0; + } + case 2: { + int bool = (int) lua_tonumber(L, -1); + err = setsockopt(sock->sock, SOL_SOCKET, SO_BROADCAST, &bool, + sizeof(bool)); + return err >= 0; + } + case 3: { + struct linger linger; + if (!lua_istable(L, -1)) return 0; + lua_pushstring(L, "l_onoff"); + lua_gettable(L, -2); + linger.l_onoff = lua_tonumber(L, -1); + lua_pop(L, 1); + lua_pushstring(L, "l_linger"); + lua_gettable(L, -2); + linger.l_linger = lua_tonumber(L, -1); + lua_pop(L, 1); + err = setsockopt(sock->sock, SOL_SOCKET, SO_LINGER, &linger, + sizeof(linger)); + return err >= 0; + } + default: return 0; + } +} + + /*-------------------------------------------------------------------------*\ * Tries to create a TCP socket and bind it to (address, port) * Input @@ -1462,20 +1524,20 @@ static int receive_dosline(lua_State *L, p_sock sock) } buffer = bf_receive(sock, &got); if (got <= 0) { - luaL_pushresult(&b); + luaL_pushresult(&b); return NET_CLOSED; - } - pos = 0; + } + pos = 0; while (pos < got && buffer[pos] != '\n') { - /* we ignore all \r's */ - if (buffer[pos] != '\r') luaL_putchar(&b, buffer[pos]); - pos++; - } - if (pos < got) { - luaL_pushresult(&b); - bf_skip(sock, pos+1); /* skip '\n' too */ - return NET_DONE; - } else bf_skip(sock, pos); + /* we ignore all \r's */ + if (buffer[pos] != '\r') luaL_putchar(&b, buffer[pos]); + pos++; + } + if (pos < got) { + luaL_pushresult(&b); + bf_skip(sock, pos+1); /* skip '\n' too */ + return NET_DONE; + } else bf_skip(sock, pos); } } @@ -1501,17 +1563,17 @@ static int receive_unixline(lua_State *L, p_sock sock) } buffer = bf_receive(sock, &got); if (got <= 0) { - luaL_pushresult(&b); + luaL_pushresult(&b); return NET_CLOSED; - } - pos = 0; + } + pos = 0; while (pos < got && buffer[pos] != '\n') pos++; - luaL_addlstring(&b, buffer, pos); - if (pos < got) { - luaL_pushresult(&b); - bf_skip(sock, pos+1); /* skip '\n' too */ - return NET_DONE; - } else bf_skip(sock, pos); + luaL_addlstring(&b, buffer, pos); + if (pos < got) { + luaL_pushresult(&b); + bf_skip(sock, pos+1); /* skip '\n' too */ + return NET_DONE; + } else bf_skip(sock, pos); } } @@ -1545,7 +1607,7 @@ static int receive_word(lua_State *L, p_sock sock) bf_skip(sock, pos); if (pos < got) { buffer += pos; - got -= pos; + got -= pos; pos = 0; break; } @@ -1620,20 +1682,20 @@ void lua_socketlibopen(lua_State *L) lua_pushcfunction(L, global_time); lua_setglobal(L, "time"); #endif #ifndef LUASOCKET_NOGLOBALS - { - char *global[] = { - "accept", "close", "getpeername", - "getsockname", "receive", "send", - "receivefrom", "sendto" - }; - unsigned int i; - for (i = 0; i < sizeof(global)/sizeof(char *); i++) { - lua_pushstring(L, global[i]); - lua_pushuserdata(L, tags); - lua_pushcclosure(L, global_calltable, 2); - lua_setglobal(L, global[i]); - } - } + { + char *global[] = { + "accept", "close", "getpeername", + "getsockname", "receive", "send", + "receivefrom", "sendto" + }; + unsigned int i; + for (i = 0; i < sizeof(global)/sizeof(char *); i++) { + lua_pushstring(L, global[i]); + lua_pushuserdata(L, tags); + lua_pushcclosure(L, global_callfromtable, 2); + lua_setglobal(L, global[i]); + } + } #endif } From 7d60e27bea63c4b9228197e400a7edac1118a4b5 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 25 Apr 2001 20:08:21 +0000 Subject: [PATCH 038/483] Transformed in a library, instead of a sample program. --- etc/dict.lua | 102 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 37 deletions(-) diff --git a/etc/dict.lua b/etc/dict.lua index c620cc4..4685ca1 100644 --- a/etc/dict.lua +++ b/etc/dict.lua @@ -1,39 +1,67 @@ --- dict.lua --- simple client for DICT protocol (see http://www.dict.org/) --- shows definitions for each word from stdin. uses only "wn" dictionary. --- if a word is "=", then the rest of the line is sent verbatim as a protocol --- command to the server. - -if verbose then verbose=write else verbose=function()end end - -verbose(">>> connecting to server\n") -local s,e=connect("dict.org",2628) -assert(s,e) -verbose(">>> connected\n") - -while 1 do - local w=read"*w" - if w==nil then break end - if w=="=" then - w=read"*l" - verbose(">>>",w,"\n") - s:send(w,"\r\n") - else - verbose(">>> looking up `",w,"'\n") - s:send("DEFINE wn ",w,"\r\n") - end - while 1 do - local l=s:receive() - if l==nil then break end - if strfind(l,"^[0-9]") then - write("<<< ",l,"\n") - else - write(l,"\n") - end - if strfind(l,"^250") or strfind(l,"^[45]") then break end - end +function get_status(sock, valid) + local line, err = sock:receive() + local code, par + if not line then sock:close() return err end + _, _, code = strfind(line, "^(%d%d%d)") + code = tonumber(code) + if code ~= valid then return code end + if code == 150 then + _,_,_, par = strfind(line, "^(%d%d%d) (%d*)") + par = tonumber(par) + end + return nil, par end -s:send("QUIT\r\n") -verbose("<<< ",s:receive(),"\n") -s:close() +function get_def(sock) + local line, err = sock:receive() + local def = "" + while (not err) and line ~= "." do + def = def .. line .. "\n" + line, err = sock:receive() + end + if err then sock:close() return nil, err + else return def end +end + +function dict_open() + local sock, err = connect("dict.org", 2628) + if not sock then return nil, err end + sock:timeout(10) + local code, par = get_status(sock, 220) + if code then return nil, code end + return sock +end + +function dict_define(sock, word, dict) + dict = dict or "web1913" + sock:send("DEFINE " .. dict .. " " .. word .. "\r\n") + local code, par = get_status(sock, 150) + if code or not par then return nil, code end + local defs = "" + for i = 1, par do + local def + code, par = get_status(sock, 151) + if code then return nil, code end + def, err = get_def(sock) + if not def then return nil, err end + defs = defs .. def .. "\n" + end + code, par = get_status(sock, 250) + if code then return nil, code end + return gsub(defs, "%s%s$", "") +end + +function dict_close(sock) + sock:send("QUIT\r\n") + local code, par = get_status(sock, 221) + sock:close() + return code +end + +function dict_get(word, dict) + local sock, err = dict_open() + if not sock then return nil, err end + local defs, err = dict_define(sock, word, dict) + dict_close(sock) + return defs, err +end From f68b4dd1361c1932f544283cb349de91831c8e7a Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 25 Apr 2001 20:11:03 +0000 Subject: [PATCH 039/483] New description for the dict.lua file. --- samples/README | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/samples/README b/samples/README index a273af8..e295035 100644 --- a/samples/README +++ b/samples/README @@ -11,9 +11,11 @@ printed by listen.lua. dict.lua -- dict client -The dict.lua module is a cool simple client for the DICT protocol, -written by Luiz Henrique Figueiredo. Just run it and enter a few words -to see it working. +The dict.lua module was a cool simple client for the DICT protocol, +written by Luiz Henrique Figueiredo. This new version has been converted +into a library, similar to the HTTP and FTP libraries, that can be used +from within any luasocket application. Take a look on the source code +and you will be able to figure out how to use it. daytimeclnt.lua -- day time client From f1ae9db45e692d9b143fc0eefb36b9ad2fe32f69 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 21 May 2001 18:12:20 +0000 Subject: [PATCH 040/483] HTTP message bodies are transfered using a callback to return body parts chunk by chunk. --- src/http.lua | 328 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 207 insertions(+), 121 deletions(-) diff --git a/src/http.lua b/src/http.lua index 7212728..d1e4894 100644 --- a/src/http.lua +++ b/src/http.lua @@ -14,7 +14,7 @@ local TIMEOUT = 60 -- default port for document retrieval local PORT = 80 -- user agent field sent in request -local USERAGENT = "LuaSocket 1.2 HTTP 1.1" +local USERAGENT = "LuaSocket 1.3 HTTP 1.1" ----------------------------------------------------------------------------- -- Tries to get a pattern from the server and closes socket on error @@ -86,7 +86,7 @@ end -- all name_i are lowercase -- nil and error message in case of error ----------------------------------------------------------------------------- -local get_headers = function(sock, headers) +local get_hdrs = function(sock, headers) local line, err local name, value -- get first line @@ -121,63 +121,120 @@ end -- Receives a chunked message body -- Input -- sock: socket connected to the server +-- callback: function to receive chunks -- Returns --- body: a string containing the body of the message --- nil and error message in case of error +-- nil if successfull or an error message in case of error ----------------------------------------------------------------------------- -local try_getchunked = function(sock) - local chunk_size, line, err - local body = "" +local try_getchunked = function(sock, callback) + local chunk, size, line, err repeat -- get chunk size, skip extention line, err = %try_get(sock) - if err then return nil, err end - chunk_size = tonumber(gsub(line, ";.*", ""), 16) - if not chunk_size then + if err then + callback(nil, err) + return err + end + size = tonumber(gsub(line, ";.*", ""), 16) + if not size then sock:close() - return nil, "invalid chunk size" + callback(nil, "invalid chunk size") + return "invalid chunk size" end -- get chunk - line, err = %try_get(sock, chunk_size) - if err then return nil, err end - -- concatenate new chunk - body = body .. line + chunk, err = %try_get(sock, size) + if err then + callback(nil, err) + return err + end + -- pass chunk to callback + if not callback(chunk) then + sock:close() + return "aborted by callback" + end -- skip blank line _, err = %try_get(sock) - if err then return nil, err end - until chunk_size <= 0 - return body + if err then + callback(nil, err) + return err + end + until size <= 0 + -- let callback know we are done + callback("", "done") end ----------------------------------------------------------------------------- --- Receives http body +-- Receives a message body by content-length -- Input -- sock: socket connected to the server --- headers: response header fields +-- callback: function to receive chunks -- Returns --- body: a string containing the body of the document --- nil and error message in case of error --- Obs: --- headers: headers might be modified by chunked transfer +-- nil if successfull or an error message in case of error ----------------------------------------------------------------------------- -local get_body = function(sock, headers) - local body, err - if headers["transfer-encoding"] == "chunked" then - body, err = %try_getchunked(sock) - if err then return nil, err end - -- store extra entity headers - --_, err = %get_headers(sock, headers) - --if err then return nil, err end - elseif headers["content-length"] then - body, err = %try_get(sock, tonumber(headers["content-length"])) - if err then return nil, err end +local try_getbylength = function(sock, length, callback) + while length > 0 do + local size = min(4096, length) + local chunk, err = sock:receive(size) + if err then + callback(nil, err) + return err + end + if not callback(chunk) then + sock:close() + return "aborted by callback" + end + length = length - size + end + callback("", "done") +end + +----------------------------------------------------------------------------- +-- Receives a message body by content-length +-- Input +-- sock: socket connected to the server +-- callback: function to receive chunks +-- Returns +-- nil if successfull or an error message in case of error +----------------------------------------------------------------------------- +local try_getuntilclosed = function(sock, callback) + local err + while 1 do + local chunk, err = sock:receive(4096) + if err == "closed" or not err then + if not callback(chunk) then + sock:close() + return "aborted by callback" + end + if err then break end + else + callback(nil, err) + return err + end + end + callback("", "done") +end + +----------------------------------------------------------------------------- +-- Receives http response body +-- Input +-- sock: socket connected to the server +-- resp_hdrs: response header fields +-- callback: function to receive chunks +-- Returns +-- nil if successfull or an error message in case of error +----------------------------------------------------------------------------- +local try_getbody = function(sock, resp_hdrs, callback) + local err + if resp_hdrs["transfer-encoding"] == "chunked" then + -- get by chunked transfer-coding of message body + return %try_getchunked(sock, callback) + elseif tonumber(resp_hdrs["content-length"]) then + -- get by content-length + local length = tonumber(resp_hdrs["content-length"]) + return %try_getbylength(sock, length, callback) else - -- get it all until connection closes! - body, err = %try_get(sock, "*a") - if err then return nil, err end + -- get it all until connection closes + return %try_getuntilclosed(sock, callback) end - -- return whole body - return body end ----------------------------------------------------------------------------- @@ -218,58 +275,52 @@ local split_url = function(url, default) return parsed end ------------------------------------------------------------------------------ --- Tries to send request body, using chunked transfer-encoding --- Apache, for instance, accepts only 8kb of body in a post to a CGI script --- if we use only the content-length header field... --- Input --- sock: socket connected to the server --- body: body to be sent in request --- Returns --- err: nil in case of success, error message otherwise ------------------------------------------------------------------------------ -local try_sendchunked = function(sock, body) - local wanted = strlen(body) - local first = 1 - local chunk_size - local err - while wanted > 0 do - chunk_size = min(wanted, 1024) - err = %try_send(sock, format("%x\r\n", chunk_size)) - if err then return err end - err = %try_send(sock, strsub(body, first, first + chunk_size - 1)) - if err then return err end - err = %try_send(sock, "\r\n") - if err then return err end - wanted = wanted - chunk_size - first = first + chunk_size - end - err = %try_send(sock, "0\r\n") - return err -end - ----------------------------------------------------------------------------- -- Sends a http request message through socket -- Input -- sock: socket connected to the server -- method: request method to be used -- path: url path --- headers: request headers to be sent --- body: request message body, if any +-- req_hdrs: request headers to be sent +-- callback: callback to send request message body -- Returns -- err: nil in case of success, error message otherwise ----------------------------------------------------------------------------- -local send_request = function(sock, method, path, headers, body) - local err = %try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") +local send_request = function(sock, method, path, req_hdrs, callback) + local chunk, size, done + -- send request line + local err = %try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") if err then return err end - for i, v in headers do + -- send request headers + for i, v in req_hdrs do err = %try_send(sock, i .. ": " .. v .. "\r\n") if err then return err end end + -- if there is a request message body, add content-length header + if callback then + chunk, size = callback() + if chunk and size then + err = %try_send(sock, "content-length: "..tostring(size).."\r\n") + if err then return err end + else + sock:close() + return size or "invalid callback return" + end + end + -- mark end of request headers err = %try_send(sock, "\r\n") - --if not err and body then err = %try_sendchunked(sock, body) end - if not err and body then err = %try_send(sock, body) end - return err + if err then return err end + -- send message request body, getting it chunk by chunk from callback + if callback then + done = 0 + while chunk and chunk ~= "" and done < size do + err = %try_send(sock, chunk) + if err then return err end + done = done + strlen(chunk) + chunk, err = callback() + end + if not chunk then return err end + end end ----------------------------------------------------------------------------- @@ -280,13 +331,18 @@ end -- Returns -- 1 if a message body should be processed, nil otherwise ----------------------------------------------------------------------------- -function has_responsebody(method, code) +function has_respbody(method, code) if method == "HEAD" then return nil end if code == 204 or code == 304 then return nil end if code >= 100 and code < 200 then return nil end return 1 end +----------------------------------------------------------------------------- +-- We need base64 convertion routines for Basic Authentication Scheme +----------------------------------------------------------------------------- +dofile("base64.lua") + ----------------------------------------------------------------------------- -- Converts field names to lowercase and add message body size specification -- Input @@ -296,14 +352,12 @@ end -- Returns -- lower: a table with the same headers, but with lowercase field names ----------------------------------------------------------------------------- -local fill_headers = function(headers, parsed, body) +local fill_hdrs = function(headers, parsed, body) local lower = {} headers = headers or {} for i,v in headers do lower[strlower(i)] = v end - --if body then lower["transfer-encoding"] = "chunked" end - if body then lower["content-length"] = tostring(strlen(body)) end lower["connection"] = "close" lower["host"] = parsed.host lower["user-agent"] = %USERAGENT @@ -315,9 +369,58 @@ local fill_headers = function(headers, parsed, body) end ----------------------------------------------------------------------------- --- We need base64 convertion routines for Basic Authentication Scheme +-- Sends a HTTP request and retrieves the server reply using callbacks to +-- send the request body and receive the response body +-- Input +-- method: "GET", "PUT", "POST" etc +-- url: target uniform resource locator +-- req_hdrs: request headers to send +-- req_body: function to return request message body +-- resp_body: function to receive response message body +-- Returns +-- resp_hdrs: response header fields received, if sucessfull +-- resp_line: server response status line, if successfull +-- err: error message if any ----------------------------------------------------------------------------- -dofile("base64.lua") +function http_requestindirect(method, url, req_hdrs, req_body, resp_body) + local sock, err + local resp_hdrs + local resp_line, resp_code + -- get url components + local parsed = %split_url(url, {port = %PORT, path ="/"}) + -- methods are case sensitive + method = strupper(method) + -- fill default headers + req_hdrs = %fill_hdrs(req_hdrs, parsed) + -- try connection + sock, err = connect(parsed.host, parsed.port) + if not sock then return nil, nil, err end + -- set connection timeout + sock:timeout(%TIMEOUT) + -- send request + err = %send_request(sock, method, parsed.path, req_hdrs, req_body) + if err then return nil, nil, err end + -- get server message + resp_code, resp_line, err = %get_status(sock) + if err then return nil, nil, err end + -- deal with reply + resp_hdrs, err = %get_hdrs(sock, {}) + if err then return nil, line, err end + -- did we get a redirect? should we automatically retry? + if (resp_code == 301 or resp_code == 302) and + (method == "GET" or method == "HEAD") then + sock:close() + return http_requestindirect(method, resp_hdrs["location"], req_hdrs, + req_body, resp_body) + end + -- get body if status and method combination allow one + if has_respbody(method, resp_code) then + err = %try_getbody(sock, resp_hdrs, resp_body) + if err then return resp_hdrs, resp_line, err end + end + sock:close() + return resp_hdrs, resp_line +end ----------------------------------------------------------------------------- -- Sends a HTTP request and retrieves the server reply @@ -329,46 +432,29 @@ dofile("base64.lua") -- Returns -- resp_body: response message body, if successfull -- resp_hdrs: response header fields received, if sucessfull --- line: server response status line, if successfull +-- resp_line: server response status line, if successfull -- err: error message if any ----------------------------------------------------------------------------- -function http_request(method, url, headers, body) - local sock, err - local resp_hdrs, response_body - local line, code - -- get url components - local parsed = %split_url(url, {port = %PORT, path ="/"}) - -- methods are case sensitive - method = strupper(method) - -- fill default headers - headers = %fill_headers(headers, parsed, body) - -- try connection - sock, err = connect(parsed.host, parsed.port) - if not sock then return nil, nil, nil, err end - -- set connection timeout - sock:timeout(%TIMEOUT) - -- send request - err = %send_request(sock, method, parsed.path, headers, body) - if err then return nil, nil, nil, err end - -- get server message - code, line, err = %get_status(sock) - if err then return nil, nil, nil, err end - -- deal with reply - resp_hdrs, err = %get_headers(sock, {}) - if err then return nil, nil, line, err end - -- get body if status and method allow one - if has_responsebody(method, code) then - resp_body, err = %get_body(sock, resp_hdrs) - if err then return nil, resp_hdrs, line, err end +function http_request(method, url, req_hdrs, body) + local resp_hdrs, resp_line, err + local req_callback = function() + return %body, strlen(%body) end - sock:close() - -- should we automatically retry? - if (code == 301 or code == 302) then - if (method == "GET" or method == "HEAD") and resp_hdrs["location"] then - return http_request(method, resp_hdrs["location"], headers, body) - else return nil, resp_hdrs, line end - end - return resp_body, resp_hdrs, line + local resp_aux = { resp_body = "" } + local resp_callback = function(chunk, err) + if not chunk then + %resp_aux.resp_body = nil + %resp_aux.err = err + return nil + end + %resp_aux.resp_body = %resp_aux.resp_body .. chunk + return 1 + end + if not body then resp_callback = nil end + resp_hdrs, resp_line, err = http_requestindirect(method, url, req_hdrs, + req_callback, resp_callback) + if err then return nil, resp_hdrs, resp_line, err + else return resp_aux.resp_body, resp_hdrs, resp_line, resp_aux.err end end ----------------------------------------------------------------------------- From 791c9f3168fc1bf86eb1c6b70393a5391de56439 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 4 Jun 2001 20:44:39 +0000 Subject: [PATCH 041/483] Select was also crashing on non-table parameters. Changed time to _time and sleep to _sleep to avoid name clashes. --- src/luasocket.c | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index f144fce..9abfa09 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -316,12 +316,14 @@ static int global_udpsocket(lua_State *L) int top = lua_gettop(L); p_sock sock = push_udptable(L, tags); if (!sock) return 2; - if (top >= 1 && lua_istable(L, 1)) { - lua_pushnil(L); - while (lua_next(L, 1)) { - if (!set_option(L, sock)) lua_error(L, "invalid socket option"); - lua_pop(L, 1); - } + if (top >= 1 ) { + if (lua_istable(L, 1)) { + lua_pushnil(L); + while (lua_next(L, 1)) { + if (!set_option(L, sock)) lua_error(L, "error setting option"); + lua_pop(L, 1); + } + } else luaL_argerror(L, 1, "invalid options"); } return 1; } @@ -590,7 +592,7 @@ int global_select(lua_State *L) /* writable sockets table to be returned */ lua_newtable(L); canwrite = lua_gettop(L); /* get sockets we will test for readability into fd_set */ - if (!lua_isnil(L, 1)) { + if (lua_istable(L, 1)) { lua_pushnil(L); while (lua_next(L, 1)) { if (lua_tag(L, -1) == tags->table) { /* skip strange fields */ @@ -620,7 +622,7 @@ int global_select(lua_State *L) } } /* get sockets we will test for writability into fd_set */ - if (!lua_isnil(L, 2)) { + if (lua_istable(L, 2)) { lua_pushnil(L); while (lua_next(L, 2)) { if (lua_tag(L, -1) == tags->table) { /* skip strange fields */ @@ -1070,23 +1072,31 @@ static int set_option(lua_State *L, p_sock sock) static const char *const optionnames[] = { "SO_KEEPALIVE", "SO_DONTROUTE", "SO_BROADCAST", "SO_LINGER", NULL }; - const char *option = lua_tostring(L, -2); + const char *option; int err; + if (!lua_isstring(L, -2)) return 0; + option = lua_tostring(L, -2); switch (luaL_findstring(option, optionnames)) { case 0: { - int bool = (int) lua_tonumber(L, -1); + int bool; + if (!lua_isnumber(L, -1)) return 0; + bool = (int) lua_tonumber(L, -1); err = setsockopt(sock->sock, SOL_SOCKET, SO_KEEPALIVE, &bool, sizeof(bool)); return err >= 0; } case 1: { - int bool = (int) lua_tonumber(L, -1); + int bool; + if (!lua_isnumber(L, -1)) return 0; + bool = (int) lua_tonumber(L, -1); err = setsockopt(sock->sock, SOL_SOCKET, SO_DONTROUTE, &bool, sizeof(bool)); return err >= 0; } case 2: { - int bool = (int) lua_tonumber(L, -1); + int bool; + if (!lua_isnumber(L, -1)) return 0; + bool = (int) lua_tonumber(L, -1); err = setsockopt(sock->sock, SOL_SOCKET, SO_BROADCAST, &bool, sizeof(bool)); return err >= 0; @@ -1096,10 +1106,12 @@ static int set_option(lua_State *L, p_sock sock) if (!lua_istable(L, -1)) return 0; lua_pushstring(L, "l_onoff"); lua_gettable(L, -2); + if (!lua_isnumber(L, -1)) return 0; linger.l_onoff = lua_tonumber(L, -1); lua_pop(L, 1); lua_pushstring(L, "l_linger"); lua_gettable(L, -2); + if (!lua_isnumber(L, -1)) return 0; linger.l_linger = lua_tonumber(L, -1); lua_pop(L, 1); err = setsockopt(sock->sock, SOL_SOCKET, SO_LINGER, &linger, @@ -1678,8 +1690,8 @@ void lua_socketlibopen(lua_State *L) #endif #ifdef _DEBUG /* test support functions */ - lua_pushcfunction(L, global_sleep); lua_setglobal(L, "sleep"); - lua_pushcfunction(L, global_time); lua_setglobal(L, "time"); + lua_pushcfunction(L, global_sleep); lua_setglobal(L, "_sleep"); + lua_pushcfunction(L, global_time); lua_setglobal(L, "_time"); #endif #ifndef LUASOCKET_NOGLOBALS { From 77090c53fe558c48680516410a928bece08dd0d5 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 4 Jun 2001 20:45:17 +0000 Subject: [PATCH 042/483] Updated for 1.3b version. --- src/luasocket.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/luasocket.h b/src/luasocket.h index 3c6578c..4402c02 100644 --- a/src/luasocket.h +++ b/src/luasocket.h @@ -8,7 +8,7 @@ #define _LUASOCKET_H_ /* Current luasocket version */ -#define LUASOCKET_VERSION "LuaSocket 1.2.1" +#define LUASOCKET_VERSION "LuaSocket 1.3b" /*-------------------------------------------------------------------------*\ * These can be changed to according to the applications' needs. From c53ad62b00b9dbf2a54214c2c6bf3f06d7c43eea Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 6 Jun 2001 20:55:45 +0000 Subject: [PATCH 043/483] Streaming by callbacks implemented. --- src/ftp.lua | 173 ++++++++++++++++++----- src/http.lua | 387 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 365 insertions(+), 195 deletions(-) diff --git a/src/ftp.lua b/src/ftp.lua index a9dac71..229bbc6 100644 --- a/src/ftp.lua +++ b/src/ftp.lua @@ -15,6 +15,8 @@ local PORT = 21 -- this is the default anonymous password. used when no password is -- provided in url. should be changed for your e-mail. local EMAIL = "anonymous@anonymous.org" +-- block size used in transfers +local BLOCKSIZE = 4096 ----------------------------------------------------------------------------- -- Parses a url and returns its scheme, user, password, host, port @@ -68,7 +70,7 @@ local get_pasv = function(pasv) local ip, port _,_, a, b, c, d, p1, p2 = strfind(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)") - if not a or not b or not c or not d or not p1 or not p2 then + if not (a and b and c and d and p1 and p2) then return nil, nil end ip = format("%d.%d.%d.%d", a, b, c, d) @@ -82,6 +84,8 @@ end -- control: control connection socket -- cmd: command -- arg: command argument if any +-- Returns +-- error message in case of error, nil otherwise ----------------------------------------------------------------------------- local send_command = function(control, cmd, arg) local line, err @@ -283,6 +287,24 @@ local logout = function(control) return code, answer end +----------------------------------------------------------------------------- +-- Receives data and send it to a callback +-- Input +-- data: data connection +-- callback: callback to return file contents +-- Returns +-- nil if successfull, or an error message in case of error +----------------------------------------------------------------------------- +local receive_indirect = function(data, callback) + local chunk, err, res + while not err do + chunk, err = data:receive(%BLOCKSIZE) + if err == "closed" then err = "done" end + res = callback(chunk, err) + if not res then break end + end +end + ----------------------------------------------------------------------------- -- Retrieves file or directory listing -- Input @@ -290,29 +312,60 @@ end -- server: server socket bound to local address -- file: file name under current directory -- isdir: is file a directory name? +-- callback: callback to receive file contents -- Returns --- file: string with file contents, nil if error --- answer: server answer or error message +-- err: error message in case of error, nil otherwise ----------------------------------------------------------------------------- -local retrieve_file = function(control, server, file, isdir) +local retrieve = function(control, server, file, isdir, callback) + local code, answer local data -- ask server for file or directory listing accordingly if isdir then code, answer = %try_command(control, "nlst", file, {150, 125}) else code, answer = %try_command(control, "retr", file, {150, 125}) end data, answer = server:accept() server:close() - if not data then return nil, answer end - -- download whole file - file, err = data:receive("*a") - data:close() - if err then + if not data then control:close() - return nil, err + return answer end + answer = %receive_indirect(data, callback) + if answer then + control:close() + return answer + end + data:close() -- make sure file transfered ok code, answer = %check_answer(control, {226, 250}) - if not code then return nil, answer - else return file, answer end + if not code then return answer end +end + +----------------------------------------------------------------------------- +-- Sends data comming from a callback +-- Input +-- data: data connection +-- send_cb: callback to produce file contents +-- chunk, size: first callback results +-- Returns +-- nil if successfull, or an error message in case of error +----------------------------------------------------------------------------- +local try_sendindirect = function(data, send_cb, chunk, size) + local sent, err + sent = 0 + while 1 do + if type(chunk) ~= "string" or type(size) ~= "number" then + data:close() + if not chunk and type(size) == "string" then return size + else return "invalid callback return" end + end + err = data:send(chunk) + if err then + data:close() + return err + end + sent = sent + strlen(chunk) + if sent >= size then break end + chunk, size = send_cb() + end end ----------------------------------------------------------------------------- @@ -321,29 +374,34 @@ end -- control: control connection with server -- server: server socket bound to local address -- file: file name under current directory --- bytes: file contents in string +-- send_cb: callback to produce the file contents -- Returns -- code: return code, nil if error -- answer: server answer or error message ----------------------------------------------------------------------------- -local store_file = function (control, server, file, bytes) +local store = function(control, server, file, send_cb) local data local code, answer = %try_command(control, "stor", file, {150, 125}) if not code then - data:close() + control:close() return nil, answer end + -- start data connection data, answer = server:accept() server:close() - if not data then return nil, answer end - -- send whole file and close connection to mark file end - answer = data:send(bytes) - data:close() - if answer then + if not data then control:close() - return nil, answer + return nil, answer end - -- check if file was received right + -- send whole file + err = %try_sendindirect(data, send_cb, send_cb()) + if err then + control:close() + return nil, err + end + -- close connection to inform that file transmission is complete + data:close() + -- check if file was received correctly return %check_answer(control, {226, 250}) end @@ -365,55 +423,53 @@ end -- Retrieve a file from a ftp server -- Input -- url: file location +-- receive_cb: callback to receive file contents -- type: "binary" or "ascii" -- Returns --- file: downloaded file or nil in case of error -- err: error message if any ----------------------------------------------------------------------------- -function ftp_get(url, type) +function ftp_getindirect(url, receive_cb, type) local control, server, data, err local answer, code, server, pfile, file parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) -- start control connection control, err = connect(parsed.host, parsed.port) - if not control then return nil, err end + if not control then return err end control:timeout(%TIMEOUT) -- get and check greeting code, answer = %check_greeting(control) - if not code then return nil, answer end + if not code then return answer end -- try to log in code, answer = %login(control, parsed.user, parsed.pass) - if not code then return nil, answer end + if not code then return answer end -- go to directory pfile = %split_path(parsed.path) - if not pfile then return nil, "invalid path" end + if not pfile then return "invalid path" end code, answer = %cwd(control, pfile.path) - if not code then return nil, answer end + if not code then return answer end -- change to binary type? code, answer = %change_type(control, type) - if not code then return nil, answer end + if not code then return answer end -- setup passive connection server, answer = %port(control) - if not server then return nil, answer end + if not server then return answer end -- ask server to send file or directory listing - file, answer = %retrieve_file(control, server, pfile.name, pfile.isdir) - if not file then return nil, answer end + err = %retrieve(control, server, pfile.name, pfile.isdir, receive_cb) + if err then return err end -- disconnect %logout(control) - -- return whatever file we received plus a possible error message - return file, answer end ----------------------------------------------------------------------------- -- Uploads a file to a FTP server -- Input -- url: file location --- bytes: file contents +-- send_cb: callback to produce the file contents -- type: "binary" or "ascii" -- Returns -- err: error message if any ----------------------------------------------------------------------------- -function ftp_put(url, bytes, type) +function ftp_putindirect(url, send_cb, type) local control, data local answer, code, server, file, pfile parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) @@ -439,10 +495,51 @@ function ftp_put(url, bytes, type) server, answer = %port(control) if not server then return answer end -- ask server to send file - code, answer = %store_file(control, server, pfile.name, bytes) + code, answer = %store(control, server, pfile.name, send_cb) if not code then return answer end -- disconnect %logout(control) -- no errors return nil end + +----------------------------------------------------------------------------- +-- Uploads a file to a FTP server +-- Input +-- url: file location +-- bytes: file contents +-- type: "binary" or "ascii" +-- Returns +-- err: error message if any +----------------------------------------------------------------------------- +function ftp_put(url, bytes, type) + local send_cb = function() + return %bytes, strlen(%bytes) + end + return ftp_putindirect(url, send_cb, type) +end + +----------------------------------------------------------------------------- +-- We need fast concatenation routines for direct requests +----------------------------------------------------------------------------- +dofile("buffer.lua") + +----------------------------------------------------------------------------- +-- Retrieve a file from a ftp server +-- Input +-- url: file location +-- type: "binary" or "ascii" +-- Returns +-- data: file contents as a string +-- err: error message in case of error, nil otherwise +----------------------------------------------------------------------------- +function ftp_get(url, type) + local bytes = { buf = buf_create() } + local receive_cb = function(chunk, err) + if not chunk then %bytes.buf = nil end + buf_addstring(%bytes.buf, chunk) + return 1 + end + err = ftp_getindirect(url, receive_cb, type) + return buf_getresult(bytes.buf), err +end diff --git a/src/http.lua b/src/http.lua index d1e4894..38d54d0 100644 --- a/src/http.lua +++ b/src/http.lua @@ -14,7 +14,9 @@ local TIMEOUT = 60 -- default port for document retrieval local PORT = 80 -- user agent field sent in request -local USERAGENT = "LuaSocket 1.3 HTTP 1.1" +local USERAGENT = "LuaSocket 1.3b HTTP 1.1" +-- block size used in transfers +local BLOCKSIZE = 4096 ----------------------------------------------------------------------------- -- Tries to get a pattern from the server and closes socket on error @@ -26,7 +28,7 @@ local USERAGENT = "LuaSocket 1.3 HTTP 1.1" ----------------------------------------------------------------------------- local try_get = function(...) local sock = arg[1] - local data, err = call(sock.receive, arg) + local data, err = call(sock.receive, arg) if err then sock:close() return nil, err @@ -79,14 +81,14 @@ end -- Receive and parse responce header fields -- Input -- sock: socket connected to the server --- headers: a table that might already contain headers +-- hdrs: a table that might already contain headers -- Returns --- headers: a table with all headers fields in the form +-- hdrs: a table with all headers fields in the form -- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"} -- all name_i are lowercase -- nil and error message in case of error ----------------------------------------------------------------------------- -local get_hdrs = function(sock, headers) +local get_hdrs = function(sock, hdrs) local line, err local name, value -- get first line @@ -111,106 +113,114 @@ local get_hdrs = function(sock, headers) if err then return nil, err end end -- save pair in table - if headers[name] then headers[name] = headers[name] .. ", " .. value - else headers[name] = value end + if hdrs[name] then hdrs[name] = hdrs[name] .. ", " .. value + else hdrs[name] = value end end - return headers + return hdrs end ----------------------------------------------------------------------------- -- Receives a chunked message body -- Input -- sock: socket connected to the server --- callback: function to receive chunks +-- receive_cb: function to receive chunks -- Returns -- nil if successfull or an error message in case of error ----------------------------------------------------------------------------- -local try_getchunked = function(sock, callback) - local chunk, size, line, err +local try_getchunked = function(sock, receive_cb) + local chunk, size, line, err, go, uerr, _ repeat -- get chunk size, skip extention line, err = %try_get(sock) if err then - callback(nil, err) - return err - end + local _, uerr = receive_cb(nil, err) + return uerr or err + end size = tonumber(gsub(line, ";.*", ""), 16) if not size then + err = "invalid chunk size" sock:close() - callback(nil, "invalid chunk size") - return "invalid chunk size" + _, uerr = receive_cb(nil, err) + return uerr or err end -- get chunk chunk, err = %try_get(sock, size) if err then - callback(nil, err) - return err - end - -- pass chunk to callback - if not callback(chunk) then - sock:close() - return "aborted by callback" - end + _, uerr = receive_cb(nil, err) + return uerr or err + end + -- pass chunk to callback + go, uerr = receive_cb(chunk) + if not go then + sock:close() + return uerr or "aborted by callback" + end -- skip blank line _, err = %try_get(sock) if err then - callback(nil, err) - return err - end + _, uerr = receive_cb(nil, err) + return uerr or err + end until size <= 0 - -- let callback know we are done - callback("", "done") + -- let callback know we are done + _, uerr = receive_cb("") + return uerr end ----------------------------------------------------------------------------- -- Receives a message body by content-length -- Input -- sock: socket connected to the server --- callback: function to receive chunks +-- receive_cb: function to receive chunks -- Returns -- nil if successfull or an error message in case of error ----------------------------------------------------------------------------- -local try_getbylength = function(sock, length, callback) - while length > 0 do - local size = min(4096, length) - local chunk, err = sock:receive(size) - if err then - callback(nil, err) - return err - end - if not callback(chunk) then - sock:close() - return "aborted by callback" - end - length = length - size - end - callback("", "done") +local try_getbylength = function(sock, length, receive_cb) + local uerr, go + while length > 0 do + local size = min(%BLOCKSIZE, length) + local chunk, err = sock:receive(size) + if err then + go, uerr = receive_cb(nil, err) + return uerr or err + end + go, uerr = receive_cb(chunk) + if not go then + sock:close() + return uerr or "aborted by callback" + end + length = length - size + end + go, uerr = receive_cb("") + return uerr end ----------------------------------------------------------------------------- -- Receives a message body by content-length -- Input -- sock: socket connected to the server --- callback: function to receive chunks +-- receive_cb: function to receive chunks -- Returns -- nil if successfull or an error message in case of error ----------------------------------------------------------------------------- -local try_getuntilclosed = function(sock, callback) - local err - while 1 do - local chunk, err = sock:receive(4096) - if err == "closed" or not err then - if not callback(chunk) then - sock:close() - return "aborted by callback" - end - if err then break end - else - callback(nil, err) - return err - end - end - callback("", "done") +local try_getuntilclosed = function(sock, receive_cb) + local err, go, uerr + while 1 do + local chunk, err = sock:receive(%BLOCKSIZE) + if err == "closed" or not err then + go, uerr = receive_cb(chunk) + if not go then + sock:close() + return uerr or "aborted by callback" + end + if err then break end + else + go, uerr = callback(nil, err) + return uerr or err + end + end + go, uerr = receive_cb("") + return uerr end ----------------------------------------------------------------------------- @@ -218,22 +228,22 @@ end -- Input -- sock: socket connected to the server -- resp_hdrs: response header fields --- callback: function to receive chunks +-- receive_cb: function to receive chunks -- Returns -- nil if successfull or an error message in case of error ----------------------------------------------------------------------------- -local try_getbody = function(sock, resp_hdrs, callback) +local try_getbody = function(sock, resp_hdrs, receive_cb) local err if resp_hdrs["transfer-encoding"] == "chunked" then -- get by chunked transfer-coding of message body - return %try_getchunked(sock, callback) + return %try_getchunked(sock, receive_cb) elseif tonumber(resp_hdrs["content-length"]) then -- get by content-length - local length = tonumber(resp_hdrs["content-length"]) - return %try_getbylength(sock, length, callback) + local length = tonumber(resp_hdrs["content-length"]) + return %try_getbylength(sock, length, receive_cb) else -- get it all until connection closes - return %try_getuntilclosed(sock, callback) + return %try_getuntilclosed(sock, receive_cb) end end @@ -275,6 +285,35 @@ local split_url = function(url, default) return parsed end +----------------------------------------------------------------------------- +-- Sends data comming from a callback +-- Input +-- data: data connection +-- send_cb: callback to produce file contents +-- chunk, size: first callback results +-- Returns +-- nil if successfull, or an error message in case of error +----------------------------------------------------------------------------- +local try_sendindirect = function(data, send_cb, chunk, size) + local sent, err + sent = 0 + while 1 do + if type(chunk) ~= "string" or type(size) ~= "number" then + data:close() + if not chunk and type(size) == "string" then return size + else return "invalid callback return" end + end + err = data:send(chunk) + if err then + data:close() + return err + end + sent = sent + strlen(chunk) + if sent >= size then break end + chunk, size = send_cb() + end +end + ----------------------------------------------------------------------------- -- Sends a http request message through socket -- Input @@ -282,45 +321,38 @@ end -- method: request method to be used -- path: url path -- req_hdrs: request headers to be sent --- callback: callback to send request message body +-- req_body_cb: callback to send request message body -- Returns -- err: nil in case of success, error message otherwise ----------------------------------------------------------------------------- -local send_request = function(sock, method, path, req_hdrs, callback) - local chunk, size, done - -- send request line - local err = %try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") +local send_request = function(sock, method, path, req_hdrs, req_body_cb) + local chunk, size, done, err + -- send request line + err = %try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") if err then return err end - -- send request headers + -- if there is a request message body, add content-length header + if req_body_cb then + chunk, size = req_body_cb() + if type(chunk) == "string" and type(size) == "number" then + req_hdrs["content-length"] = tostring(size) + else + sock:close() + if not chunk and type(size) == "string" then return size + else return "invalid callback return" end + end + end + -- send request headers for i, v in req_hdrs do err = %try_send(sock, i .. ": " .. v .. "\r\n") if err then return err end end - -- if there is a request message body, add content-length header - if callback then - chunk, size = callback() - if chunk and size then - err = %try_send(sock, "content-length: "..tostring(size).."\r\n") - if err then return err end - else - sock:close() - return size or "invalid callback return" - end - end - -- mark end of request headers + -- mark end of request headers err = %try_send(sock, "\r\n") if err then return err end - -- send message request body, getting it chunk by chunk from callback - if callback then - done = 0 - while chunk and chunk ~= "" and done < size do - err = %try_send(sock, chunk) - if err then return err end - done = done + strlen(chunk) - chunk, err = callback() - end - if not chunk then return err end - end + -- send request message body, if any + if req_body_cb then + return %try_sendindirect(sock, req_body_cb, chunk, size) + end end ----------------------------------------------------------------------------- @@ -344,18 +376,17 @@ end dofile("base64.lua") ----------------------------------------------------------------------------- --- Converts field names to lowercase and add message body size specification +-- Converts field names to lowercase and adds a few needed headers -- Input --- headers: request header fields +-- hdrs: request header fields -- parsed: parsed url components --- body: request message body, if any -- Returns -- lower: a table with the same headers, but with lowercase field names ----------------------------------------------------------------------------- -local fill_hdrs = function(headers, parsed, body) +local fill_hdrs = function(hdrs, parsed) local lower = {} - headers = headers or {} - for i,v in headers do + hdrs = hdrs or {} + for i,v in hdrs do lower[strlower(i)] = v end lower["connection"] = "close" @@ -374,15 +405,17 @@ end -- Input -- method: "GET", "PUT", "POST" etc -- url: target uniform resource locator --- req_hdrs: request headers to send --- req_body: function to return request message body --- resp_body: function to receive response message body +-- resp_body_cb: response message body receive callback +-- req_hdrs: request headers to send, or nil if none +-- req_body_cb: request message body send callback, or nil if none +-- stay: should we refrain from following a server redirect message? -- Returns --- resp_hdrs: response header fields received, if sucessfull --- resp_line: server response status line, if successfull --- err: error message if any +-- resp_hdrs: response header fields received, or nil if failed +-- resp_line: server response status line, or nil if failed +-- err: error message, or nil if successfull ----------------------------------------------------------------------------- -function http_requestindirect(method, url, req_hdrs, req_body, resp_body) +function http_requestindirect(method, url, resp_body_cb, req_hdrs, + req_body_cb, stay) local sock, err local resp_hdrs local resp_line, resp_code @@ -398,7 +431,7 @@ function http_requestindirect(method, url, req_hdrs, req_body, resp_body) -- set connection timeout sock:timeout(%TIMEOUT) -- send request - err = %send_request(sock, method, parsed.path, req_hdrs, req_body) + err = %send_request(sock, method, parsed.path, req_hdrs, req_body_cb) if err then return nil, nil, err end -- get server message resp_code, resp_line, err = %get_status(sock) @@ -407,83 +440,123 @@ function http_requestindirect(method, url, req_hdrs, req_body, resp_body) resp_hdrs, err = %get_hdrs(sock, {}) if err then return nil, line, err end -- did we get a redirect? should we automatically retry? - if (resp_code == 301 or resp_code == 302) and + if not stay and (resp_code == 301 or resp_code == 302) and (method == "GET" or method == "HEAD") then - sock:close() - return http_requestindirect(method, resp_hdrs["location"], req_hdrs, - req_body, resp_body) + sock:close() + return http_requestindirect(method, resp_hdrs["location"], + resp_body_cb, req_hdrs, req_body_cb, stay) end - -- get body if status and method combination allow one + -- get response message body if status and method combination allow one if has_respbody(method, resp_code) then - err = %try_getbody(sock, resp_hdrs, resp_body) + err = %try_getbody(sock, resp_hdrs, resp_body_cb) if err then return resp_hdrs, resp_line, err end end sock:close() return resp_hdrs, resp_line end +----------------------------------------------------------------------------- +-- We need fast concatenation routines for direct requests +----------------------------------------------------------------------------- +dofile("buffer.lua") + ----------------------------------------------------------------------------- -- Sends a HTTP request and retrieves the server reply -- Input -- method: "GET", "PUT", "POST" etc -- url: target uniform resource locator --- headers: request headers to send --- body: request message body +-- req_hdrs: request headers to send, or nil if none +-- req_body: request message body as a string, or nil if none +-- stay: should we refrain from following a server redirect message? -- Returns --- resp_body: response message body, if successfull --- resp_hdrs: response header fields received, if sucessfull --- resp_line: server response status line, if successfull --- err: error message if any +-- resp_body: response message body, or nil if failed +-- resp_hdrs: response header fields received, or nil if failed +-- resp_line: server response status line, or nil if failed +-- err: error message, or nil if successfull ----------------------------------------------------------------------------- -function http_request(method, url, req_hdrs, body) - local resp_hdrs, resp_line, err - local req_callback = function() - return %body, strlen(%body) +function http_request(method, url, req_hdrs, req_body, stay) + local resp_hdrs, resp_line, err + local req_body_cb = function() + return %req_body, strlen(%req_body) end - local resp_aux = { resp_body = "" } - local resp_callback = function(chunk, err) - if not chunk then - %resp_aux.resp_body = nil - %resp_aux.err = err - return nil - end - %resp_aux.resp_body = %resp_aux.resp_body .. chunk - return 1 - end - if not body then resp_callback = nil end - resp_hdrs, resp_line, err = http_requestindirect(method, url, req_hdrs, - req_callback, resp_callback) - if err then return nil, resp_hdrs, resp_line, err - else return resp_aux.resp_body, resp_hdrs, resp_line, resp_aux.err end + local resp_body = { buf = buf_create() } + local resp_body_cb = function(chunk, err) + if not chunk then %resp_body.buf = nil end + buf_addstring(%resp_body.buf, chunk) + return 1 + end + if not req_body then req_body_cb = nil end + resp_hdrs, resp_line, err = http_requestindirect(method, url, resp_body_cb, + req_hdrs, req_body_cb, stay) + return buf_getresult(resp_body.buf), resp_hdrs, resp_line, err end ----------------------------------------------------------------------------- -- Retrieves a URL by the method "GET" -- Input -- url: target uniform resource locator --- headers: request headers to send +-- req_hdrs: request headers to send, or nil if none +-- stay: should we refrain from following a server redirect message? -- Returns --- body: response message body, if successfull --- headers: response header fields, if sucessfull --- line: response status line, if successfull --- err: error message, if any +-- resp_body: response message body, or nil if failed +-- resp_hdrs: response header fields received, or nil if failed +-- resp_line: server response status line, or nil if failed +-- err: error message, or nil if successfull ----------------------------------------------------------------------------- -function http_get(url, headers) - return http_request("GET", url, headers) +function http_get(url, req_hdrs, stay) + return http_request("GET", url, req_hdrs, stay) end ----------------------------------------------------------------------------- -- Retrieves a URL by the method "GET" -- Input -- url: target uniform resource locator --- body: request message body --- headers: request headers to send +-- resp_body_cb: response message body receive callback +-- req_hdrs: request headers to send, or nil if none +-- stay: should we refrain from following a server redirect message? -- Returns --- body: response message body, if successfull --- headers: response header fields, if sucessfull --- line: response status line, if successfull --- err: error message, if any +-- resp_body: response message body, or nil if failed +-- resp_hdrs: response header fields received, or nil if failed +-- resp_line: server response status line, or nil if failed +-- err: error message, or nil if successfull ----------------------------------------------------------------------------- -function http_post(url, body, headers) - return http_request("POST", url, headers, body) +function http_getindirect(url, resp_body_cb, req_hdrs, stay) + return http_requestindirect("GET", url, resp_body_cb, req_hdrs, nil, stay) +end + +----------------------------------------------------------------------------- +-- Retrieves a URL by the method "POST" +-- Input +-- method: "GET", "PUT", "POST" etc +-- url: target uniform resource locator +-- req_hdrs: request headers to send, or nil if none +-- req_body: request message body, or nil if none +-- stay: should we refrain from following a server redirect message? +-- Returns +-- resp_body: response message body, or nil if failed +-- resp_hdrs: response header fields received, or nil if failed +-- resp_line: server response status line, or nil if failed +-- err: error message, or nil if successfull +----------------------------------------------------------------------------- +function http_post(url, req_body, req_hdrs, stay) + return http_request("POST", url, req_hdrs, req_body, stay) +end + +----------------------------------------------------------------------------- +-- Retrieves a URL by the method "POST" +-- Input +-- url: target uniform resource locator +-- resp_body_cb: response message body receive callback +-- req_body_cb: request message body send callback +-- req_hdrs: request headers to send, or nil if none +-- stay: should we refrain from following a server redirect message? +-- Returns +-- resp_body: response message body, or nil if failed +-- resp_hdrs: response header fields received, or nil if failed +-- resp_line: server response status line, or nil if failed +-- err: error message, or nil if successfull +----------------------------------------------------------------------------- +function http_getindirect(url, resp_body_cb, req_body_cb, req_hdrs, stay) + return http_requestindirect("GET", url, resp_body_cb, req_hdrs, + req_body_cb, stay) end From f66963f9a199250dc836835db649679e4dce881f Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 6 Jun 2001 20:59:12 +0000 Subject: [PATCH 044/483] Updated for 1.3b. --- README | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/README b/README index d708668..6b84d8a 100644 --- a/README +++ b/README @@ -2,12 +2,9 @@ This directory contains the implementation of the protocols FTP, HTTP and SMTP. The files provided are: http.lua -- HTTP protocol implementation - base64.lua -- base64 encoding implementation The module http.lua provides general HTTP client support. The -implementation conforms to the HTTP/1.1 standard, RFC 2068. The -base64.lua module provides base64 encoding and decoding. The module is -used for the HTTP Basic Authentication Scheme, and conforms to RFC 1521. +implementation conforms to the HTTP/1.1 standard, RFC 2068. smtp.lua -- SMTP protocol implementation @@ -19,5 +16,17 @@ SMTP mail server. The implementation conforms to RFC 821. The module ftp.lua provides functions to download and upload files from and to FTP servers. The implementation conforms to RFC 959. -These implementations are part of the LuaSocket library and are supported. + base64.lua -- base64 encoding implementation + +The base64.lua module provides base64 encoding and decoding. The module +is used for the HTTP Basic Authentication Scheme, and conforms to RFC +1521. + + buffer.lua -- fast concatenation library + +The module buffer.lua implements, completely in Lua, a set of functions +that greatly improves the performance of repeated concatenations of Lua +strings. The module was created by Roberto Ierusalimschy. + +These modules are part of the LuaSocket library and are supported. Please send any comments to diego@tecgraf.puc-rio.br. From 8f1349ddd4b0af71bec244a1be09b09c22709ae1 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 6 Jun 2001 20:59:36 +0000 Subject: [PATCH 045/483] Initial revision --- etc/get.lua | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 etc/get.lua diff --git a/etc/get.lua b/etc/get.lua new file mode 100644 index 0000000..4a17cfc --- /dev/null +++ b/etc/get.lua @@ -0,0 +1,100 @@ +assert(dofile("../lua/buffer.lua")) +assert(dofile("../lua/ftp.lua")) +assert(dofile("../lua/base64.lua")) +assert(dofile("../lua/http.lua")) + +-- format a number of bytes per second into a human readable form +function strbps(b) + local l = "B/s" + if b > 1024 then + b = b / 1024 + l = "KB/s" + if b > 1024 then + b = b / 1024 + l = "MB/s" + if b > 1024 then + b = b / 1024 + l = "GB/s" -- hmmm + end + end + end + return format("%.2f%s ", b, l) +end + +-- creates a new instance of a receive_cb that saves to disk +-- kind of copied from luasocket's manual callback examples +function receive2disk(file) + local aux = { + start = _time(), + got = 0, + file = openfile(file, "wb") + } + local receive_cb = function(chunk, err) + local dt = _time() - %aux.start -- elapsed time since start + if not chunk or chunk == "" then + write("\n") + closefile(%aux.file) + return + end + write(%aux.file, chunk) + %aux.got = %aux.got + strlen(chunk) -- total bytes received + if dt < 0.1 then return 1 end -- not enough time for estimate + local rate = %aux.got / dt -- get download rate + write("\r" .. strbps(rate)) -- print estimate + return 1 + end + return receive_cb +end + +-- stolen from http implementation +function split_url(url, default) + -- initialize default parameters + local parsed = default or {} + -- get scheme + url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end) + -- get user name and password. both can be empty! + -- moreover, password can be ommited + url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p) + %parsed.user = u + -- there can be an empty password, but the ':' has to be there + -- or else there is no password + %parsed.pass = nil -- kill default password + if c == ":" then %parsed.pass = p end + end) + -- get host + url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end) + -- get port if any + url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end) + -- whatever is left is the path + if url ~= "" then parsed.path = url end + return parsed +end + +-- stolen from http implementation +function get_statuscode(line) + local _,_, code = strfind(line, " (%d%d%d) ") + return tonumber(code) +end + +function getbyftp(url, file) + local err = ftp_getindirect(url, receive2disk(file), "b") + if err then print(err) else print("done.") end +end + +function getbyhttp(url, file) + local hdrs, line, err = http_getindirect(url, receive2disk(file)) + if line and get_statuscode(line) == 200 then print("done.") + elseif line then print(line) else print(err) end +end + +function get(url, file) + local parsed = split_url(url) + if parsed.scheme == "ftp" then getbyftp(url, file) + else getbyhttp(url, file) end +end + +arg = arg or {} +if getn(arg) < 2 then + write("Usage:\n luasocket -f get.lua \n") + exit(1) +else get(arg[1], arg[2]) end From 2b7ea458d64d88b1393f75e9b669d8510914e654 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 6 Jun 2001 21:00:11 +0000 Subject: [PATCH 046/483] Updated for 1.3b --- samples/README | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/samples/README b/samples/README index e295035..07bc7dc 100644 --- a/samples/README +++ b/samples/README @@ -33,14 +33,22 @@ servers as well. This module implements file retrieval by the TFTP protocol. Its main use is to test the UDP code, but someone might find it usefull. - broadcast.lua -- Broadcast telnet server + tinyirc.lua -- irc like broadcast server -This is a simple server that waits simultaneously on two server sockets for -telnet connections. Everything it receives from the telnet clients is -broadcast to every other connected client. It tests the select function and -shows how to create a simple server whith LuaSocket. Just run broadcast.lua -and then open as many telnet connections as you want to ports 8080 and -8081. +This is a simple server that waits simultaneously on two server sockets +for telnet connections. Everything it receives from the telnet clients +is broadcasted to every other connected client. It tests the select +function and shows how to create a simple server whith LuaSocket. Just +run broadcast.lua and then open as many telnet connections as you want +to ports 8080 and 8081. + + get.lua -- file retriever + +This module is a client that uses the FTP and HTTP code to implement a +command line file graber. Just run 'luasocket -f get.lua +' to download a remote file (either ftp:// or http://) to +the specified local file. The program also prints the download +throughput during download. Good luck, Diego. From d684be0cff116df0a3287b8883440b36460e0579 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 7 Jun 2001 20:52:34 +0000 Subject: [PATCH 047/483] Block size increased. --- src/http.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http.lua b/src/http.lua index 38d54d0..576118e 100644 --- a/src/http.lua +++ b/src/http.lua @@ -16,7 +16,7 @@ local PORT = 80 -- user agent field sent in request local USERAGENT = "LuaSocket 1.3b HTTP 1.1" -- block size used in transfers -local BLOCKSIZE = 4096 +local BLOCKSIZE = 8192 ----------------------------------------------------------------------------- -- Tries to get a pattern from the server and closes socket on error From 23dcfabcf1234aba4f2b2ed57a20f56e6e75fc16 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 8 Jun 2001 22:22:37 +0000 Subject: [PATCH 048/483] Issue error when select is called with invalid parameters. Some warnings removed (include string.h) --- src/luasocket.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index 9abfa09..5cea287 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -620,7 +621,7 @@ int global_select(lua_State *L) /* get rid of lua_next value and expose index */ lua_pop(L, 1); } - } + } else if (!lua_isnil(L, 1)) luaL_argerror(L, 1, "expected table or nil"); /* get sockets we will test for writability into fd_set */ if (lua_istable(L, 2)) { lua_pushnil(L); @@ -639,7 +640,7 @@ int global_select(lua_State *L) /* get rid of lua_next value and expose index */ lua_pop(L, 1); } - } + } else if (!lua_isnil(L, 2)) luaL_argerror(L, 2, "expected table or nil"); max++; /* configure timeout value */ if (ms >= 0) { @@ -1081,24 +1082,24 @@ static int set_option(lua_State *L, p_sock sock) int bool; if (!lua_isnumber(L, -1)) return 0; bool = (int) lua_tonumber(L, -1); - err = setsockopt(sock->sock, SOL_SOCKET, SO_KEEPALIVE, &bool, - sizeof(bool)); + err = setsockopt(sock->sock, SOL_SOCKET, SO_KEEPALIVE, + (char *) &bool, sizeof(bool)); return err >= 0; } case 1: { int bool; if (!lua_isnumber(L, -1)) return 0; bool = (int) lua_tonumber(L, -1); - err = setsockopt(sock->sock, SOL_SOCKET, SO_DONTROUTE, &bool, - sizeof(bool)); + err = setsockopt(sock->sock, SOL_SOCKET, SO_DONTROUTE, + (char *) &bool, sizeof(bool)); return err >= 0; } case 2: { int bool; if (!lua_isnumber(L, -1)) return 0; bool = (int) lua_tonumber(L, -1); - err = setsockopt(sock->sock, SOL_SOCKET, SO_BROADCAST, &bool, - sizeof(bool)); + err = setsockopt(sock->sock, SOL_SOCKET, SO_BROADCAST, + (char *) &bool, sizeof(bool)); return err >= 0; } case 3: { @@ -1114,8 +1115,8 @@ static int set_option(lua_State *L, p_sock sock) if (!lua_isnumber(L, -1)) return 0; linger.l_linger = lua_tonumber(L, -1); lua_pop(L, 1); - err = setsockopt(sock->sock, SOL_SOCKET, SO_LINGER, &linger, - sizeof(linger)); + err = setsockopt(sock->sock, SOL_SOCKET, SO_LINGER, + (char *) &linger, sizeof(linger)); return err >= 0; } default: return 0; From 4456edcd0b56dd1401b58192b94b8364dab69c02 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 8 Jun 2001 22:36:30 +0000 Subject: [PATCH 049/483] udpsocket errors are more verbose --- src/luasocket.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index 5cea287..bd3f473 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -1080,7 +1080,8 @@ static int set_option(lua_State *L, p_sock sock) switch (luaL_findstring(option, optionnames)) { case 0: { int bool; - if (!lua_isnumber(L, -1)) return 0; + if (!lua_isnumber(L, -1)) + lua_error(L, "invalid SO_KEEPALIVE value"); bool = (int) lua_tonumber(L, -1); err = setsockopt(sock->sock, SOL_SOCKET, SO_KEEPALIVE, (char *) &bool, sizeof(bool)); @@ -1088,7 +1089,8 @@ static int set_option(lua_State *L, p_sock sock) } case 1: { int bool; - if (!lua_isnumber(L, -1)) return 0; + if (!lua_isnumber(L, -1)) + lua_error(L, "invalid SO_DONTROUTE value"); bool = (int) lua_tonumber(L, -1); err = setsockopt(sock->sock, SOL_SOCKET, SO_DONTROUTE, (char *) &bool, sizeof(bool)); @@ -1096,7 +1098,8 @@ static int set_option(lua_State *L, p_sock sock) } case 2: { int bool; - if (!lua_isnumber(L, -1)) return 0; + if (!lua_isnumber(L, -1)) + lua_error(L, "invalid SO_BROADCAST value"); bool = (int) lua_tonumber(L, -1); err = setsockopt(sock->sock, SOL_SOCKET, SO_BROADCAST, (char *) &bool, sizeof(bool)); @@ -1104,15 +1107,18 @@ static int set_option(lua_State *L, p_sock sock) } case 3: { struct linger linger; - if (!lua_istable(L, -1)) return 0; + if (!lua_istable(L, -1)) + lua_error(L, "invalid SO_LINGER value"); lua_pushstring(L, "l_onoff"); lua_gettable(L, -2); - if (!lua_isnumber(L, -1)) return 0; + if (!lua_isnumber(L, -1)) + lua_error(L, "invalid SO_LINGER (l_onoff) value"); linger.l_onoff = lua_tonumber(L, -1); lua_pop(L, 1); lua_pushstring(L, "l_linger"); lua_gettable(L, -2); - if (!lua_isnumber(L, -1)) return 0; + if (!lua_isnumber(L, -1)) + lua_error(L, "invalid SO_LINGER (l_linger) value"); linger.l_linger = lua_tonumber(L, -1); lua_pop(L, 1); err = setsockopt(sock->sock, SOL_SOCKET, SO_LINGER, From e9eafc74809319bcbe97f0661a710601fe3d1c1e Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 8 Jun 2001 22:40:09 +0000 Subject: [PATCH 050/483] Now prints the elapsed time. --- test/httptest.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/httptest.lua b/test/httptest.lua index b88475d..8c78192 100644 --- a/test/httptest.lua +++ b/test/httptest.lua @@ -1,8 +1,11 @@ -- load http assert(dofile("../lua/http.lua")) assert(dofile("../lua/base64.lua")) +assert(dofile("../lua/buffer.lua")) assert(dofile("auxiliar.lua")) +t = _time() + -- needs Alias from /home/i/diego/public/html/luasocket/test to -- /luasocket-test -- needs ScriptAlias from /home/i/diego/public/html/luasocket/test/cgi-bin @@ -83,3 +86,5 @@ assert(f and m and s and not e, join(s, e)) assert(compare(pdir .. "auth/index.html", f), "documents differ") print("passed all tests") + +print(format("done in %.2fs", _time() - t)) From 6fb33f0a762b541da342faf7bf128092d41a6e83 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 8 Jun 2001 22:40:57 +0000 Subject: [PATCH 051/483] ftp.lua needs buffer.lua... --- test/ftptest.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/test/ftptest.lua b/test/ftptest.lua index 85dc16b..41f314d 100644 --- a/test/ftptest.lua +++ b/test/ftptest.lua @@ -1,4 +1,5 @@ assert(dofile("../lua/ftp.lua")) +assert(dofile("../lua/buffer.lua")) assert(dofile("auxiliar.lua")) pdir = "/home/i/diego/public/html/luasocket/test/" From bf4ca59463cf42a79510f42dd561f91c297b4738 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 8 Jun 2001 22:41:15 +0000 Subject: [PATCH 052/483] sleep became _sleep. --- test/testsrvr.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testsrvr.lua b/test/testsrvr.lua index a11927f..7b89987 100644 --- a/test/testsrvr.lua +++ b/test/testsrvr.lua @@ -80,7 +80,7 @@ function execute_command(cmd, par) send_command(SYNC) elseif cmd == SLEEP then print("server: sleeping for " .. par .. " seconds...") - sleep(par) + _sleep(par) print("server: woke up!") end end From c51283fa62331bb6db5e481da714493f0a91b936 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 8 Jun 2001 22:42:01 +0000 Subject: [PATCH 053/483] Added select bug test. time became _time. --- test/testclnt.lua | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/test/testclnt.lua b/test/testclnt.lua index 1efb51d..4ee2c48 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -22,7 +22,7 @@ while control == nil do if control then print("client: control connection stablished!") else - sleep(2) + _sleep(2) end end @@ -48,7 +48,7 @@ function reconnect() data = connect(HOST, PORT) if not data then print("client: waiting for data connection.") - sleep(1) + _sleep(1) end end sync() @@ -296,9 +296,6 @@ function test_returntimeout(len, t, s) else fail("blocks don't match") end end - - - ----------------------------------------------------------------------------- -- Tests read patterns ----------------------------------------------------------------------------- @@ -363,10 +360,26 @@ function test_patterns() pass("'*a' is ok") end +----------------------------------------------------------------------------- +-- Test for select bugs +----------------------------------------------------------------------------- +function test_select() + local r, s, e = select(nil, nil, 0.1) + assert(type(r) == "table" and type(s) == "table" and e == "timeout") + pass("both nil") + data:close() + r, s, e = select({ data }, { data }, 0.1) + assert(type(r) == "table" and type(s) == "table" and e == "timeout") + pass("closed sockets") + e = call(select, {"wrong", 1, 0.1}, "x", nil) + assert(e == nil) + pass("invalid input") +end + ----------------------------------------------------------------------------- -- Execute all tests ----------------------------------------------------------------------------- -start = time() +start = _time() new_test("control connection test") test_command(EXIT) @@ -376,7 +389,9 @@ test_command(ECHO_BLOCK, 12234) test_command(SLEEP, 1111) test_command(ECHO_LINE) ---a = [[ +new_test("testing for select bugs") +test_select() + new_test("connection close test") test_closed() @@ -438,17 +453,15 @@ test_returntimeout(8000, 1, 2) test_returntimeout(80000, 2, 1) test_returntimeout(800000, 0.1, 0) test_returntimeout(800000, 2, 1) ---]] ----------------------------------------------------------------------------- -- Close connection and exit server. We are done. ----------------------------------------------------------------------------- +new_test("the library has passed all tests") print("client: closing connection with server") send_command(CLOSE) send_command(EXIT) control:close() - -new_test("the library has passed all tests") -print(format("time elapsed: %6.2fs", time() - start)) +print(format("time elapsed: %6.2fs", _time() - start)) print("client: exiting...") exit() From 8a8dfcdb725e678f59c5ff7afce74854d37ead28 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 8 Jun 2001 22:45:55 +0000 Subject: [PATCH 054/483] Updated version information. --- src/ftp.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ftp.lua b/src/ftp.lua index 229bbc6..bbb672b 100644 --- a/src/ftp.lua +++ b/src/ftp.lua @@ -1,5 +1,5 @@ ----------------------------------------------------------------------------- --- Simple FTP support for the Lua language using the LuaSocket 1.2 toolkit. +-- Simple FTP support for the Lua language using the LuaSocket 1.3b toolkit. -- Author: Diego Nehab -- Date: 26/12/2000 -- Conforming to: RFC 959 From 168fe9bacf0537d7e6d19bd34fb32addef9724b3 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 8 Jun 2001 23:12:41 +0000 Subject: [PATCH 055/483] Updated for 1.3b release --- makefile.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makefile.dist b/makefile.dist index 2db8f75..c409acc 100644 --- a/makefile.dist +++ b/makefile.dist @@ -2,7 +2,7 @@ # Distribution makefile #-------------------------------------------------------------------------- -DIST = luasocket-1.2.1 +DIST = luasocket-1.3b SRC = ~diego/tec/luasocket From 6fd40adc77b1f35e098dc5b3a8fc361f46bc061f Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Sat, 9 Jun 2001 21:22:57 +0000 Subject: [PATCH 056/483] Removed some warnings. --- src/luasocket.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index bd3f473..96733b5 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -1113,13 +1113,13 @@ static int set_option(lua_State *L, p_sock sock) lua_gettable(L, -2); if (!lua_isnumber(L, -1)) lua_error(L, "invalid SO_LINGER (l_onoff) value"); - linger.l_onoff = lua_tonumber(L, -1); + linger.l_onoff = (int) lua_tonumber(L, -1); lua_pop(L, 1); lua_pushstring(L, "l_linger"); lua_gettable(L, -2); if (!lua_isnumber(L, -1)) lua_error(L, "invalid SO_LINGER (l_linger) value"); - linger.l_linger = lua_tonumber(L, -1); + linger.l_linger = (int) lua_tonumber(L, -1); lua_pop(L, 1); err = setsockopt(sock->sock, SOL_SOCKET, SO_LINGER, (char *) &linger, sizeof(linger)); From ce0ad4c6b687aa6ff8aa24ef510e6e8716f9e995 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 11 Jun 2001 18:15:49 +0000 Subject: [PATCH 057/483] debug code removed. --- src/smtp.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/smtp.lua b/src/smtp.lua index 7f9af3d..6404e6c 100644 --- a/src/smtp.lua +++ b/src/smtp.lua @@ -29,7 +29,6 @@ end ----------------------------------------------------------------------------- local try_send = function(sock, line) local err = sock:send(line .. "\r\n") -print(line) if err then sock:close() end return err end @@ -47,14 +46,12 @@ local get_answer = function(control) local line, err = control:receive() local answer = line if err then return nil, err end -print(line) _,_, code, sep = strfind(line, "^(%d%d%d)(.)") if not code or not sep then return nil, answer end if sep == "-" then -- answer is multiline repeat line, err = control:receive() if err then return nil, err end -print(line) _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)") answer = answer .. "\n" .. line until code == lastcode and sep == " " -- answer ends with same code From c16b6d931243bed0d41f2fe1b7fced48fa493268 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 19 Jun 2001 19:31:22 +0000 Subject: [PATCH 058/483] LUASOCKET_API now prefixes all exported function declarations. --- src/luasocket.c | 2 +- src/luasocket.h | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index 96733b5..f72615a 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -1660,7 +1660,7 @@ static int receive_word(lua_State *L, p_sock sock) * Initializes the library interface with Lua and the socket library. * Defines the symbols exported to Lua. \*-------------------------------------------------------------------------*/ -void lua_socketlibopen(lua_State *L) +LUASOCKET_API void lua_socketlibopen(lua_State *L) { struct luaL_reg funcs[] = { {"bind", global_tcpbind}, diff --git a/src/luasocket.h b/src/luasocket.h index 4402c02..a580b2c 100644 --- a/src/luasocket.h +++ b/src/luasocket.h @@ -20,10 +20,17 @@ #define LUASOCKET_UDPBUFFERSIZE 4096 /* note that 576 bytes is the maximum safe value */ +/*-------------------------------------------------------------------------*\ +* This macro prefixes all exported API functions +\*-------------------------------------------------------------------------*/ +#ifndef LUASOCKET_API +#define LUASOCKET_API extern +#endif + /*-------------------------------------------------------------------------*\ * Initializes the library interface with Lua and the socket library. * Defines the symbols exported to Lua. \*-------------------------------------------------------------------------*/ -void lua_socketlibopen(lua_State *L); +LUASOCKET_API void lua_socketlibopen(lua_State *L); #endif /* _LUASOCKET_H_ */ From 0cc37c4b41d8762a62a2939ac100ef8b77205c44 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 23 Jul 2001 20:13:01 +0000 Subject: [PATCH 059/483] Timeout detection bug when timeout value was 0 in the select function. --- src/luasocket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/luasocket.c b/src/luasocket.c index f72615a..3b3f697 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -652,7 +652,7 @@ int global_select(lua_State *L) /* see if we can read, write or if we timedout */ ret = select(max, prfds, pwfds, NULL, ptm); /* did we timeout? */ - if (ret <= 0 && ms > 0) { + if (ret <= 0 && ms >= 0) { push_error(L, NET_TIMEOUT); return 3; } From 3143c58e0fe839e849aca3b29e0928bd9c1df6a5 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Sun, 29 Jul 2001 03:51:36 +0000 Subject: [PATCH 060/483] Rewritten to comply to LTN7 (Modules & Packages). As a result, there have been some API changes. Parameter and return values are now passed inside tables. Automatic redirection and automatic authentication are better controlled, with loop detection. Implementation is more RFCish, conforming to RFC2616. URL parsing has been moved to an external library, to be shared with FTP. --- src/http.lua | 552 ++++++++++++++++++++++++++++----------------------- 1 file changed, 309 insertions(+), 243 deletions(-) diff --git a/src/http.lua b/src/http.lua index 576118e..e68cf22 100644 --- a/src/http.lua +++ b/src/http.lua @@ -1,22 +1,32 @@ ----------------------------------------------------------------------------- -- Full HTTP/1.1 client support for the Lua language using the --- LuaSocket 1.2 toolkit. +-- LuaSocket 1.4a toolkit. -- Author: Diego Nehab -- Date: 26/12/2000 -- Conforming to: RFC 2068 ----------------------------------------------------------------------------- +local Public, Private = {}, {} +HTTP = Public + ----------------------------------------------------------------------------- -- Program constants ----------------------------------------------------------------------------- -- connection timeout in seconds -local TIMEOUT = 60 +Private.TIMEOUT = 60 -- default port for document retrieval -local PORT = 80 +Private.PORT = 80 -- user agent field sent in request -local USERAGENT = "LuaSocket 1.3b HTTP 1.1" +Private.USERAGENT = "LuaSocket 1.4a" -- block size used in transfers -local BLOCKSIZE = 8192 +Private.BLOCKSIZE = 8192 + +----------------------------------------------------------------------------- +-- Required libraries +----------------------------------------------------------------------------- +dofile "buffer.lua" +dofile "url.lua" +dofile "encode.lua" ----------------------------------------------------------------------------- -- Tries to get a pattern from the server and closes socket on error @@ -26,9 +36,9 @@ local BLOCKSIZE = 8192 -- data: line received or nil in case of error -- err: error message if any ----------------------------------------------------------------------------- -local try_get = function(...) +function Private.try_receive(...) local sock = arg[1] - local data, err = call(sock.receive, arg) + local data, err = call(sock.receive, arg) if err then sock:close() return nil, err @@ -43,8 +53,8 @@ end -- Returns -- err: error message if any, nil if successfull ----------------------------------------------------------------------------- -local try_send = function(sock, data) - err = sock:send(data) +function Private.try_send(sock, data) + local err = sock:send(data) if err then sock:close() end return err end @@ -56,13 +66,14 @@ end -- Returns -- code: integer with status code ----------------------------------------------------------------------------- -local get_statuscode = function(line) - local _,_, code = strfind(line, " (%d%d%d) ") +function Private.get_statuscode(line) + local code, _ + _, _, code = strfind(line, "HTTP/%d*%.%d* (%d%d%d)") return tonumber(code) end ----------------------------------------------------------------------------- --- Receive server reply messages +-- Receive server reply messages, parsing status code -- Input -- sock: socket connected to the server -- Returns @@ -70,29 +81,29 @@ end -- line: full http status line -- err: error message if any ----------------------------------------------------------------------------- -local get_status = function(sock) +function Private.receive_status(sock) local line, err - line, err = %try_get(sock) - if not err then return %get_statuscode(line), line + line, err = %Private.try_receive(sock) + if not err then return %Private.get_statuscode(line), line else return nil, nil, err end end ----------------------------------------------------------------------------- --- Receive and parse responce header fields +-- Receive and parse response header fields -- Input -- sock: socket connected to the server --- hdrs: a table that might already contain headers +-- headers: a table that might already contain headers -- Returns --- hdrs: a table with all headers fields in the form +-- headers: a table with all headers fields in the form -- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"} -- all name_i are lowercase -- nil and error message in case of error ----------------------------------------------------------------------------- -local get_hdrs = function(sock, hdrs) +function Private.receive_headers(sock, headers) local line, err - local name, value + local name, value, _ -- get first line - line, err = %try_get(sock) + line, err = %Private.try_receive(sock) if err then return nil, err end -- headers go until a blank line is found while line ~= "" do @@ -104,34 +115,35 @@ local get_hdrs = function(sock, hdrs) end name = strlower(name) -- get next line (value might be folded) - line, err = %try_get(sock) + line, err = %Private.try_receive(sock) if err then return nil, err end -- unfold any folded values while not err and strfind(line, "^%s") do value = value .. line - line, err = %try_get(sock) + line, err = %Private.try_receive(sock) if err then return nil, err end end -- save pair in table - if hdrs[name] then hdrs[name] = hdrs[name] .. ", " .. value - else hdrs[name] = value end + if headers[name] then headers[name] = headers[name] .. ", " .. value + else headers[name] = value end end - return hdrs + return headers end ----------------------------------------------------------------------------- -- Receives a chunked message body -- Input -- sock: socket connected to the server +-- headers: header set in which to include trailer headers -- receive_cb: function to receive chunks -- Returns -- nil if successfull or an error message in case of error ----------------------------------------------------------------------------- -local try_getchunked = function(sock, receive_cb) +function Private.receivebody_bychunks(sock, headers, receive_cb) local chunk, size, line, err, go, uerr, _ - repeat + while 1 do -- get chunk size, skip extention - line, err = %try_get(sock) + line, err = %Private.try_receive(sock) if err then local _, uerr = receive_cb(nil, err) return uerr or err @@ -143,8 +155,10 @@ local try_getchunked = function(sock, receive_cb) _, uerr = receive_cb(nil, err) return uerr or err end + -- was it the last chunk? + if size <= 0 then break end -- get chunk - chunk, err = %try_get(sock, size) + chunk, err = %Private.try_receive(sock, size) if err then _, uerr = receive_cb(nil, err) return uerr or err @@ -155,13 +169,21 @@ local try_getchunked = function(sock, receive_cb) sock:close() return uerr or "aborted by callback" end - -- skip blank line - _, err = %try_get(sock) + -- skip CRLF on end of chunk + _, err = %Private.try_receive(sock) if err then _, uerr = receive_cb(nil, err) return uerr or err end - until size <= 0 + end + -- the server should not send trailer headers because we didn't send a + -- header informing it we know how to deal with them. we do not risk + -- being caught unprepaired. + headers, err = %Private.receive_headers(sock, headers) + if err then + _, uerr = receive_cb(nil, err) + return uerr or err + end -- let callback know we are done _, uerr = receive_cb("") return uerr @@ -175,10 +197,10 @@ end -- Returns -- nil if successfull or an error message in case of error ----------------------------------------------------------------------------- -local try_getbylength = function(sock, length, receive_cb) +function Private.receivebody_bylength(sock, length, receive_cb) local uerr, go while length > 0 do - local size = min(%BLOCKSIZE, length) + local size = min(%Private.BLOCKSIZE, length) local chunk, err = sock:receive(size) if err then go, uerr = receive_cb(nil, err) @@ -203,10 +225,10 @@ end -- Returns -- nil if successfull or an error message in case of error ----------------------------------------------------------------------------- -local try_getuntilclosed = function(sock, receive_cb) +function Private.receivebody_untilclosed(sock, receive_cb) local err, go, uerr while 1 do - local chunk, err = sock:receive(%BLOCKSIZE) + local chunk, err = sock:receive(%Private.BLOCKSIZE) if err == "closed" or not err then go, uerr = receive_cb(chunk) if not go then @@ -227,62 +249,36 @@ end -- Receives http response body -- Input -- sock: socket connected to the server --- resp_hdrs: response header fields +-- headers: response header fields -- receive_cb: function to receive chunks -- Returns -- nil if successfull or an error message in case of error ----------------------------------------------------------------------------- -local try_getbody = function(sock, resp_hdrs, receive_cb) - local err - if resp_hdrs["transfer-encoding"] == "chunked" then +function Private.receive_body(sock, headers, receive_cb) + local te = headers["transfer-encoding"] + if te and te ~= "identity" then -- get by chunked transfer-coding of message body - return %try_getchunked(sock, receive_cb) - elseif tonumber(resp_hdrs["content-length"]) then + return %Private.receivebody_bychunks(sock, headers, receive_cb) + elseif tonumber(headers["content-length"]) then -- get by content-length - local length = tonumber(resp_hdrs["content-length"]) - return %try_getbylength(sock, length, receive_cb) + local length = tonumber(headers["content-length"]) + return %Private.receivebody_bylength(sock, length, receive_cb) else -- get it all until connection closes - return %try_getuntilclosed(sock, receive_cb) + return %Private.receivebody_untilclosed(sock, receive_cb) end end ----------------------------------------------------------------------------- --- Parses a url and returns its scheme, user, password, host, port --- and path components, according to RFC 1738 +-- Drop http response body -- Input --- url: uniform resource locator of request --- default: table containing default values to be returned +-- sock: socket connected to the server +-- headers: response header fields -- Returns --- table with the following fields: --- host: host to connect --- path: url path --- port: host port to connect --- user: user name --- pass: password --- scheme: protocol +-- nil if successfull or an error message in case of error ----------------------------------------------------------------------------- -local split_url = function(url, default) - -- initialize default parameters - local parsed = default or {} - -- get scheme - url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end) - -- get user name and password. both can be empty! - -- moreover, password can be ommited - url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p) - %parsed.user = u - -- there can be an empty password, but the ':' has to be there - -- or else there is no password - %parsed.pass = nil -- kill default password - if c == ":" then %parsed.pass = p end - end) - -- get host - url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end) - -- get port if any - url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end) - -- whatever is left is the path - if url ~= "" then parsed.path = url end - return parsed +function Private.drop_body(sock, headers) + return %Private.receive_body(sock, headers, function (c, e) return 1 end) end ----------------------------------------------------------------------------- @@ -294,20 +290,20 @@ end -- Returns -- nil if successfull, or an error message in case of error ----------------------------------------------------------------------------- -local try_sendindirect = function(data, send_cb, chunk, size) +function Private.send_indirect(data, send_cb, chunk, size) local sent, err sent = 0 while 1 do if type(chunk) ~= "string" or type(size) ~= "number" then - data:close() + data:close() if not chunk and type(size) == "string" then return size else return "invalid callback return" end end err = data:send(chunk) if err then - data:close() - return err - end + data:close() + return err + end sent = sent + strlen(chunk) if sent >= size then break end chunk, size = send_cb() @@ -320,243 +316,313 @@ end -- sock: socket connected to the server -- method: request method to be used -- path: url path --- req_hdrs: request headers to be sent --- req_body_cb: callback to send request message body +-- headers: request headers to be sent +-- body_cb: callback to send request message body -- Returns -- err: nil in case of success, error message otherwise ----------------------------------------------------------------------------- -local send_request = function(sock, method, path, req_hdrs, req_body_cb) +function Private.send_request(sock, method, path, headers, body_cb) local chunk, size, done, err -- send request line - err = %try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") + err = %Private.try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") if err then return err end -- if there is a request message body, add content-length header - if req_body_cb then - chunk, size = req_body_cb() + if body_cb then + chunk, size = body_cb() if type(chunk) == "string" and type(size) == "number" then - req_hdrs["content-length"] = tostring(size) + headers["content-length"] = tostring(size) else sock:close() - if not chunk and type(size) == "string" then return size - else return "invalid callback return" end + if not chunk and type(size) == "string" then return size + else return "invalid callback return" end end end -- send request headers - for i, v in req_hdrs do - err = %try_send(sock, i .. ": " .. v .. "\r\n") + for i, v in headers do + err = %Private.try_send(sock, i .. ": " .. v .. "\r\n") if err then return err end end -- mark end of request headers - err = %try_send(sock, "\r\n") + err = %Private.try_send(sock, "\r\n") if err then return err end -- send request message body, if any - if req_body_cb then - return %try_sendindirect(sock, req_body_cb, chunk, size) + if body_cb then + return %Private.send_indirect(sock, body_cb, chunk, size) end end ----------------------------------------------------------------------------- -- Determines if we should read a message body from the server response -- Input --- method: method used in request --- code: server response status code +-- request: a table with the original request information +-- response: a table with the server response information -- Returns -- 1 if a message body should be processed, nil otherwise ----------------------------------------------------------------------------- -function has_respbody(method, code) - if method == "HEAD" then return nil end - if code == 204 or code == 304 then return nil end - if code >= 100 and code < 200 then return nil end +function Private.has_body(request, response) + if request.method == "HEAD" then return nil end + if response.code == 204 or response.code == 304 then return nil end + if response.code >= 100 and response.code < 200 then return nil end return 1 end ------------------------------------------------------------------------------ --- We need base64 convertion routines for Basic Authentication Scheme ------------------------------------------------------------------------------ -dofile("base64.lua") - ----------------------------------------------------------------------------- -- Converts field names to lowercase and adds a few needed headers -- Input --- hdrs: request header fields +-- headers: request header fields -- parsed: parsed url components -- Returns -- lower: a table with the same headers, but with lowercase field names ----------------------------------------------------------------------------- -local fill_hdrs = function(hdrs, parsed) +function Private.fill_headers(headers, parsed) local lower = {} - hdrs = hdrs or {} - for i,v in hdrs do + headers = headers or {} + -- set default headers + lower["user-agent"] = %Private.USERAGENT + lower["host"] = parsed.host + -- override with user values + for i,v in headers do lower[strlower(i)] = v end + -- this cannot be overriden lower["connection"] = "close" - lower["host"] = parsed.host - lower["user-agent"] = %USERAGENT - if parsed.user and parsed.pass then -- Basic Authentication - lower["authorization"] = "Basic ".. - base64(parsed.user .. ":" .. parsed.pass) - end return lower end +----------------------------------------------------------------------------- +-- Decides wether we should follow retry with authorization formation +-- Input +-- request: a table with the original request information +-- parsed: parsed request url +-- response: a table with the server response information +-- Returns +-- 1 if we should retry, nil otherwise +----------------------------------------------------------------------------- +function Private.should_authorize(request, parsed, response) + -- if there has been an authorization attempt, it must have failed + if request.headers["authorization"] then return nil end + -- if we don't have authorization information, we can't retry + if parsed.user and parsed.password then return 1 + else return nil end +end + +----------------------------------------------------------------------------- +-- Returns the result of retrying a request with authorization information +-- Input +-- request: a table with the original request information +-- parsed: parsed request url +-- response: a table with the server response information +-- Returns +-- response: result of target redirection +----------------------------------------------------------------------------- +function Private.authorize(request, parsed, response) + request.headers["authorization"] = "Basic " .. + base64(parsed.user .. ":" .. parsed.password) + local authorize = { + redirects = request.redirects, + method = request.method, + url = request.url, + body_cb = request.body_cb, + headers = request.headers + } + return %Public.request_indirect(authorize, response) +end + +----------------------------------------------------------------------------- +-- Decides wether we should follow a server redirect message +-- Input +-- request: a table with the original request information +-- response: a table with the server response information +-- Returns +-- 1 if we should redirect, nil otherwise +----------------------------------------------------------------------------- +function Private.should_redirect(request, response) + local follow = not request.stay + follow = follow and (response.code == 301 or response.code == 302) + follow = follow and (request.method == "GET" or request.method == "HEAD") + follow = follow and not (request.redirects and request.redirects >= 5) + return follow +end + +----------------------------------------------------------------------------- +-- Returns the result of a request following a server redirect message. +-- Input +-- request: a table with the original request information +-- response: a table with the following fields: +-- body_cb: response method body receive-callback +-- Returns +-- response: result of target redirection +----------------------------------------------------------------------------- +function Private.redirect(request, response) + local redirects = request.redirects or 0 + redirects = redirects + 1 + local redirect = { + redirects = redirects, + method = request.method, + -- the RFC says the redirect url has to be absolute, but some + -- servers do not respect that + url = URL.build_absolute(request.url,response.headers["location"]), + body_cb = request.body_cb, + headers = request.headers + } + return %Public.request_indirect(redirect, response) +end + +----------------------------------------------------------------------------- +-- Computes the request URI from the given URL +-- Input +-- parsed: parsed url +-- Returns +-- uri: request URI for parsed URL +----------------------------------------------------------------------------- +function Private.request_uri(parsed) + local uri = "" + if parsed.path then uri = uri .. parsed.path end + if parsed.params then uri = uri .. ";" .. parsed.params end + if parsed.query then uri = uri .. "?" .. parsed.query end + if parsed.fragment then uri = uri .. "#" .. parsed.fragment end + return uri +end + ----------------------------------------------------------------------------- -- Sends a HTTP request and retrieves the server reply using callbacks to -- send the request body and receive the response body -- Input --- method: "GET", "PUT", "POST" etc --- url: target uniform resource locator --- resp_body_cb: response message body receive callback --- req_hdrs: request headers to send, or nil if none --- req_body_cb: request message body send callback, or nil if none --- stay: should we refrain from following a server redirect message? +-- request: a table with the following fields +-- method: "GET", "PUT", "POST" etc (defaults to "GET") +-- url: target uniform resource locator +-- user, password: authentication information +-- headers: request headers to send, or nil if none +-- body_cb: request message body send-callback, or nil if none +-- stay: should we refrain from following a server redirect message? +-- response: a table with the following fields: +-- body_cb: response method body receive-callback -- Returns --- resp_hdrs: response header fields received, or nil if failed --- resp_line: server response status line, or nil if failed --- err: error message, or nil if successfull +-- response: a table with the following fields: +-- headers: response header fields received, or nil if failed +-- status: server response status line, or nil if failed +-- code: server status code, or nil if failed +-- error: error message, or nil if successfull ----------------------------------------------------------------------------- -function http_requestindirect(method, url, resp_body_cb, req_hdrs, - req_body_cb, stay) - local sock, err - local resp_hdrs - local resp_line, resp_code +function Public.request_indirect(request, response) -- get url components - local parsed = %split_url(url, {port = %PORT, path ="/"}) - -- methods are case sensitive - method = strupper(method) + local parsed = URL.parse(request.url, {port = %Private.PORT, path ="/"}) + -- explicit authentication info overrides that given by the url + parsed.user = request.user or parsed.user + parsed.password = request.password or parsed.password + -- default method + request.method = request.method or "GET" -- fill default headers - req_hdrs = %fill_hdrs(req_hdrs, parsed) - -- try connection - sock, err = connect(parsed.host, parsed.port) - if not sock then return nil, nil, err end - -- set connection timeout - sock:timeout(%TIMEOUT) - -- send request - err = %send_request(sock, method, parsed.path, req_hdrs, req_body_cb) - if err then return nil, nil, err end - -- get server message - resp_code, resp_line, err = %get_status(sock) - if err then return nil, nil, err end - -- deal with reply - resp_hdrs, err = %get_hdrs(sock, {}) - if err then return nil, line, err end - -- did we get a redirect? should we automatically retry? - if not stay and (resp_code == 301 or resp_code == 302) and - (method == "GET" or method == "HEAD") then + request.headers = %Private.fill_headers(request.headers, parsed) + -- try to connect to server + local sock + sock, response.error = connect(parsed.host, parsed.port) + if not sock then return response end + -- set connection timeout so that we do not hang forever + sock:timeout(%Private.TIMEOUT) + -- send request message + response.error = %Private.send_request(sock, request.method, + %Private.request_uri(parsed), request.headers, request.body_cb) + if response.error then return response end + -- get server response message + response.code, response.status, response.error = + %Private.receive_status(sock) + if response.error then return response end + -- receive all headers + response.headers, response.error = %Private.receive_headers(sock, {}) + if response.error then return response end + -- decide what to do based on request and response parameters + if %Private.should_redirect(request, response) then + %Private.drop_body(sock, response.headers) sock:close() - return http_requestindirect(method, resp_hdrs["location"], - resp_body_cb, req_hdrs, req_body_cb, stay) - end - -- get response message body if status and method combination allow one - if has_respbody(method, resp_code) then - err = %try_getbody(sock, resp_hdrs, resp_body_cb) - if err then return resp_hdrs, resp_line, err end + return %Private.redirect(request, response) + elseif %Private.should_authorize(request, parsed, response) then + %Private.drop_body(sock, response.headers) + sock:close() + return %Private.authorize(request, parsed, response) + elseif %Private.has_body(request, response) then + response.error = %Private.receive_body(sock, response.headers, + response.body_cb) + if response.error then return response end + sock:close() + return response end sock:close() - return resp_hdrs, resp_line end ------------------------------------------------------------------------------ --- We need fast concatenation routines for direct requests ------------------------------------------------------------------------------ -dofile("buffer.lua") - ----------------------------------------------------------------------------- -- Sends a HTTP request and retrieves the server reply -- Input --- method: "GET", "PUT", "POST" etc --- url: target uniform resource locator --- req_hdrs: request headers to send, or nil if none --- req_body: request message body as a string, or nil if none --- stay: should we refrain from following a server redirect message? +-- request: a table with the following fields +-- method: "GET", "PUT", "POST" etc (defaults to "GET") +-- url: target url, i.e. the document to be retrieved +-- user, password: authentication information +-- headers: request header fields, or nil if none +-- body: request message body as a string, or nil if none +-- stay: should we refrain from following a server redirect message? -- Returns --- resp_body: response message body, or nil if failed --- resp_hdrs: response header fields received, or nil if failed --- resp_line: server response status line, or nil if failed --- err: error message, or nil if successfull +-- response: a table with the following fields: +-- body: response message body, or nil if failed +-- headers: response header fields, or nil if failed +-- status: server response status line, or nil if failed +-- code: server response status code, or nil if failed +-- error: error message if any ----------------------------------------------------------------------------- -function http_request(method, url, req_hdrs, req_body, stay) - local resp_hdrs, resp_line, err - local req_body_cb = function() - return %req_body, strlen(%req_body) +function Public.request(request) + local response = {} + if request.body then + request.body_cb = function() + return %request.body, strlen(%request.body) + end end - local resp_body = { buf = buf_create() } - local resp_body_cb = function(chunk, err) - if not chunk then %resp_body.buf = nil end - buf_addstring(%resp_body.buf, chunk) + local auxiliar = { buf = buf_create() } + response.body_cb = function(chunk, err) + if not chunk then %auxiliar.buf = nil end + buf_addstring(%auxiliar.buf, chunk) return 1 end - if not req_body then req_body_cb = nil end - resp_hdrs, resp_line, err = http_requestindirect(method, url, resp_body_cb, - req_hdrs, req_body_cb, stay) - return buf_getresult(resp_body.buf), resp_hdrs, resp_line, err + %Public.request_indirect(request, response) + response.body = buf_getresult(auxiliar.buf) + response.body_cb = nil + return response end ----------------------------------------------------------------------------- -- Retrieves a URL by the method "GET" -- Input --- url: target uniform resource locator --- req_hdrs: request headers to send, or nil if none --- stay: should we refrain from following a server redirect message? +-- url: target url, i.e. the document to be retrieved -- Returns --- resp_body: response message body, or nil if failed --- resp_hdrs: response header fields received, or nil if failed --- resp_line: server response status line, or nil if failed --- err: error message, or nil if successfull +-- body: response message body, or nil if failed +-- headers: response header fields received, or nil if failed +-- status: server response status line, or nil if failed +-- error: error message if any ----------------------------------------------------------------------------- -function http_get(url, req_hdrs, stay) - return http_request("GET", url, req_hdrs, stay) -end - ------------------------------------------------------------------------------ --- Retrieves a URL by the method "GET" --- Input --- url: target uniform resource locator --- resp_body_cb: response message body receive callback --- req_hdrs: request headers to send, or nil if none --- stay: should we refrain from following a server redirect message? --- Returns --- resp_body: response message body, or nil if failed --- resp_hdrs: response header fields received, or nil if failed --- resp_line: server response status line, or nil if failed --- err: error message, or nil if successfull ------------------------------------------------------------------------------ -function http_getindirect(url, resp_body_cb, req_hdrs, stay) - return http_requestindirect("GET", url, resp_body_cb, req_hdrs, nil, stay) +function Public.get(url) + local response = %Public.request { + method = "GET", + url = url + } + return response.body, response.headers, + response.status, response.error end ----------------------------------------------------------------------------- -- Retrieves a URL by the method "POST" -- Input --- method: "GET", "PUT", "POST" etc --- url: target uniform resource locator --- req_hdrs: request headers to send, or nil if none --- req_body: request message body, or nil if none --- stay: should we refrain from following a server redirect message? +-- url: target url, i.e. the document to be retrieved +-- body: request message body, or nil if none -- Returns --- resp_body: response message body, or nil if failed --- resp_hdrs: response header fields received, or nil if failed --- resp_line: server response status line, or nil if failed --- err: error message, or nil if successfull +-- body: response message body, or nil if failed +-- headers: response header fields received, or nil if failed +-- status: server response status line, or nil if failed +-- error: error message, or nil if successfull ----------------------------------------------------------------------------- -function http_post(url, req_body, req_hdrs, stay) - return http_request("POST", url, req_hdrs, req_body, stay) -end - ------------------------------------------------------------------------------ --- Retrieves a URL by the method "POST" --- Input --- url: target uniform resource locator --- resp_body_cb: response message body receive callback --- req_body_cb: request message body send callback --- req_hdrs: request headers to send, or nil if none --- stay: should we refrain from following a server redirect message? --- Returns --- resp_body: response message body, or nil if failed --- resp_hdrs: response header fields received, or nil if failed --- resp_line: server response status line, or nil if failed --- err: error message, or nil if successfull ------------------------------------------------------------------------------ -function http_getindirect(url, resp_body_cb, req_body_cb, req_hdrs, stay) - return http_requestindirect("GET", url, resp_body_cb, req_hdrs, - req_body_cb, stay) +function Public.post(url, body) + local response = %Public.request { + method = "POST", + url = url, + body = body + } + return response.body, response.headers, + response.status, response.error end From cb61077f7a8c71536c3ad331f041bc4ab97c719c Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 7 Aug 2001 19:50:04 +0000 Subject: [PATCH 061/483] Minor documentation changes. Constants were moved to Public table. Updated to use new fast concatenation module (concat.lua). --- src/http.lua | 104 +++++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/src/http.lua b/src/http.lua index e68cf22..1e4c2cd 100644 --- a/src/http.lua +++ b/src/http.lua @@ -1,9 +1,10 @@ ----------------------------------------------------------------------------- --- Full HTTP/1.1 client support for the Lua language using the +-- HTTP/1.1 client support for the Lua language. -- LuaSocket 1.4a toolkit. -- Author: Diego Nehab -- Date: 26/12/2000 --- Conforming to: RFC 2068 +-- Conforming to: RFC 2616, LTN7 +-- RCS ID: $Id$ ----------------------------------------------------------------------------- local Public, Private = {}, {} @@ -13,27 +14,27 @@ HTTP = Public -- Program constants ----------------------------------------------------------------------------- -- connection timeout in seconds -Private.TIMEOUT = 60 +Public.TIMEOUT = 60 -- default port for document retrieval -Private.PORT = 80 +Public.PORT = 80 -- user agent field sent in request -Private.USERAGENT = "LuaSocket 1.4a" +Public.USERAGENT = "LuaSocket 1.4a" -- block size used in transfers -Private.BLOCKSIZE = 8192 +Public.BLOCKSIZE = 8192 ----------------------------------------------------------------------------- -- Required libraries ----------------------------------------------------------------------------- dofile "buffer.lua" dofile "url.lua" -dofile "encode.lua" +dofile "code.lua" ----------------------------------------------------------------------------- -- Tries to get a pattern from the server and closes socket on error -- sock: socket connected to the server --- pattern: pattern to receive +-- ...: patterns to receive -- Returns --- data: line received or nil in case of error +-- ...: received patterns -- err: error message if any ----------------------------------------------------------------------------- function Private.try_receive(...) @@ -60,11 +61,11 @@ function Private.try_send(sock, data) end ----------------------------------------------------------------------------- --- Retrieves status code from http status line +-- Computes status code from HTTP status line -- Input --- line: http status line +-- line: HTTP status line -- Returns --- code: integer with status code +-- code: integer with status code, or nil if malformed line ----------------------------------------------------------------------------- function Private.get_statuscode(line) local code, _ @@ -73,12 +74,12 @@ function Private.get_statuscode(line) end ----------------------------------------------------------------------------- --- Receive server reply messages, parsing status code +-- Receive server reply messages, parsing for status code -- Input -- sock: socket connected to the server -- Returns -- code: server status code or nil if error --- line: full http status line +-- line: full HTTP status line -- err: error message if any ----------------------------------------------------------------------------- function Private.receive_status(sock) @@ -108,7 +109,7 @@ function Private.receive_headers(sock, headers) -- headers go until a blank line is found while line ~= "" do -- get field-name and value - _,_, name, value = strfind(line, "(.-):%s*(.*)") + _,_, name, value = strfind(line, "^(.-):%s*(.*)") if not name or not value then sock:close() return nil, "malformed reponse headers" @@ -145,14 +146,14 @@ function Private.receivebody_bychunks(sock, headers, receive_cb) -- get chunk size, skip extention line, err = %Private.try_receive(sock) if err then - local _, uerr = receive_cb(nil, err) + local go, uerr = receive_cb(nil, err) return uerr or err end size = tonumber(gsub(line, ";.*", ""), 16) if not size then err = "invalid chunk size" sock:close() - _, uerr = receive_cb(nil, err) + go, uerr = receive_cb(nil, err) return uerr or err end -- was it the last chunk? @@ -160,7 +161,7 @@ function Private.receivebody_bychunks(sock, headers, receive_cb) -- get chunk chunk, err = %Private.try_receive(sock, size) if err then - _, uerr = receive_cb(nil, err) + go, uerr = receive_cb(nil, err) return uerr or err end -- pass chunk to callback @@ -172,7 +173,7 @@ function Private.receivebody_bychunks(sock, headers, receive_cb) -- skip CRLF on end of chunk _, err = %Private.try_receive(sock) if err then - _, uerr = receive_cb(nil, err) + go, uerr = receive_cb(nil, err) return uerr or err end end @@ -181,11 +182,11 @@ function Private.receivebody_bychunks(sock, headers, receive_cb) -- being caught unprepaired. headers, err = %Private.receive_headers(sock, headers) if err then - _, uerr = receive_cb(nil, err) + go, uerr = receive_cb(nil, err) return uerr or err end -- let callback know we are done - _, uerr = receive_cb("") + go, uerr = receive_cb("") return uerr end @@ -193,6 +194,7 @@ end -- Receives a message body by content-length -- Input -- sock: socket connected to the server +-- length: message body length -- receive_cb: function to receive chunks -- Returns -- nil if successfull or an error message in case of error @@ -200,7 +202,7 @@ end function Private.receivebody_bylength(sock, length, receive_cb) local uerr, go while length > 0 do - local size = min(%Private.BLOCKSIZE, length) + local size = min(%Public.BLOCKSIZE, length) local chunk, err = sock:receive(size) if err then go, uerr = receive_cb(nil, err) @@ -228,14 +230,14 @@ end function Private.receivebody_untilclosed(sock, receive_cb) local err, go, uerr while 1 do - local chunk, err = sock:receive(%Private.BLOCKSIZE) + local chunk, err = sock:receive(%Public.BLOCKSIZE) if err == "closed" or not err then go, uerr = receive_cb(chunk) if not go then sock:close() return uerr or "aborted by callback" end - if err then break end + if err == "closed" then break end else go, uerr = callback(nil, err) return uerr or err @@ -246,7 +248,7 @@ function Private.receivebody_untilclosed(sock, receive_cb) end ----------------------------------------------------------------------------- --- Receives http response body +-- Receives HTTP response body -- Input -- sock: socket connected to the server -- headers: response header fields @@ -270,7 +272,7 @@ function Private.receive_body(sock, headers, receive_cb) end ----------------------------------------------------------------------------- --- Drop http response body +-- Drop HTTP response body -- Input -- sock: socket connected to the server -- headers: response header fields @@ -286,7 +288,7 @@ end -- Input -- data: data connection -- send_cb: callback to produce file contents --- chunk, size: first callback results +-- chunk, size: first callback return values -- Returns -- nil if successfull, or an error message in case of error ----------------------------------------------------------------------------- @@ -311,20 +313,20 @@ function Private.send_indirect(data, send_cb, chunk, size) end ----------------------------------------------------------------------------- --- Sends a http request message through socket +-- Sends a HTTP request message through socket -- Input -- sock: socket connected to the server -- method: request method to be used --- path: url path +-- uri: request uri -- headers: request headers to be sent -- body_cb: callback to send request message body -- Returns -- err: nil in case of success, error message otherwise ----------------------------------------------------------------------------- -function Private.send_request(sock, method, path, headers, body_cb) +function Private.send_request(sock, method, uri, headers, body_cb) local chunk, size, done, err -- send request line - err = %Private.try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") + err = %Private.try_send(sock, method .. " " .. uri .. " HTTP/1.1\r\n") if err then return err end -- if there is a request message body, add content-length header if body_cb then @@ -370,7 +372,7 @@ end -- Converts field names to lowercase and adds a few needed headers -- Input -- headers: request header fields --- parsed: parsed url components +-- parsed: parsed request URL -- Returns -- lower: a table with the same headers, but with lowercase field names ----------------------------------------------------------------------------- @@ -378,7 +380,7 @@ function Private.fill_headers(headers, parsed) local lower = {} headers = headers or {} -- set default headers - lower["user-agent"] = %Private.USERAGENT + lower["user-agent"] = %Public.USERAGENT lower["host"] = parsed.host -- override with user values for i,v in headers do @@ -393,7 +395,7 @@ end -- Decides wether we should follow retry with authorization formation -- Input -- request: a table with the original request information --- parsed: parsed request url +-- parsed: parsed request URL -- response: a table with the server response information -- Returns -- 1 if we should retry, nil otherwise @@ -410,14 +412,14 @@ end -- Returns the result of retrying a request with authorization information -- Input -- request: a table with the original request information --- parsed: parsed request url +-- parsed: parsed request URL -- response: a table with the server response information -- Returns -- response: result of target redirection ----------------------------------------------------------------------------- function Private.authorize(request, parsed, response) request.headers["authorization"] = "Basic " .. - base64(parsed.user .. ":" .. parsed.password) + Code.base64(parsed.user .. ":" .. parsed.password) local authorize = { redirects = request.redirects, method = request.method, @@ -459,9 +461,9 @@ function Private.redirect(request, response) local redirect = { redirects = redirects, method = request.method, - -- the RFC says the redirect url has to be absolute, but some + -- the RFC says the redirect URL has to be absolute, but some -- servers do not respect that - url = URL.build_absolute(request.url,response.headers["location"]), + url = URL.absolute_url(request.url, response.headers["location"]), body_cb = request.body_cb, headers = request.headers } @@ -469,9 +471,9 @@ function Private.redirect(request, response) end ----------------------------------------------------------------------------- --- Computes the request URI from the given URL +-- Computes the request URI from the parsed request URL -- Input --- parsed: parsed url +-- parsed: parsed URL -- Returns -- uri: request URI for parsed URL ----------------------------------------------------------------------------- @@ -505,9 +507,8 @@ end -- error: error message, or nil if successfull ----------------------------------------------------------------------------- function Public.request_indirect(request, response) - -- get url components - local parsed = URL.parse(request.url, {port = %Private.PORT, path ="/"}) - -- explicit authentication info overrides that given by the url + local parsed = URL.parse_url(request.url, {port = %Public.PORT, path ="/"}) + -- explicit authentication info overrides that given by the URL parsed.user = request.user or parsed.user parsed.password = request.password or parsed.password -- default method @@ -519,7 +520,7 @@ function Public.request_indirect(request, response) sock, response.error = connect(parsed.host, parsed.port) if not sock then return response end -- set connection timeout so that we do not hang forever - sock:timeout(%Private.TIMEOUT) + sock:timeout(%Public.TIMEOUT) -- send request message response.error = %Private.send_request(sock, request.method, %Private.request_uri(parsed), request.headers, request.body_cb) @@ -555,7 +556,7 @@ end -- Input -- request: a table with the following fields -- method: "GET", "PUT", "POST" etc (defaults to "GET") --- url: target url, i.e. the document to be retrieved +-- url: request URL, i.e. the document to be retrieved -- user, password: authentication information -- headers: request header fields, or nil if none -- body: request message body as a string, or nil if none @@ -572,17 +573,16 @@ function Public.request(request) local response = {} if request.body then request.body_cb = function() - return %request.body, strlen(%request.body) + return %request.body, strlen(%request.body) end end - local auxiliar = { buf = buf_create() } + local cat = Concat.create() response.body_cb = function(chunk, err) - if not chunk then %auxiliar.buf = nil end - buf_addstring(%auxiliar.buf, chunk) + %cat:addstring(chunk) return 1 end %Public.request_indirect(request, response) - response.body = buf_getresult(auxiliar.buf) + response.body = cat:getresult() response.body_cb = nil return response end @@ -590,7 +590,7 @@ end ----------------------------------------------------------------------------- -- Retrieves a URL by the method "GET" -- Input --- url: target url, i.e. the document to be retrieved +-- url: request URL, i.e. the document to be retrieved -- Returns -- body: response message body, or nil if failed -- headers: response header fields received, or nil if failed @@ -609,7 +609,7 @@ end ----------------------------------------------------------------------------- -- Retrieves a URL by the method "POST" -- Input --- url: target url, i.e. the document to be retrieved +-- url: request URL, i.e. the document to be retrieved -- body: request message body, or nil if none -- Returns -- body: response message body, or nil if failed From 504ecdc0aaeb89dd16cfc8f058de3b03d869270e Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 12 Sep 2001 18:16:09 +0000 Subject: [PATCH 062/483] Simple HTTP functions can deal with table arguments also. --- src/http.lua | 101 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 71 insertions(+), 30 deletions(-) diff --git a/src/http.lua b/src/http.lua index 1e4c2cd..7eaaf9e 100644 --- a/src/http.lua +++ b/src/http.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- HTTP/1.1 client support for the Lua language. --- LuaSocket 1.4a toolkit. +-- LuaSocket 1.4 toolkit. -- Author: Diego Nehab -- Date: 26/12/2000 -- Conforming to: RFC 2616, LTN7 @@ -18,23 +18,23 @@ Public.TIMEOUT = 60 -- default port for document retrieval Public.PORT = 80 -- user agent field sent in request -Public.USERAGENT = "LuaSocket 1.4a" +Public.USERAGENT = "LuaSocket 1.4" -- block size used in transfers Public.BLOCKSIZE = 8192 ----------------------------------------------------------------------------- -- Required libraries ----------------------------------------------------------------------------- -dofile "buffer.lua" +dofile "concat.lua" dofile "url.lua" dofile "code.lua" ----------------------------------------------------------------------------- -- Tries to get a pattern from the server and closes socket on error -- sock: socket connected to the server --- ...: patterns to receive +-- ...: pattern to receive -- Returns --- ...: received patterns +-- ...: received pattern -- err: error message if any ----------------------------------------------------------------------------- function Private.try_receive(...) @@ -312,6 +312,26 @@ function Private.send_indirect(data, send_cb, chunk, size) end end +----------------------------------------------------------------------------- +-- Sends mime headers +-- Input +-- sock: server socket +-- headers: table with mime headers to be sent +-- Returns +-- err: error message if any +----------------------------------------------------------------------------- +function Private.send_headers(sock, headers) + local err + headers = headers or {} + -- send request headers + for i, v in headers do + err = %Private.try_send(sock, i .. ": " .. v .. "\r\n") + if err then return err end + end + -- mark end of request headers + return %Private.try_send(sock, "\r\n") +end + ----------------------------------------------------------------------------- -- Sends a HTTP request message through socket -- Input @@ -340,12 +360,7 @@ function Private.send_request(sock, method, uri, headers, body_cb) end end -- send request headers - for i, v in headers do - err = %Private.try_send(sock, i .. ": " .. v .. "\r\n") - if err then return err end - end - -- mark end of request headers - err = %Private.try_send(sock, "\r\n") + err = %Private.send_headers(sock, headers) if err then return err end -- send request message body, if any if body_cb then @@ -427,7 +442,7 @@ function Private.authorize(request, parsed, response) body_cb = request.body_cb, headers = request.headers } - return %Public.request_indirect(authorize, response) + return %Public.request_cb(authorize, response) end ----------------------------------------------------------------------------- @@ -467,7 +482,7 @@ function Private.redirect(request, response) body_cb = request.body_cb, headers = request.headers } - return %Public.request_indirect(redirect, response) + return %Public.request_cb(redirect, response) end ----------------------------------------------------------------------------- @@ -486,6 +501,23 @@ function Private.request_uri(parsed) return uri end +----------------------------------------------------------------------------- +-- Builds a request table from a URL or request table +-- Input +-- url_or_request: target url or request table (a table with the fields: +-- url: the target URL +-- user: account user name +-- password: account password) +-- Returns +-- request: request table +----------------------------------------------------------------------------- +function Private.build_request(data) + local request = {} + if type(data) == "table" then for i, v in data do request[i] = v end + else request.url = data end + return request +end + ----------------------------------------------------------------------------- -- Sends a HTTP request and retrieves the server reply using callbacks to -- send the request body and receive the response body @@ -506,8 +538,12 @@ end -- code: server status code, or nil if failed -- error: error message, or nil if successfull ----------------------------------------------------------------------------- -function Public.request_indirect(request, response) - local parsed = URL.parse_url(request.url, {port = %Public.PORT, path ="/"}) +function Public.request_cb(request, response) + local parsed = URL.parse_url(request.url, { + host = "", + port = %Public.PORT, + path ="/" + }) -- explicit authentication info overrides that given by the URL parsed.user = request.user or parsed.user parsed.password = request.password or parsed.password @@ -578,10 +614,10 @@ function Public.request(request) end local cat = Concat.create() response.body_cb = function(chunk, err) - %cat:addstring(chunk) + if chunk then %cat:addstring(chunk) end return 1 end - %Public.request_indirect(request, response) + response = %Public.request_cb(request, response) response.body = cat:getresult() response.body_cb = nil return response @@ -590,18 +626,20 @@ end ----------------------------------------------------------------------------- -- Retrieves a URL by the method "GET" -- Input --- url: request URL, i.e. the document to be retrieved +-- url_or_request: target url or request table (a table with the fields: +-- url: the target URL +-- user: account user name +-- password: account password) -- Returns -- body: response message body, or nil if failed -- headers: response header fields received, or nil if failed -- status: server response status line, or nil if failed -- error: error message if any ----------------------------------------------------------------------------- -function Public.get(url) - local response = %Public.request { - method = "GET", - url = url - } +function Public.get(url_or_request) + local request = %Private.build_request(url_or_request) + request.method = "GET" + local response = %Public.request(request) return response.body, response.headers, response.status, response.error end @@ -609,7 +647,11 @@ end ----------------------------------------------------------------------------- -- Retrieves a URL by the method "POST" -- Input --- url: request URL, i.e. the document to be retrieved +-- url_or_request: target url or request table (a table with the fields: +-- url: the target URL +-- body: request message body +-- user: account user name +-- password: account password) -- body: request message body, or nil if none -- Returns -- body: response message body, or nil if failed @@ -617,12 +659,11 @@ end -- status: server response status line, or nil if failed -- error: error message, or nil if successfull ----------------------------------------------------------------------------- -function Public.post(url, body) - local response = %Public.request { - method = "POST", - url = url, - body = body - } +function Public.post(url_or_request, body) + local request = %Private.build_request(url_or_request) + request.method = "POST" + request.body = request.body or body + local response = %Public.request(request) return response.body, response.headers, response.status, response.error end From 480689a70217d2ec9bedf699166f05bb972e9448 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 12 Sep 2001 18:16:31 +0000 Subject: [PATCH 063/483] Updated for LuaSocket 1.4, following LTN7 etc. Module is now automaticaly tested. --- src/smtp.lua | 347 ++++++++++++++++++++++++++------------------------- 1 file changed, 175 insertions(+), 172 deletions(-) diff --git a/src/smtp.lua b/src/smtp.lua index 6404e6c..7450792 100644 --- a/src/smtp.lua +++ b/src/smtp.lua @@ -1,88 +1,55 @@ ----------------------------------------------------------------------------- --- Simple SMTP support for the Lua language using the LuaSocket toolkit. +-- SMTP support for the Lua language. +-- LuaSocket 1.4 toolkit -- Author: Diego Nehab -- Date: 26/12/2000 --- Conforming to: RFC 821 +-- Conforming to: RFC 821, LTN7 +-- RCS ID: $Id$ ----------------------------------------------------------------------------- +local Public, Private = {}, {} +SMTP = Public + ----------------------------------------------------------------------------- -- Program constants ----------------------------------------------------------------------------- -- timeout in secconds before we give up waiting -local TIMEOUT = 180 +Public.TIMEOUT = 180 -- port used for connection -local PORT = 25 --- domain used in HELO command. If we are under a CGI, try to get from --- environment -local DOMAIN = getenv("SERVER_NAME") -if not DOMAIN then - DOMAIN = "localhost" -end +Public.PORT = 25 +-- domain used in HELO command and default sendmail +-- If we are under a CGI, try to get from environment +Public.DOMAIN = getenv("SERVER_NAME") or "localhost" +-- default server used to send e-mails +Public.SERVER = "localhost" ----------------------------------------------------------------------------- --- Tries to send DOS mode lines. Closes socket on error. +-- Tries to send data through socket. Closes socket on error. -- Input -- sock: server socket --- line: string to be sent +-- data: string to be sent -- Returns -- err: message in case of error, nil if successfull ----------------------------------------------------------------------------- -local try_send = function(sock, line) - local err = sock:send(line .. "\r\n") +function Private.try_send(sock, data) + local err = sock:send(data) if err then sock:close() end return err end ----------------------------------------------------------------------------- --- Gets command reply, (accepts multiple-line replies) --- Input --- control: control connection socket +-- Tries to get a pattern from the server and closes socket on error +-- sock: socket opened to the server +-- ...: pattern to receive -- Returns --- answer: whole server reply, nil if error --- code: reply status code or error message +-- ...: received pattern +-- err: error message if any ----------------------------------------------------------------------------- -local get_answer = function(control) - local code, lastcode, sep - local line, err = control:receive() - local answer = line - if err then return nil, err end - _,_, code, sep = strfind(line, "^(%d%d%d)(.)") - if not code or not sep then return nil, answer end - if sep == "-" then -- answer is multiline - repeat - line, err = control:receive() - if err then return nil, err end - _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)") - answer = answer .. "\n" .. line - until code == lastcode and sep == " " -- answer ends with same code - end - return answer, tonumber(code) -end - ------------------------------------------------------------------------------ --- Checks if a message reply code is correct. Closes control connection --- if not. --- Input --- control: control connection socket --- success: table with successfull reply status code --- Returns --- code: reply code or nil in case of error --- answer: complete server answer or system error message ------------------------------------------------------------------------------ -local check_answer = function(control, success) - local answer, code = %get_answer(control) - if not answer then - control:close() - return nil, code - end - if type(success) ~= "table" then success = {success} end - for i = 1, getn(success) do - if code == success[i] then - return code, answer - end - end - control:close() - return nil, answer +function Private.try_receive(...) + local sock = arg[1] + local data, err = call(sock.receive, arg) + if err then sock:close() end + return data, err end ----------------------------------------------------------------------------- @@ -94,11 +61,60 @@ end -- Returns -- err: error message if any ----------------------------------------------------------------------------- -local send_command = function(sock, command, param) +function Private.send_command(sock, command, param) local line - if param then line = command .. " " .. param - else line = command end - return %try_send(sock, line) + if param then line = command .. " " .. param .. "\r\n" + else line = command .. "\r\n" end + return %Private.try_send(sock, line) +end + +----------------------------------------------------------------------------- +-- Gets command reply, (accepts multiple-line replies) +-- Input +-- control: control openion socket +-- Returns +-- answer: whole server reply, nil if error +-- code: reply status code or error message +----------------------------------------------------------------------------- +function Private.get_answer(control) + local code, lastcode, sep, _ + local line, err = %Private.try_receive(control) + local answer = line + if err then return nil, err end + _,_, code, sep = strfind(line, "^(%d%d%d)(.)") + if not code or not sep then return nil, answer end + if sep == "-" then -- answer is multiline + repeat + line, err = %Private.try_receive(control) + if err then return nil, err end + _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)") + answer = answer .. "\n" .. line + until code == lastcode and sep == " " -- answer ends with same code + end + return answer, tonumber(code) +end + +----------------------------------------------------------------------------- +-- Checks if a message reply code is correct. Closes control openion +-- if not. +-- Input +-- control: control openion socket +-- success: table with successfull reply status code +-- Returns +-- code: reply code or nil in case of error +-- answer: complete server answer or system error message +----------------------------------------------------------------------------- +function Private.check_answer(control, success) + local answer, code = %Private.get_answer(control) + if not answer then return nil, code end + if type(success) ~= "table" then success = {success} end + for i = 1, getn(success) do + if code == success[i] then + return code, answer + end + end + control:close() + return nil, answer end ----------------------------------------------------------------------------- @@ -109,49 +125,24 @@ end -- code: server code if ok, nil if error -- answer: complete server reply ----------------------------------------------------------------------------- -local send_helo = function(sock) - local err = %send_command(sock, "HELO", %DOMAIN) +function Private.send_helo(sock) + local err = %Private.send_command(sock, "HELO", %Public.DOMAIN) if err then return nil, err end - return %check_answer(sock, 250) + return %Private.check_answer(sock, 250) end ----------------------------------------------------------------------------- --- Sends mime headers --- Input --- sock: server socket --- mime: table with mime headers to be sent --- Returns --- err: error message if any ------------------------------------------------------------------------------ -local send_mime = function(sock, mime) - local err - mime = mime or {} - -- send all headers - for name,value in mime do - err = sock:send(name .. ": " .. value .. "\r\n") - if err then - sock:close() - return err - end - end - -- end mime part - err = sock:send("\r\n") - if err then sock:close() end - return err -end - ------------------------------------------------------------------------------ --- Sends connection termination command +-- Sends openion termination command -- Input -- sock: server socket -- Returns -- code: server status code, nil if error -- answer: complete server reply or error message ----------------------------------------------------------------------------- -local send_quit = function(sock) - local err = %send_command(sock, "QUIT") +function Private.send_quit(sock) + local err = %Private.send_command(sock, "QUIT") if err then return nil, err end - local code, answer = %check_answer(sock, 221) + local code, answer = %Private.check_answer(sock, 221) sock:close() return code, answer end @@ -165,36 +156,55 @@ end -- code: server status code, nil if error -- answer: complete server reply or error message ----------------------------------------------------------------------------- -local send_mail = function(sock, sender) - local param = format("FROM:<%s>", sender) - local err = %send_command(sock, "MAIL", param) +function Private.send_mail(sock, sender) + local param = format("FROM:<%s>", sender or "") + local err = %Private.send_command(sock, "MAIL", param) if err then return nil, err end - return %check_answer(sock, 250) + return %Private.check_answer(sock, 250) +end + +----------------------------------------------------------------------------- +-- Sends mime headers +-- Input +-- sock: server socket +-- headers: table with mime headers to be sent +-- Returns +-- err: error message if any +----------------------------------------------------------------------------- +function Private.send_headers(sock, headers) + local err + -- send request headers + for i, v in headers or {} do + err = %Private.try_send(sock, i .. ": " .. v .. "\r\n") + if err then return err end + end + -- mark end of request headers + return %Private.try_send(sock, "\r\n") end ----------------------------------------------------------------------------- -- Sends message mime headers and body -- Input -- sock: server socket --- mime: table containing all mime headers to be sent +-- headers: table containing all mime headers to be sent -- body: message body -- Returns -- code: server status code, nil if error -- answer: complete server reply or error message ----------------------------------------------------------------------------- -local send_data = function (sock, mime, body) - local err = %send_command(sock, "DATA") +function Private.send_data(sock, headers, body) + local err = %Private.send_command(sock, "DATA") if err then return nil, err end - local code, answer = %check_answer(sock, 354) + local code, answer = %Private.check_answer(sock, 354) if not code then return nil, answer end -- avoid premature end in message body body = gsub(body or "", "\n%.", "\n%.%.") -- mark end of message body - body = body .. "\r\n." - err = %send_mime(sock, mime) + body = body .. "\r\n.\r\n" + err = %Private.send_headers(sock, headers) if err then return nil, err end - err = %try_send(sock, body) - return %check_answer(sock, 250) + err = %Private.try_send(sock, body) + return %Private.check_answer(sock, 250) end ----------------------------------------------------------------------------- @@ -206,107 +216,100 @@ end -- code: server status code, nil if error -- answer: complete server reply ----------------------------------------------------------------------------- -local send_rcpt = function(sock, rcpt) - local err, code, answer +function Private.send_rcpt(sock, rcpt) + local err + local code, answer = nil, "No recipient specified" if type(rcpt) ~= "table" then rcpt = {rcpt} end for i = 1, getn(rcpt) do - err = %send_command(sock, "RCPT", format("TO:<%s>", rcpt[i])) + err = %Private.send_command(sock, "RCPT", format("TO:<%s>", rcpt[i])) if err then return nil, err end - code, answer = %check_answer(sock, {250, 251}) + code, answer = %Private.check_answer(sock, {250, 251}) if not code then return code, answer end end return code, answer end ----------------------------------------------------------------------------- --- Connection oriented mail functions +-- Starts the connection and greets server +-- Input +-- parsed: parsed URL components +-- Returns +-- sock: socket connected to server +-- err: error message if any ----------------------------------------------------------------------------- -function smtp_connect(server) +function Private.open(server) local code, answer - -- connect to server - local sock, err = connect(server, %PORT) + -- default server + server = server or %Public.SERVER + -- connect to server and make sure we won't hang + local sock, err = connect(server, %Public.PORT) if not sock then return nil, err end - sock:timeout(%TIMEOUT) + sock:timeout(%Public.TIMEOUT) -- initial server greeting - code, answer = %check_answer(sock, 220) + code, answer = %Private.check_answer(sock, 220) if not code then return nil, answer end -- HELO - code, answer = %send_helo(sock) + code, answer = %Private.send_helo(sock) if not code then return nil, answer end return sock end -function smtp_send(sock, from, rcpt, mime, body) +----------------------------------------------------------------------------- +-- Sends a message using an opened server +-- Input +-- sock: socket connected to server +-- message: a table with the following fields: +-- from: message sender's e-mail +-- rcpt: message recipient's e-mail +-- headers: message mime headers +-- body: messge body +-- Returns +-- code: server status code, nil if error +-- answer: complete server reply +----------------------------------------------------------------------------- +function Private.send(sock, message) local code, answer -- MAIL - code, answer = %send_mail(sock, from) + code, answer = %Private.send_mail(sock, message.from) if not code then return nil, answer end -- RCPT - code, answer = %send_rcpt(sock, rcpt) + code, answer = %Private.send_rcpt(sock, message.rcpt) if not code then return nil, answer end -- DATA - return %send_data(sock, mime, body) + return %Private.send_data(sock, message.headers, message.body) end -function smtp_close(sock) +----------------------------------------------------------------------------- +-- Closes connection with server +-- Input +-- sock: socket connected to server +-- Returns +-- code: server status code, nil if error +-- answer: complete server reply +----------------------------------------------------------------------------- +function Private.close(sock) -- QUIT - return %send_quit(sock) + return %Private.send_quit(sock) end ----------------------------------------------------------------------------- -- Main mail function -- Input --- from: message sender --- rcpt: table containing message recipients --- mime: table containing mime headers --- body: message body --- server: smtp server to be used +-- message: a table with the following fields: +-- from: message sender +-- rcpt: table containing message recipients +-- headers: table containing mime headers +-- body: message body +-- server: smtp server to be used -- Returns -- nil if successfull, error message in case of error ----------------------------------------------------------------------------- -function smtp_mail(from, rcpt, mime, body, server) - local sock, err = smtp_connect(server) +function Public.mail(message) + local sock, err = %Private.open(message.server) if not sock then return err end - local code, answer = smtp_send(sock, from, rcpt, mime, body) + local code, answer = %Private.send(sock, message) if not code then return answer end - code, answer = smtp_close(sock) + code, answer = %Private.close(sock) if code then return nil end return answer end - ---=========================================================================== --- Compatibility functions ---=========================================================================== ------------------------------------------------------------------------------ --- Converts a comma separated list into a Lua table with one entry for each --- list element. --- Input --- str: string containing the list to be converted --- tab: table to be filled with entries --- Returns --- a table t, where t.n is the number of elements with an entry t[i] --- for each element ------------------------------------------------------------------------------ -local fill = function(str, tab) - gsub(str, "([^%s,]+)", function (w) tinsert(%tab, w) end) - return tab -end - ------------------------------------------------------------------------------ --- Client mail function, implementing CGILUA 3.2 interface ------------------------------------------------------------------------------ -function mail(msg) - local rcpt = {} - local mime = {} - mime["Subject"] = msg.subject - mime["To"] = msg.to - mime["From"] = msg.from - %fill(msg.to, rcpt) - if msg.cc then - %fill(msg.cc, rcpt) - mime["Cc"] = msg.cc - end - if msg.bcc then %fill(msg.bcc, rcpt) end - rcpt.n = nil - return %smtp_mail(msg.from, rcpt, mime, msg.message, msg.mailserver) -end From 5c622071f95b833727767219cdee31b1419d6cc4 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 12 Sep 2001 18:27:40 +0000 Subject: [PATCH 064/483] Initial revision --- src/url.lua | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 src/url.lua diff --git a/src/url.lua b/src/url.lua new file mode 100644 index 0000000..8cafd9b --- /dev/null +++ b/src/url.lua @@ -0,0 +1,204 @@ +----------------------------------------------------------------------------- +-- URI parsing, composition and relative URL resolution +-- LuaSocket 1.4 toolkit. +-- Author: Diego Nehab +-- Date: 20/7/2001 +-- Conforming to: RFC 2396, LTN7 +-- RCS ID: $Id$ +---------------------------------------------------------------------------- + +local Public, Private = {}, {} +URL = Public + +----------------------------------------------------------------------------- +-- Parses a url and returns a table with all its parts according to RFC 2396 +-- The following grammar describes the names given to the URL parts +-- ::= :///;?# +-- ::= @: +-- ::= [:] +-- :: = {/} +-- Input +-- url: uniform resource locator of request +-- default: table with default values for each field +-- Returns +-- table with the following fields, where RFC naming conventions have +-- been preserved: +-- scheme, authority, userinfo, user, password, host, port, +-- path, params, query, fragment +-- Obs: +-- the leading '/' in {/} is considered part of +----------------------------------------------------------------------------- +function Public.parse_url(url, default) + -- initialize default parameters + local parsed = default or {} + -- empty url is parsed to nil + if not url or url == "" then return nil end + -- remove whitespace + url = gsub(url, "%s", "") + -- get fragment + url = gsub(url, "#(.*)$", function(f) %parsed.fragment = f end) + -- get scheme + url = gsub(url, "^([%w][%w%+%-%.]*)%:", function(s) %parsed.scheme = s end) + -- get authority + url = gsub(url, "^//([^/]*)", function(n) %parsed.authority = n end) + -- get query string + url = gsub(url, "%?(.*)", function(q) %parsed.query = q end) + -- get params + url = gsub(url, "%;(.*)", function(p) %parsed.params = p end) + if url ~= "" then parsed.path = url end + local authority = parsed.authority + if not authority then return parsed end + authority = gsub(authority,"^([^@]*)@",function(u) %parsed.userinfo = u end) + authority = gsub(authority, ":([^:]*)$", function(p) %parsed.port = p end) + if authority ~= "" then parsed.host = authority end + local userinfo = parsed.userinfo + if not userinfo then return parsed end + userinfo = gsub(userinfo, ":([^:]*)$", function(p) %parsed.password = p end) + parsed.user = userinfo + return parsed +end + +----------------------------------------------------------------------------- +-- Rebuilds a parsed URL from its components. +-- Components are protected if any reserved or unallowed characters are found +-- Input +-- parsed: parsed URL, as returned by Public.parse +-- Returns +-- a string with the corresponding URL +----------------------------------------------------------------------------- +function Public.build_url(parsed) + local url = parsed.path or "" + if parsed.params then url = url .. ";" .. parsed.params end + if parsed.query then url = url .. "?" .. parsed.query end + if parsed.authority then url = "//" .. parsed.authority .. url end + if parsed.scheme then url = parsed.scheme .. ":" .. url end + if parsed.fragment then url = url .. "#" .. parsed.fragment end + return gsub(url, "%s", "") +end + +----------------------------------------------------------------------------- +-- Builds a absolute URL from a base and a relative URL according to RFC 2396 +-- Input +-- base_url +-- relative_url +-- Returns +-- corresponding absolute url +----------------------------------------------------------------------------- +function Public.absolute_url(base_url, relative_url) + local base = %Public.parse_url(base_url) + local relative = %Public.parse_url(relative_url) + if not base then return relative_url + elseif not relative then return base_url + elseif relative.scheme then return relative_url + else + relative.scheme = base.scheme + if not relative.authority then + relative.authority = base.authority + if not relative.path then + relative.path = base.path + if not relative.params then + relative.params = base.params + if not relative.query then + relative.query = base.query + end + end + else + relative.path = %Private.absolute_path(base.path,relative.path) + end + end + return %Public.build_url(relative) + end +end + +----------------------------------------------------------------------------- +-- Breaks a path into its segments, unescaping the segments +-- Input +-- path +-- Returns +-- segment: a table with one entry per segment +----------------------------------------------------------------------------- +function Public.parse_path(path) + local parsed = {} + path = gsub(path, "%s", "") + gsub(path, "([^/]+)", function (s) tinsert(%parsed, s) end) + for i = 1, getn(parsed) do + parsed[i] = Code.unescape(parsed[i]) + end + if strsub(path, 1, 1) == "/" then parsed.is_absolute = 1 end + if strsub(path, -1, -1) == "/" then parsed.is_directory = 1 end + return parsed +end + +----------------------------------------------------------------------------- +-- Builds a path component from its segments, escaping protected characters. +-- Input +-- parsed: path segments +-- Returns +-- path: correspondin path string +----------------------------------------------------------------------------- +function Public.build_path(parsed) + local path = "" + local n = getn(parsed) + for i = 1, n-1 do + path = path .. %Private.protect_segment(parsed[i]) + path = path .. "/" + end + if n > 0 then + path = path .. %Private.protect_segment(parsed[n]) + if parsed.is_directory then path = path .. "/" end + end + if parsed.is_absolute then path = "/" .. path end + return path +end + +function Private.make_set(table) + local s = {} + for i = 1, getn(table) do + s[table[i]] = 1 + end + return s +end + +-- these are allowed withing a path segment, along with alphanum +-- other characters must be escaped +Private.segment_set = Private.make_set { + "-", "_", ".", "!", "~", "*", "'", "(", + ")", ":", "@", "&", "=", "+", "$", ",", +} + +function Private.protect_segment(s) + local segment_set = %Private.segment_set + return gsub(s, "(%W)", function (c) + if %segment_set[c] then return c + else return Code.escape(c) end + end) +end + +----------------------------------------------------------------------------- +-- Builds a path from a base path and a relative path +-- Input +-- base_path +-- relative_path +-- Returns +-- corresponding absolute path +----------------------------------------------------------------------------- +function Private.absolute_path(base_path, relative_path) + if strsub(relative_path, 1, 1) == "/" then return relative_path end + local path = gsub(base_path, "[^/]*$", "") + path = path .. relative_path + path = gsub(path, "([^/]*%./)", function (s) + if s ~= "./" then return s else return "" end + end) + path = gsub(path, "/%.$", "/") + local reduced + while reduced ~= path do + reduced = path + path = gsub(reduced, "([^/]*/%.%./)", function (s) + if s ~= "../../" then return "" else return s end + end) + end + path = gsub(reduced, "([^/]*/%.%.)$", function (s) + if s ~= "../.." then return "" else return s end + end) + return path +end From 9546cd10ab0f9e51123e3111548549d36f10d473 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 12 Sep 2001 18:27:55 +0000 Subject: [PATCH 065/483] Updated for LuaSocket 1.4. Better tested, some name changes. --- src/ftp.lua | 571 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 334 insertions(+), 237 deletions(-) diff --git a/src/ftp.lua b/src/ftp.lua index bbb672b..98b08eb 100644 --- a/src/ftp.lua +++ b/src/ftp.lua @@ -1,60 +1,62 @@ ----------------------------------------------------------------------------- --- Simple FTP support for the Lua language using the LuaSocket 1.3b toolkit. +-- FTP support for the Lua language +-- LuaSocket 1.4 toolkit. -- Author: Diego Nehab -- Date: 26/12/2000 --- Conforming to: RFC 959 +-- Conforming to: RFC 959, LTN7 +-- RCS ID: $Id$ ----------------------------------------------------------------------------- +local Public, Private = {}, {} +FTP = Public + ----------------------------------------------------------------------------- -- Program constants ----------------------------------------------------------------------------- -- timeout in seconds before the program gives up on a connection -local TIMEOUT = 60 +Public.TIMEOUT = 60 -- default port for ftp service -local PORT = 21 +Public.PORT = 21 -- this is the default anonymous password. used when no password is --- provided in url. should be changed for your e-mail. -local EMAIL = "anonymous@anonymous.org" +-- provided in url. should be changed to your e-mail. +Public.EMAIL = "anonymous@anonymous.org" -- block size used in transfers -local BLOCKSIZE = 4096 +Public.BLOCKSIZE = 8192 ----------------------------------------------------------------------------- --- Parses a url and returns its scheme, user, password, host, port --- and path components, according to RFC 1738, Uniform Resource Locators (URL), --- of December 1994 --- Input --- url: unique resource locator desired --- default: table containing default values to be returned --- Returns --- table with the following fields: --- host: host to connect --- path: url path --- port: host port to connect --- user: user name --- pass: password --- scheme: protocol +-- Required libraries ----------------------------------------------------------------------------- -local split_url = function(url, default) - -- initialize default parameters - local parsed = default or {} - -- get scheme - url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end) - -- get user name and password. both can be empty! - -- moreover, password can be ommited - url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p) - %parsed.user = u - -- there can be an empty password, but the ':' has to be there - -- or else there is no password - %parsed.pass = nil -- kill default password - if c == ":" then %parsed.pass = p end - end) - -- get host - url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end) - -- get port if any - url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end) - -- whatever is left is the path - if url ~= "" then parsed.path = url end - return parsed +dofile "concat.lua" +dofile "url.lua" +dofile "code.lua" + +----------------------------------------------------------------------------- +-- Tries to send DOS mode lines. Closes socket on error. +-- Input +-- sock: server socket +-- line: string to be sent +-- Returns +-- err: message in case of error, nil if successfull +----------------------------------------------------------------------------- +function Private.try_sendline(sock, line) + local err = sock:send(line .. "\r\n") + if err then sock:close() end + return err +end + +----------------------------------------------------------------------------- +-- Tries to get a pattern from the server and closes socket on error +-- sock: socket connected to the server +-- ...: pattern to receive +-- Returns +-- ...: received pattern +-- err: error message if any +----------------------------------------------------------------------------- +function Private.try_receive(...) + local sock = arg[1] + local data, err = call(sock.receive, arg) + if err then sock:close() end + return data, err end ----------------------------------------------------------------------------- @@ -65,14 +67,12 @@ end -- ip: string containing ip for data connection -- port: port for data connection ----------------------------------------------------------------------------- -local get_pasv = function(pasv) - local a,b,c,d,p1,p2 +function Private.get_pasv(pasv) + local a, b, c, d, p1, p2, _ local ip, port _,_, a, b, c, d, p1, p2 = strfind(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)") - if not (a and b and c and d and p1 and p2) then - return nil, nil - end + if not (a and b and c and d and p1 and p2) then return nil, nil end ip = format("%d.%d.%d.%d", a, b, c, d) port = tonumber(p1)*256 + tonumber(p2) return ip, port @@ -87,12 +87,11 @@ end -- Returns -- error message in case of error, nil otherwise ----------------------------------------------------------------------------- -local send_command = function(control, cmd, arg) - local line, err - if arg then line = cmd .. " " .. arg .. "\r\n" - else line = cmd .. "\r\n" end - err = control:send(line) - return err +function Private.send_command(control, cmd, arg) + local line + if arg then line = cmd .. " " .. arg + else line = cmd end + return %Private.try_sendline(control, line) end ----------------------------------------------------------------------------- @@ -103,16 +102,16 @@ end -- answer: whole server reply, nil if error -- code: answer status code or error message ----------------------------------------------------------------------------- -local get_answer = function(control) - local code, lastcode, sep - local line, err = control:receive() +function Private.get_answer(control) + local code, lastcode, sep, _ + local line, err = %Private.try_receive(control) local answer = line if err then return nil, err end _,_, code, sep = strfind(line, "^(%d%d%d)(.)") if not code or not sep then return nil, answer end if sep == "-" then -- answer is multiline repeat - line, err = control:receive() + line, err = %Private.try_receive(control) if err then return nil, err end _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)") answer = answer .. "\n" .. line @@ -130,12 +129,9 @@ end -- code: reply code or nil in case of error -- answer: server complete answer or system error message ----------------------------------------------------------------------------- -local check_answer = function(control, success) - local answer, code = %get_answer(control) - if not answer then - control:close() - return nil, code - end +function Private.check_answer(control, success) + local answer, code = %Private.get_answer(control) + if not answer then return nil, code end if type(success) ~= "table" then success = {success} end for i = 1, getn(success) do if code == success[i] then @@ -158,38 +154,10 @@ end -- code: reply code or nil in case of error -- answer: server complete answer or system error message ----------------------------------------------------------------------------- -local try_command = function(control, cmd, arg, success) - local err = %send_command(control, cmd, arg) - if err then - control:close() - return nil, err - end - local code, answer = %check_answer(control, success) - if not code then return nil, answer end - return code, answer -end - ------------------------------------------------------------------------------ --- Creates a table with all directories in path --- Input --- file: abolute path to file --- Returns --- a table with the following fields --- name: filename --- path: directory to file --- isdir: is it a directory? ------------------------------------------------------------------------------ -local split_path = function(file) - local parsed = {} - file = gsub(file, "(/)$", function(i) %parsed.isdir = i end) - if not parsed.isdir then - file = gsub(file, "([^/]+)$", function(n) %parsed.name = n end) - end - file = gsub(file, "/$", "") - file = gsub(file, "^/", "") - if file == "" then file = nil end - parsed.path = file - if parsed.path or parsed.name or parsed.isdir then return parsed end +function Private.command(control, cmd, arg, success) + local err = %Private.send_command(control, cmd, arg) + if err then return nil, err end + return %Private.check_answer(control, success) end ----------------------------------------------------------------------------- @@ -200,11 +168,10 @@ end -- code: nil if error -- answer: server answer or error message ----------------------------------------------------------------------------- -local check_greeting = function(control) - local code, answer = %check_answer(control, {120, 220}) - if not code then return nil, answer end +function Private.greet(control) + local code, answer = %Private.check_answer(control, {120, 220}) if code == 120 then -- please try again, somewhat busy now... - code, answer = %check_answer(control, {220}) + return %Private.check_answer(control, {220}) end return code, answer end @@ -214,16 +181,15 @@ end -- Input -- control: control connection with server -- user: user name --- pass: user password if any +-- password: user password if any -- Returns -- code: nil if error -- answer: server answer or error message ----------------------------------------------------------------------------- -local login = function(control, user, pass) - local code, answer = %try_command(control, "user", parsed.user, {230, 331}) - if not code then return nil, answer end - if code == 331 and parsed.pass then -- need pass and we have pass - code, answer = %try_command(control, "pass", parsed.pass, {230, 202}) +function Private.login(control, user, password) + local code, answer = %Private.command(control, "user", user, {230, 331}) + if code == 331 and password then -- need pass and we have pass + return %Private.command(control, "pass", password, {230, 202}) end return code, answer end @@ -237,12 +203,9 @@ end -- code: nil if error -- answer: server answer or error message ----------------------------------------------------------------------------- -local cwd = function(control, path) - local code, answer = 250, "Home directory used" - if path then - code, answer = %try_command(control, "cwd", path, {250}) - end - return code, answer +function Private.cwd(control, path) + if path then return %Private.command(control, "cwd", path, {250}) + else return 250, nil end end ----------------------------------------------------------------------------- @@ -253,20 +216,19 @@ end -- server: server socket bound to local address, nil if error -- answer: error message if any ----------------------------------------------------------------------------- -local port = function(control) +function Private.port(control) local code, answer local server, ctl_ip ctl_ip, answer = control:getsockname() server, answer = bind(ctl_ip, 0) - server:timeout(%TIMEOUT) + server:timeout(%Public.TIMEOUT) local ip, p, ph, pl ip, p = server:getsockname() pl = mod(p, 256) ph = (p - pl)/256 local arg = gsub(format("%s,%d,%d", ip, ph, pl), "%.", ",") - code, answer = %try_command(control, "port", arg, {200}) + code, answer = %Private.command(control, "port", arg, {200}) if not code then - control:close() server:close() return nil, answer else return server end @@ -280,10 +242,9 @@ end -- code: nil if error -- answer: server answer or error message ----------------------------------------------------------------------------- -local logout = function(control) - local code, answer = %try_command(control, "quit", nil, {221}) - if not code then return nil, answer end - control:close() +function Private.logout(control) + local code, answer = %Private.command(control, "quit", nil, {221}) + if code then control:close() end return code, answer end @@ -295,10 +256,10 @@ end -- Returns -- nil if successfull, or an error message in case of error ----------------------------------------------------------------------------- -local receive_indirect = function(data, callback) +function Private.receive_indirect(data, callback) local chunk, err, res while not err do - chunk, err = data:receive(%BLOCKSIZE) + chunk, err = %Private.try_receive(data, %Public.BLOCKSIZE) if err == "closed" then err = "done" end res = callback(chunk, err) if not res then break end @@ -310,33 +271,38 @@ end -- Input -- control: control connection with server -- server: server socket bound to local address --- file: file name under current directory --- isdir: is file a directory name? --- callback: callback to receive file contents +-- name: file name +-- is_directory: is file a directory name? +-- download_cb: callback to receive file contents -- Returns -- err: error message in case of error, nil otherwise ----------------------------------------------------------------------------- -local retrieve = function(control, server, file, isdir, callback) +function Private.retrieve(control, server, name, is_directory, download_cb) local code, answer local data -- ask server for file or directory listing accordingly - if isdir then code, answer = %try_command(control, "nlst", file, {150, 125}) - else code, answer = %try_command(control, "retr", file, {150, 125}) end + if is_directory then + code, answer = %Private.cwd(control, name) + if not code then return answer end + code, answer = %Private.command(control, "nlst", nil, {150, 125}) + else + code, answer = %Private.command(control, "retr", name, {150, 125}) + end + if not code then return nil, answer end data, answer = server:accept() server:close() if not data then control:close() return answer end - answer = %receive_indirect(data, callback) + answer = %Private.receive_indirect(data, download_cb) if answer then control:close() return answer end data:close() -- make sure file transfered ok - code, answer = %check_answer(control, {226, 250}) - if not code then return answer end + return %Private.check_answer(control, {226, 250}) end ----------------------------------------------------------------------------- @@ -344,11 +310,11 @@ end -- Input -- data: data connection -- send_cb: callback to produce file contents --- chunk, size: first callback results +-- chunk, size: first callback return values -- Returns -- nil if successfull, or an error message in case of error ----------------------------------------------------------------------------- -local try_sendindirect = function(data, send_cb, chunk, size) +function Private.send_indirect(data, send_cb, chunk, size) local sent, err sent = 0 while 1 do @@ -358,9 +324,9 @@ local try_sendindirect = function(data, send_cb, chunk, size) else return "invalid callback return" end end err = data:send(chunk) - if err then - data:close() - return err + if err then + data:close() + return err end sent = sent + strlen(chunk) if sent >= size then break end @@ -379,9 +345,9 @@ end -- code: return code, nil if error -- answer: server answer or error message ----------------------------------------------------------------------------- -local store = function(control, server, file, send_cb) - local data - local code, answer = %try_command(control, "stor", file, {150, 125}) +function Private.store(control, server, file, send_cb) + local data, err + local code, answer = %Private.command(control, "stor", file, {150, 125}) if not code then control:close() return nil, answer @@ -394,7 +360,7 @@ local store = function(control, server, file, send_cb) return nil, answer end -- send whole file - err = %try_sendindirect(data, send_cb, send_cb()) + err = %Private.send_indirect(data, send_cb, send_cb()) if err then control:close() return nil, err @@ -402,144 +368,275 @@ local store = function(control, server, file, send_cb) -- close connection to inform that file transmission is complete data:close() -- check if file was received correctly - return %check_answer(control, {226, 250}) + return %Private.check_answer(control, {226, 250}) end ----------------------------------------------------------------------------- -- Change transfer type -- Input -- control: control connection with server --- type: new transfer type --- Returns --- code: nil if error --- answer: server answer or error message ------------------------------------------------------------------------------ -local change_type = function(control, type) - if type == "b" then type = "i" else type = "a" end - return %try_command(control, "type", type, {200}) -end - ------------------------------------------------------------------------------ --- Retrieve a file from a ftp server --- Input --- url: file location --- receive_cb: callback to receive file contents --- type: "binary" or "ascii" +-- params: "type=i" for binary or "type=a" for ascii -- Returns -- err: error message if any ----------------------------------------------------------------------------- -function ftp_getindirect(url, receive_cb, type) - local control, server, data, err - local answer, code, server, pfile, file - parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) +function Private.change_type(control, params) + local type + if params == "type=i" then type = "i" + elseif params == "type=a" then type = "a" end + if type then + local code, err = %Private.command(control, "type", type, {200}) + if not code then return err end + end +end + +----------------------------------------------------------------------------- +-- Starts a control connection, checks the greeting and log on +-- Input +-- parsed: parsed URL components +-- Returns +-- control: control connection with server, or nil if error +-- err: error message if any +----------------------------------------------------------------------------- +function Private.open(parsed) -- start control connection - control, err = connect(parsed.host, parsed.port) - if not control then return err end - control:timeout(%TIMEOUT) - -- get and check greeting - code, answer = %check_greeting(control) - if not code then return answer end + local control, err = connect(parsed.host, parsed.port) + if not control then return nil, err end + -- make sure we don't block forever + control:timeout(%Public.TIMEOUT) + -- check greeting + local code, answer = %Private.greet(control) + if not code then return nil, answer end -- try to log in - code, answer = %login(control, parsed.user, parsed.pass) - if not code then return answer end - -- go to directory - pfile = %split_path(parsed.path) - if not pfile then return "invalid path" end - code, answer = %cwd(control, pfile.path) - if not code then return answer end - -- change to binary type? - code, answer = %change_type(control, type) - if not code then return answer end + code, err = %Private.login(control, parsed.user, parsed.password) + if not code then return nil, err + else return control end +end + +----------------------------------------------------------------------------- +-- Closes the connection with the server +-- Input +-- control: control connection with server +----------------------------------------------------------------------------- +function Private.close(control) + -- disconnect + %Private.logout(control) +end + +----------------------------------------------------------------------------- +-- Changes to the directory pointed to by URL +-- Input +-- control: control connection with server +-- segment: parsed URL path segments +-- Returns +-- err: error message if any +----------------------------------------------------------------------------- +function Private.change_dir(control, segment) + local n = getn(segment) + for i = 1, n-1 do + local code, answer = %Private.cwd(control, segment[i]) + if not code then return answer end + end +end + +----------------------------------------------------------------------------- +-- Stores a file in current directory +-- Input +-- control: control connection with server +-- request: a table with the fields: +-- upload_cb: send callback to send file contents +-- segment: parsed URL path segments +-- Returns +-- err: error message if any +----------------------------------------------------------------------------- +function Private.upload(control, request, segment) + local code, name, upload_cb + -- get remote file name + name = segment[getn(segment)] + if not name then + control:close() + return "Invalid file path" + end + upload_cb = request.upload_cb -- setup passive connection - server, answer = %port(control) + local server, answer = %Private.port(control) + if not server then return answer end + -- ask server to receive file + code, answer = %Private.store(control, server, name, upload_cb) + if not code then return answer end +end + +----------------------------------------------------------------------------- +-- Download a file from current directory +-- Input +-- control: control connection with server +-- request: a table with the fields: +-- download_cb: receive callback to receive file contents +-- segment: parsed URL path segments +-- Returns +-- err: error message if any +----------------------------------------------------------------------------- +function Private.download(control, request, segment) + local code, name, is_directory, download_cb + is_directory = segment.is_directory + download_cb = request.download_cb + -- get remote file name + name = segment[getn(segment)] + if not name and not is_directory then + control:close() + return "Invalid file path" + end + -- setup passive connection + local server, answer = %Private.port(control) if not server then return answer end -- ask server to send file or directory listing - err = %retrieve(control, server, pfile.name, pfile.isdir, receive_cb) - if err then return err end - -- disconnect - %logout(control) + code, answer = %Private.retrieve(control, server, name, + is_directory, download_cb) + if not code then return answer end +end + +----------------------------------------------------------------------------- +-- Parses the FTP URL setting default values +-- Input +-- request: a table with the fields: +-- url: the target URL +-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory +-- user: account user name +-- password: account password +-- Returns +-- parsed: a table with parsed components +----------------------------------------------------------------------------- +function Private.parse_url(request) + local parsed = URL.parse_url(request.url, { + host = "", + user = "anonymous", + port = 21, + path = "/", + password = %Public.EMAIL + }) + -- explicit login information overrides that given by URL + parsed.user = request.user or parsed.user + parsed.password = request.password or parsed.password + -- explicit representation type overrides that given by URL + if request.type then parsed.params = "type=" .. request.type end + return parsed +end + +----------------------------------------------------------------------------- +-- Parses the FTP URL path setting default values +-- Input +-- parsed: a table with the parsed URL components +-- Returns +-- dirs: a table with parsed directory components +----------------------------------------------------------------------------- +function Private.parse_path(parsed) + local segment = URL.parse_path(parsed.path) + segment.is_directory = segment.is_directory or (parsed.params == "type=d") + return segment +end + +----------------------------------------------------------------------------- +-- Builds a request table from a URL or request table +-- Input +-- url_or_request: target url or request table (a table with the fields: +-- url: the target URL +-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory +-- user: account user name +-- password: account password) +-- Returns +-- request: request table +----------------------------------------------------------------------------- +function Private.build_request(data) + local request = {} + if type(data) == "table" then for i, v in data do request[i] = v end + else request.url = data end + return request +end + +----------------------------------------------------------------------------- +-- Downloads a file from a FTP server +-- Input +-- request: a table with the fields: +-- url: the target URL +-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory +-- user: account user name +-- password: account password +-- download_cb: receive callback to receive file contents +-- Returns +-- err: error message if any +----------------------------------------------------------------------------- +function Public.get_cb(request) + local parsed = %Private.parse_url(request) + local control, err = %Private.open(parsed) + if not control then return err end + local segment = %Private.parse_path(parsed) + return %Private.change_dir(control, segment) or + %Private.change_type(control, parsed.params) or + %Private.download(control, request, segment) or + %Private.close(control) end ----------------------------------------------------------------------------- -- Uploads a file to a FTP server -- Input --- url: file location --- send_cb: callback to produce the file contents --- type: "binary" or "ascii" +-- request: a table with the fields: +-- url: the target URL +-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory +-- user: account user name +-- password: account password +-- upload_cb: send callback to send file contents -- Returns -- err: error message if any ----------------------------------------------------------------------------- -function ftp_putindirect(url, send_cb, type) - local control, data - local answer, code, server, file, pfile - parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) - -- start control connection - control, answer = connect(parsed.host, parsed.port) - if not control then return answer end - control:timeout(%TIMEOUT) - -- get and check greeting - code, answer = %check_greeting(control) - if not code then return answer end - -- try to log in - code, answer = %login(control, parsed.user, parsed.pass) - if not code then return answer end - -- go to directory - pfile = %split_path(parsed.path) - if not pfile or pfile.isdir then return "invalid path" end - code, answer = %cwd(control, pfile.path) - if not code then return answer end - -- change to binary type? - code, answer = %change_type(control, type) - if not code then return answer end - -- setup passive connection - server, answer = %port(control) - if not server then return answer end - -- ask server to send file - code, answer = %store(control, server, pfile.name, send_cb) - if not code then return answer end - -- disconnect - %logout(control) - -- no errors - return nil +function Public.put_cb(request) + local parsed = %Private.parse_url(request) + local control, err = %Private.open(parsed) + if not control then return err end + local segment = %Private.parse_path(parsed) + return %Private.change_dir(control, segment) or + %Private.change_type(control, parsed.params) or + %Private.upload(control, request, segment) or + %Private.close(control) end ----------------------------------------------------------------------------- -- Uploads a file to a FTP server -- Input --- url: file location --- bytes: file contents --- type: "binary" or "ascii" +-- url_or_request: target url or request table (a table with the fields: +-- url: the target URL +-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory +-- user: account user name +-- password: account password) +-- content: file contents -- Returns -- err: error message if any ----------------------------------------------------------------------------- -function ftp_put(url, bytes, type) - local send_cb = function() - return %bytes, strlen(%bytes) +function Public.put(url_or_request, content) + local request = %Private.build_request(url_or_request) + request.upload_cb = function() + return %content, strlen(%content) end - return ftp_putindirect(url, send_cb, type) + return %Public.put_cb(request) end ------------------------------------------------------------------------------ --- We need fast concatenation routines for direct requests ------------------------------------------------------------------------------ -dofile("buffer.lua") - ----------------------------------------------------------------------------- -- Retrieve a file from a ftp server -- Input --- url: file location --- type: "binary" or "ascii" +-- url_or_request: target url or request table (a table with the fields: +-- url: the target URL +-- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory +-- user: account user name +-- password: account password) -- Returns -- data: file contents as a string -- err: error message in case of error, nil otherwise ----------------------------------------------------------------------------- -function ftp_get(url, type) - local bytes = { buf = buf_create() } - local receive_cb = function(chunk, err) - if not chunk then %bytes.buf = nil end - buf_addstring(%bytes.buf, chunk) +function Public.get(url_or_request) + local cat = Concat.create() + local request = %Private.build_request(url_or_request) + request.download_cb = function(chunk, err) + if chunk then %cat:addstring(chunk) end return 1 end - err = ftp_getindirect(url, receive_cb, type) - return buf_getresult(bytes.buf), err + local err = %Public.get_cb(request) + return cat:getresult(), err end From b2df4282a37381438c46d4d8dbc520c1bf8ae7b1 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 12 Sep 2001 18:34:31 +0000 Subject: [PATCH 066/483] Updated for LuaSocket 1.4 --- README | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/README b/README index 6b84d8a..e548d03 100644 --- a/README +++ b/README @@ -1,5 +1,6 @@ This directory contains the implementation of the protocols FTP, HTTP -and SMTP. The files provided are: +and SMTP, the URL parsing and composition module and the Concat and Code +auxiliary modules. The files provided are: http.lua -- HTTP protocol implementation @@ -16,17 +17,22 @@ SMTP mail server. The implementation conforms to RFC 821. The module ftp.lua provides functions to download and upload files from and to FTP servers. The implementation conforms to RFC 959. - base64.lua -- base64 encoding implementation + url.lua -- URL parsing and composition -The base64.lua module provides base64 encoding and decoding. The module -is used for the HTTP Basic Authentication Scheme, and conforms to RFC -1521. +The module url.lua provides routines to split a URL into its components +and to compose a base URL and relative URL into an absolute URL. - buffer.lua -- fast concatenation library + code.lua -- some coding conversion routines -The module buffer.lua implements, completely in Lua, a set of functions +The code.lua module provides base64, hexa and escaped encoding and +decoding. The module is used for the HTTP Basic Authentication Scheme, +and URL protection, conforming to RFC 1521. + + concat.lua -- fast concatenation library + +The module concat.lua implements, completely in Lua, a set of functions that greatly improves the performance of repeated concatenations of Lua -strings. The module was created by Roberto Ierusalimschy. +strings. The algorithm was inventd by Roberto Ierusalimschy. These modules are part of the LuaSocket library and are supported. Please send any comments to diego@tecgraf.puc-rio.br. From d794e581ff769953d4f34ae44ecd14ac200f4991 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 12 Sep 2001 22:07:08 +0000 Subject: [PATCH 067/483] lua_socketlibopen now returns a result code. --- src/luasocket.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index 3b3f697..bbf7ca7 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -8,12 +8,14 @@ * This module is part of an effort to make the most important features * of the IPv4 Socket layer available to Lua scripts. * The Lua interface to TCP/IP follows the BSD TCP/IP API closely, -* trying to simplify all tasks involved in setting up a client connection -* and server connections. +* trying to simplify all tasks involved in setting up both client +* and server connections. * The provided IO routines, send and receive, follow the Lua style, being * very similar to the standard Lua read and write functions. * The module implements both a BSD bind and a Winsock2 bind, and has * been tested on several Unix flavors, as well as Windows 98 and NT. +* +* RCS ID: $Id$ \*=========================================================================*/ /*=========================================================================*\ @@ -1660,7 +1662,7 @@ static int receive_word(lua_State *L, p_sock sock) * Initializes the library interface with Lua and the socket library. * Defines the symbols exported to Lua. \*-------------------------------------------------------------------------*/ -LUASOCKET_API void lua_socketlibopen(lua_State *L) +LUASOCKET_API int lua_socketlibopen(lua_State *L) { struct luaL_reg funcs[] = { {"bind", global_tcpbind}, @@ -1674,6 +1676,8 @@ LUASOCKET_API void lua_socketlibopen(lua_State *L) /* declare new Lua tags for used userdata values */ p_tags tags = (p_tags) lua_newuserdata(L, sizeof(t_tags)); if (!tags) lua_error(L, "out of memory"); + /* remove tags userdatum from stack but avoid garbage collection */ + lua_ref(L, 1); tags->client = lua_newtag(L); tags->server = lua_newtag(L); tags->table = lua_newtag(L); @@ -1690,7 +1694,7 @@ LUASOCKET_API void lua_socketlibopen(lua_State *L) lua_settagmethod(L, tags->table, "gc"); #ifdef WIN32 /* WinSock needs special initialization */ - winsock_open(); + if (!winsock_open()) return 0; #else /* avoid getting killed by a SIGPIPE signal thrown by send */ handle_sigpipe(); @@ -1716,6 +1720,7 @@ LUASOCKET_API void lua_socketlibopen(lua_State *L) } } #endif + return 1; } /*=========================================================================*\ From 5b7d44e961ba6651bc5c37da08ebcb999ed95d79 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 12 Sep 2001 22:08:43 +0000 Subject: [PATCH 068/483] lua_socketlibopen now returns a result code. Updated for LuaSocket 1.4. --- src/luasocket.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/luasocket.h b/src/luasocket.h index a580b2c..319bffb 100644 --- a/src/luasocket.h +++ b/src/luasocket.h @@ -8,7 +8,7 @@ #define _LUASOCKET_H_ /* Current luasocket version */ -#define LUASOCKET_VERSION "LuaSocket 1.3b" +#define LUASOCKET_VERSION "LuaSocket 1.4" /*-------------------------------------------------------------------------*\ * These can be changed to according to the applications' needs. @@ -31,6 +31,6 @@ * Initializes the library interface with Lua and the socket library. * Defines the symbols exported to Lua. \*-------------------------------------------------------------------------*/ -LUASOCKET_API void lua_socketlibopen(lua_State *L); +LUASOCKET_API int lua_socketlibopen(lua_State *L); #endif /* _LUASOCKET_H_ */ From b73a66e635fa2aa5362a389913b98ab86c72f21f Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 18 Sep 2001 18:41:01 +0000 Subject: [PATCH 069/483] Changed HTTP.get and HTTP.post to return status code instead of status line information. Who cares about the rest of the line, anyways... --- src/http.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/http.lua b/src/http.lua index 7eaaf9e..1cad3ef 100644 --- a/src/http.lua +++ b/src/http.lua @@ -633,7 +633,7 @@ end -- Returns -- body: response message body, or nil if failed -- headers: response header fields received, or nil if failed --- status: server response status line, or nil if failed +-- code: server response status code, or nil if failed -- error: error message if any ----------------------------------------------------------------------------- function Public.get(url_or_request) @@ -641,7 +641,7 @@ function Public.get(url_or_request) request.method = "GET" local response = %Public.request(request) return response.body, response.headers, - response.status, response.error + response.code, response.error end ----------------------------------------------------------------------------- @@ -656,7 +656,7 @@ end -- Returns -- body: response message body, or nil if failed -- headers: response header fields received, or nil if failed --- status: server response status line, or nil if failed +-- code: server response status code, or nil if failed -- error: error message, or nil if successfull ----------------------------------------------------------------------------- function Public.post(url_or_request, body) @@ -665,5 +665,5 @@ function Public.post(url_or_request, body) request.body = request.body or body local response = %Public.request(request) return response.body, response.headers, - response.status, response.error + response.code, response.error end From 29ab78a74a9ded9b72a757b372c78b454da4c45c Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 18 Sep 2001 20:22:59 +0000 Subject: [PATCH 070/483] HTTP.request was not returning response for body-less requests. --- src/http.lua | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/http.lua b/src/http.lua index 1cad3ef..2e8c8e9 100644 --- a/src/http.lua +++ b/src/http.lua @@ -156,8 +156,8 @@ function Private.receivebody_bychunks(sock, headers, receive_cb) go, uerr = receive_cb(nil, err) return uerr or err end - -- was it the last chunk? - if size <= 0 then break end + -- was it the last chunk? + if size <= 0 then break end -- get chunk chunk, err = %Private.try_receive(sock, size) if err then @@ -177,14 +177,14 @@ function Private.receivebody_bychunks(sock, headers, receive_cb) return uerr or err end end - -- the server should not send trailer headers because we didn't send a - -- header informing it we know how to deal with them. we do not risk - -- being caught unprepaired. - headers, err = %Private.receive_headers(sock, headers) - if err then + -- the server should not send trailer headers because we didn't send a + -- header informing it we know how to deal with them. we do not risk + -- being caught unprepaired. + headers, err = %Private.receive_headers(sock, headers) + if err then go, uerr = receive_cb(nil, err) return uerr or err - end + end -- let callback know we are done go, uerr = receive_cb("") return uerr @@ -257,7 +257,7 @@ end -- nil if successfull or an error message in case of error ----------------------------------------------------------------------------- function Private.receive_body(sock, headers, receive_cb) - local te = headers["transfer-encoding"] + local te = headers["transfer-encoding"] if te and te ~= "identity" then -- get by chunked transfer-coding of message body return %Private.receivebody_bychunks(sock, headers, receive_cb) @@ -436,7 +436,7 @@ function Private.authorize(request, parsed, response) request.headers["authorization"] = "Basic " .. Code.base64(parsed.user .. ":" .. parsed.password) local authorize = { - redirects = request.redirects, + redirects = request.redirects, method = request.method, url = request.url, body_cb = request.body_cb, @@ -540,10 +540,10 @@ end ----------------------------------------------------------------------------- function Public.request_cb(request, response) local parsed = URL.parse_url(request.url, { - host = "", - port = %Public.PORT, - path ="/" - }) + host = "", + port = %Public.PORT, + path ="/" + }) -- explicit authentication info overrides that given by the URL parsed.user = request.user or parsed.user parsed.password = request.password or parsed.password @@ -585,6 +585,7 @@ function Public.request_cb(request, response) return response end sock:close() + return response end ----------------------------------------------------------------------------- @@ -637,8 +638,8 @@ end -- error: error message if any ----------------------------------------------------------------------------- function Public.get(url_or_request) - local request = %Private.build_request(url_or_request) - request.method = "GET" + local request = %Private.build_request(url_or_request) + request.method = "GET" local response = %Public.request(request) return response.body, response.headers, response.code, response.error @@ -660,9 +661,9 @@ end -- error: error message, or nil if successfull ----------------------------------------------------------------------------- function Public.post(url_or_request, body) - local request = %Private.build_request(url_or_request) - request.method = "POST" - request.body = request.body or body + local request = %Private.build_request(url_or_request) + request.method = "POST" + request.body = request.body or body local response = %Public.request(request) return response.body, response.headers, response.code, response.error From 9f677cdbf4dee43b7fde43e2c673073e623451d9 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 25 Sep 2001 18:40:58 +0000 Subject: [PATCH 071/483] updated rfc reference from 1521 to 2045 --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index e548d03..c30799c 100644 --- a/README +++ b/README @@ -26,7 +26,7 @@ and to compose a base URL and relative URL into an absolute URL. The code.lua module provides base64, hexa and escaped encoding and decoding. The module is used for the HTTP Basic Authentication Scheme, -and URL protection, conforming to RFC 1521. +and URL protection, conforming to RFC 2045. concat.lua -- fast concatenation library From b319e6a1e86a54c23b4848c5be7c0670f349a63f Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 25 Sep 2001 21:32:52 +0000 Subject: [PATCH 072/483] updated due to test location changes --- test/tftptest.lua | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/test/tftptest.lua b/test/tftptest.lua index 7fb8253..1e9e1d5 100644 --- a/test/tftptest.lua +++ b/test/tftptest.lua @@ -1,16 +1,25 @@ -- load tftpclng.lua assert(dofile("../examples/tftpclnt.lua")) -assert(dofile("auxiliar.lua")) -- needs tftp server running on localhost, with root pointing to -- /home/i/diego/public/html/luasocket/test -host = host or "localhost" +function readfile(file) + local f = openfile("file", "rb") + local a + if f then + a = read(f, "*a") + closefile(f) + end + return a +end + +host = host or "goya" print("downloading") -err = tftp_get(host, 69, "test/index.html") +err = tftp_get(host, 69, "index.html", "index.got") assert(not err, err) -original = readfile("/home/i/diego/public/html/luasocket/test/index.html") -retrieved = readfile("index.html") -remove("index.html") +original = readfile("index.index") +retrieved = readfile("index.got") +remove("index.got") assert(original == retrieved, "files differ!") print("passed") From d36460a249261680a4a920f05767b7f7cf2868ba Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 25 Sep 2001 21:34:43 +0000 Subject: [PATCH 073/483] updated for luasocket 1.4 --- test/ftptest.lua | 137 ++++++++++++++---- test/httptest.lua | 356 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 402 insertions(+), 91 deletions(-) diff --git a/test/ftptest.lua b/test/ftptest.lua index 41f314d..7b0d3ab 100644 --- a/test/ftptest.lua +++ b/test/ftptest.lua @@ -1,34 +1,121 @@ +function mysetglobal (varname, oldvalue, newvalue) + print("changing " .. varname) + %rawset(%globals(), varname, newvalue) +end +function mygetglobal (varname, newvalue) + print("checking " .. varname) + return %rawget(%globals(), varname) +end +settagmethod(tag(nil), "setglobal", mysetglobal) +settagmethod(tag(nil), "getglobal", mygetglobal) + assert(dofile("../lua/ftp.lua")) -assert(dofile("../lua/buffer.lua")) -assert(dofile("auxiliar.lua")) +assert(dofile("../lua/url.lua")) +assert(dofile("../lua/concat.lua")) +assert(dofile("../lua/code.lua")) -pdir = "/home/i/diego/public/html/luasocket/test/" -ldir = "/home/luasocket/" -adir = "/home/ftp/test/" +local similar = function(s1, s2) + return strlower(gsub(s1, "%s", "")) == strlower(gsub(s2, "%s", "")) +end --- needs an accound luasocket:password --- and a copy of /home/i/diego/public/html/luasocket/test in ~ftp/test +local capture = function(cmd) + readfrom("| " .. cmd) + local s = read("*a") + readfrom() + return s +end -print("testing authenticated upload") -bf = readfile(pdir .. "index.html") -e = ftp_put("ftp://luasocket:password@localhost/index.html", bf, "b") -assert(not e, e) -assert(compare(ldir .. "index.html", bf), "files differ") -remove(ldir .. "index.html") +local readfile = function(name) + local f = readfrom(name) + if not f then return nil end + local s = read("*a") + readfrom() + return s +end -print("testing authenticated download") -f, e = ftp_get("ftp://luasocket:password@localhost/test/index.html", "b") -assert(f, e) -assert(compare(pdir .. "index.html", f), "files differ") +local check = function(v, e, o) + e = e or "failed!" + o = o or "ok" + if v then print(o) + else print(e) exit() end +end -print("testing anonymous download") -f, e = ftp_get("ftp://localhost/test/index.html", "b") -assert(f, e) -assert(compare(adir .. "index.html", f), "files differ") +-- needs an account luasocket:password +-- and some directories and files in ~ftp -print("testing directory listing") -f, e = ftp_get("ftp://localhost/test/") -assert(f, e) -assert(f == "index.html\r\n", "files differ") +local index, err, saved, back, expected + +local t = _time() + +index = readfile("index.html") + +write("testing file upload: ") +remove("/home/ftp/dir1/index.up.html") +err = FTP.put("ftp://localhost/dir1/index.up.html;type=i", index) +saved = readfile("/home/ftp/dir1/index.up.html") +check(not err and saved == index, err) + +write("testing file download: ") +back, err = FTP.get("ftp://localhost/dir1/index.up.html;type=i") +check(not err and back == index, err) + +write("testing no directory changes: ") +back, err = FTP.get("ftp://localhost/index.html;type=i") +check(not err and back == index, err) + +write("testing multiple directory changes: ") +back, err = FTP.get("ftp://localhost/dir1/dir2/dir3/dir4/dir5/dir6/index.html;type=i") +check(not err and back == index, err) + +write("testing authenticated upload: ") +remove("/home/luasocket/index.up.html") +err = FTP.put("ftp://luasocket:password@localhost/index.up.html;type=i", index) +saved = readfile("/home/luasocket/index.up.html") +check(not err and saved == index, err) + +write("testing authenticated download: ") +back, err = FTP.get("ftp://luasocket:password@localhost/index.up.html;type=i") +check(not err and back == index, err) + +write("testing weird-character translation: ") +back, err = FTP.get("ftp://luasocket:password@localhost/%2fhome/ftp/dir1/index.html;type=i") +check(not err and back == index, err) + +write("testing parameter overriding: ") +back, err = FTP.get { + url = "//stupid:mistake@localhost/dir1/index.html", + user = "luasocket", + password = "password", + type = "i" +} +check(not err and back == index, err) + +write("testing invalid url: ") +back, err = FTP.get("localhost/dir1/index.html;type=i") +local c, e = connect("", 21) +check(not back and err == e, err) + +write("testing directory listing: ") +expected = capture("ls -F /home/ftp/dir1 | grep -v /") +back, err = FTP.get("ftp://localhost/dir1;type=d") +check(similar(back, expected)) + +write("testing home directory listing: ") +expected = capture("ls -F /home/ftp | grep -v /") +back, err = FTP.get("ftp://localhost/") +check(back and similar(back, expected), nil, err) + +write("testing upload denial: ") +err = FTP.put("ftp://localhost/index.up.html;type=a", index) +check(err, err) + +write("testing authentication failure: ") +err = FTP.put("ftp://luasocket:wrong@localhost/index.html;type=a", index) +check(err, err) + +write("testing wrong file: ") +back, err = FTP.get("ftp://localhost/index.wrong.html;type=a") +check(err, err) print("passed all tests") +print(format("done in %.2fs", _time() - t)) diff --git a/test/httptest.lua b/test/httptest.lua index 8c78192..0b61729 100644 --- a/test/httptest.lua +++ b/test/httptest.lua @@ -1,89 +1,313 @@ --- load http -assert(dofile("../lua/http.lua")) -assert(dofile("../lua/base64.lua")) -assert(dofile("../lua/buffer.lua")) -assert(dofile("auxiliar.lua")) - -t = _time() - -- needs Alias from /home/i/diego/public/html/luasocket/test to -- /luasocket-test --- needs ScriptAlias from /home/i/diego/public/html/luasocket/test/cgi-bin --- to /luasocket-cgi-bin/ +-- needs ScriptAlias from /home/i/diego/public/html/luasocket/test/cgi +-- to /luasocket-test/cgi -function join(s, e) - return tostring(s) .. ":" .. tostring(e) +function mysetglobal (varname, oldvalue, newvalue) + print("changing " .. varname) + %rawset(%globals(), varname, newvalue) +end +function mygetglobal (varname, newvalue) + print("checking " .. varname) + return %rawget(%globals(), varname) +end +settagmethod(tag(nil), "setglobal", mysetglobal) +settagmethod(tag(nil), "getglobal", mygetglobal) + +local similar = function(s1, s2) + return strlower(gsub(s1, "%s", "")) == strlower(gsub(s2, "%s", "")) end -function status(s) - local code - _,_, code = strfind(s, "(%d%d%d)") - return tonumber(code) +local fail = function(s) + s = s or "failed!" + print(s) + exit() end -pdir = pdir or "/home/i/diego/public/html/luasocket/test/" +local readfile = function(name) + local f = readfrom(name) + if not f then return nil end + local s = read("*a") + readfrom() + return s +end + +local check = function (v, e) + if v then print("ok") + else %fail(e) end +end + +local check_request = function(request, expect, ignore) + local response = HTTP.request(request) + for i,v in response do + if not ignore[i] then + if v ~= expect[i] then %fail(i .. " differs!") end + end + end + for i,v in expect do + if not ignore[i] then + if v ~= response[i] then %fail(i .. " differs!") end + end + end + print("ok") +end + +local host, request, response, ignore, expect, index, prefix, cgiprefix + +-- load http +assert(dofile("../lua/http.lua")) +assert(dofile("../lua/code.lua")) +assert(dofile("../lua/concat.lua")) +assert(dofile("../lua/url.lua")) + +local t = _time() + host = host or "localhost" +prefix = prefix or "/luasocket-test" +cgiprefix = cgiprefix or "/luasocket-test-cgi" +index = readfile("index.html") -print("testing document retrieval") -url = "http://" .. host .. "/luasocket-test/index.html" -f, m, s, e = http_get(url) -assert(f and m and s and not e, join(s, e)) -assert(compare(pdir .. "index.html", f), "documents differ") +write("testing request uri correctness: ") +local forth = cgiprefix .. "/request-uri?" .. "this+is+the+query+string" +local back = HTTP.get("http://" .. host .. forth) +if similar(back, forth) then print("ok") +else fail("failed!") end -print("testing HTTP redirection") -url = "http://" .. host .. "/luasocket-test" -f, m, s, e = http_get(url) -assert(f and m and s and not e, join(s, e)) -assert(compare(pdir .. "index.html", f), "documents differ") +write("testing query string correctness: ") +forth = "this+is+the+query+string" +back = HTTP.get("http://" .. host .. cgiprefix .. "/query-string?" .. forth) +if similar(back, forth) then print("ok") +else fail("failed!") end -print("testing cgi output retrieval (probably chunked...)") -url = "http://" .. host .. "/luasocket-cgi-bin/cat-index-html" -f, m, s, e = http_get(url) -assert(f and m and s and not e, join(s, e)) -assert(compare(pdir .. "index.html", f), "documents differ") +write("testing document retrieval: ") +request = { + url = "http://" .. host .. prefix .. "/index.html" +} +expect = { + body = index, + code = 200 +} +ignore = { + status = 1, + headers = 1 +} +check_request(request, expect, ignore) -print("testing post method") -url = "http://" .. host .. "/luasocket-cgi-bin/cat" -rf = strrep("!@#$!@#%", 80000) -f, m, s, e = http_post(url, rf) -assert(f and m and s and not e) -assert(rf == f, "files differ") +write("testing HTTP redirection: ") +request = { + url = "http://" .. host .. prefix +} +expect = { + body = index, + code = 200 +} +ignore = { + status = 1, + headers = 1 +} +check_request(request, expect, ignore) -print("testing automatic auth failure") -url = "http://really:wrong@" .. host .. "/luasocket-test/auth/index.html" -f, m, s, e = http_get(url) -assert(f and m and s and not e and status(s) == 401) +write("testing automatic auth failure: ") +request = { + url = "http://really:wrong@" .. host .. prefix .. "/auth/index.html" +} +expect = { + code = 401 +} +ignore = { + body = 1, + status = 1, + headers = 1 +} +check_request(request, expect, ignore) + +write("testing HTTP redirection failure: ") +request = { + url = "http://" .. host .. prefix, + stay = 1 +} +expect = { + code = 301 +} +ignore = { + body = 1, + status = 1, + headers = 1 +} +check_request(request, expect, ignore) + write("testing host not found: ") -url = "http://wronghost/luasocket-test/index.html" -f, m, s, e = http_get(url) -assert(not f and not m and not s and e) -print(e) +request = { + url = "http://wronghost/does/not/exist" +} +local c, e = connect("wronghost", 80) +expect = { + error = e +} +ignore = {} +check_request(request, expect, ignore) -write("testing auth failure: ") -url = "http://" .. host .. "/luasocket-test/auth/index.html" -f, m, s, e = http_get(url) -assert(f and m and s and not e and status(s) == 401) -print(s) +write("testing invalid url: ") +request = { + url = host .. prefix +} +local c, e = connect("", 80) +expect = { + error = e +} +ignore = {} +check_request(request, expect, ignore) write("testing document not found: ") -url = "http://" .. host .. "/luasocket-test/wrongdocument.html" -f, m, s, e = http_get(url) -assert(f and m and s and not e and status(s) == 404) -print(s) +request = { + url = "http://" .. host .. "/wrongdocument.html" +} +expect = { + code = 404 +} +ignore = { + body = 1, + status = 1, + headers = 1 +} +check_request(request, expect, ignore) -print("testing manual auth") -url = "http://" .. host .. "/luasocket-test/auth/index.html" -h = {authorization = "Basic " .. base64("luasocket:password")} -f, m, s, e = http_get(url, h) -assert(f and m and s and not e, join(s, e)) -assert(compare(pdir .. "auth/index.html", f), "documents differ") +write("testing auth failure: ") +request = { + url = "http://" .. host .. prefix .. "/auth/index.html" +} +expect = { + code = 401 +} +ignore = { + body = 1, + status = 1, + headers = 1 +} +check_request(request, expect, ignore) -print("testing automatic auth") -url = "http://luasocket:password@" .. host .. "/luasocket-test/auth/index.html" -f, m, s, e = http_get(url) -assert(f and m and s and not e, join(s, e)) -assert(compare(pdir .. "auth/index.html", f), "documents differ") +write("testing manual basic auth: ") +request = { + url = "http://" .. host .. prefix .. "/auth/index.html", + headers = { + authorization = "Basic " .. Code.base64("luasocket:password") + } +} +expect = { + code = 200, + body = index +} +ignore = { + status = 1, + headers = 1 +} +check_request(request, expect, ignore) + +write("testing automatic basic auth: ") +request = { + url = "http://luasocket:password@" .. host .. prefix .. "/auth/index.html" +} +expect = { + code = 200, + body = index +} +ignore = { + status = 1, + headers = 1 +} +check_request(request, expect, ignore) + +write("testing auth info overriding: ") +request = { + url = "http://really:wrong@" .. host .. prefix .. "/auth/index.html", + user = "luasocket", + password = "password" +} +expect = { + code = 200, + body = index +} +ignore = { + status = 1, + headers = 1 +} +check_request(request, expect, ignore) + +write("testing cgi output retrieval (probably chunked...): ") +request = { + url = "http://" .. host .. cgiprefix .. "/cat-index-html" +} +expect = { + body = index, + code = 200 +} +ignore = { + status = 1, + headers = 1 +} +check_request(request, expect, ignore) + +write("testing redirect loop: ") +request = { + url = "http://" .. host .. cgiprefix .. "/redirect-loop" +} +expect = { + code = 302 +} +ignore = { + status = 1, + headers = 1, + body = 1 +} +check_request(request, expect, ignore) + +write("testing post method: ") +request = { + url = "http://" .. host .. cgiprefix .. "/cat", + method = "POST", + body = index +} +expect = { + body = index, + code = 200 +} +ignore = { + status = 1, + headers = 1 +} +check_request(request, expect, ignore) + +local body +write("testing simple get function: ") +body = HTTP.get("http://" .. host .. prefix .. "/index.html") +check(body == index) + +write("testing simple get function with table args: ") +body = HTTP.get { + url = "http://really:wrong@" .. host .. prefix .. "/auth/index.html", + user = "luasocket", + password = "password" +} +check(body == index) + +write("testing simple post function: ") +body = HTTP.post("http://" .. host .. cgiprefix .. "/cat", index) +check(body == index) + +write("testing simple post function with table args: ") +body = HTTP.post { + url = "http://" .. host .. cgiprefix .. "/cat", + body = index +} +check(body == index) + +write("testing HEAD method: ") +response = HTTP.request { + method = "HEAD", + url = "http://www.tecgraf.puc-rio.br/~diego/" +} +check(response and response.headers) print("passed all tests") From 6dd115015c6f2514a2f180aaf474a66364939c31 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 25 Sep 2001 21:35:17 +0000 Subject: [PATCH 074/483] Initial revision --- test/smtptest.lua | 306 ++++++++++++++++++++++++++++++++++++ test/urltest.lua | 388 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 694 insertions(+) create mode 100644 test/smtptest.lua create mode 100644 test/urltest.lua diff --git a/test/smtptest.lua b/test/smtptest.lua new file mode 100644 index 0000000..fcd238b --- /dev/null +++ b/test/smtptest.lua @@ -0,0 +1,306 @@ +local sent = {} + +local from = "luasock@tecgraf.puc-rio.br" +local server = "mail.tecgraf.puc-rio.br" +local rcpt = "luasock@tecgraf.puc-rio.br" + +local parse = {} + +local name = "/var/spool/mail/luasock" + +local t = _time() +local err + +function mysetglobal (varname, oldvalue, newvalue) + print("changing " .. varname) + %rawset(%globals(), varname, newvalue) +end +function mygetglobal (varname, newvalue) + print("checking " .. varname) + return %rawget(%globals(), varname) +end +settagmethod(tag(nil), "setglobal", mysetglobal) +settagmethod(tag(nil), "getglobal", mygetglobal) + +assert(dofile("../lua/smtp.lua")) +assert(dofile("../lua/cl-compat.lua")) + +local total = function() + local t = 0 + for i = 1, getn(%sent) do + t = t + %sent[i].count + end + return t +end + +local similar = function(s1, s2) + return strlower(gsub(s1, "%s", "")) == strlower(gsub(s2, "%s", "")) +end + +function parse.headers(headers_s) + local headers = {} + headers_s = "\n" .. headers_s .. "$$$:\n" + local i, j = 1, 1 + local name, value, _ + while 1 do + j = strfind(headers_s, "\n%S-:", i+1) + if not j then break end + _, _, name, value = strfind(strsub(headers_s, i+1, j-1), "(%S-): (.*)") + value = gsub(value, "\r\n", "\n") + value = gsub(value, "\n%s*", " ") + name = strlower(name) + if headers[name] then headers[name] = headers[name] .. ", " .. value + else headers[name] = value end + i, j = j, i + end + headers["$$$"] = nil + return headers +end + +function parse.message(message_s) + message_s = gsub(message_s, "^.-\n", "") + local _, headers_s, body + _, _, headers_s, body = strfind(message_s, "^(.-\n)\n(.*)") + headers_s = headers_s or "" + body = body or "" + return { headers = %parse.headers(headers_s), body = body } +end + +function parse.mbox(mbox_s) + local mbox = {} + mbox_s = "\n" .. mbox_s .. "\nFrom " + local i, j = 1, 1 + while 1 do + j = strfind(mbox_s, "\nFrom ", i + 1) + if not j then break end + tinsert(mbox, %parse.message(strsub(mbox_s, i + 1, j - 1))) + i, j = j, i + end + return mbox +end + +local readfile = function(name) + local f = readfrom(name) + if not f then return nil end + local s = read("*a") + readfrom() + return s +end + +local capture = function(cmd) + readfrom("| " .. cmd) + local s = read("*a") + readfrom() + return s +end + +local fail = function(s) + s = s or "failed!" + print(s) + exit() +end + +local empty = function() + local f = openfile(%name, "w") + closefile(f) +end + +local get = function() + return %readfile(%name) +end + +local list = function() + return %capture("ls -l " .. %name) +end + +local check_headers = function(sent, got) + sent = sent or {} + got = got or {} + for i,v in sent do + if not %similar(v, got[i]) then %fail("header " .. v .. "failed!") end + end +end + +local check_body = function(sent, got) + sent = sent or "" + got = got or "" + if not %similar(sent, got) then %fail("bodies differ!") end +end + +local check = function(sent, m) + write("checking ", m.headers.title, ": ") + for i = 1, getn(sent) do + local s = sent[i] + if s.title == m.headers.title and s.count > 0 then + %check_headers(s.headers, m.headers) + %check_body(s.body, m.body) + s.count = s.count - 1 + print("ok") + return + end + end + %fail("not found") +end + +local insert = function(sent, message) + if type(message.rcpt) == "table" then + message.count = getn(message.rcpt) + else message.count = 1 end + message.headers = message.headers or {} + message.headers.title = message.title + tinsert(sent, message) +end + +local mark = function() + local time = _time() + return { time = time } +end + +local wait = function(sentinel, n) + local to + write("waiting for ", n, " messages: ") + while 1 do + local mbox = %parse.mbox(%get()) + if n == getn(mbox) then break end + if _time() - sentinel.time > 50 then + to = 1 + break + end + _sleep(1) + write(".") + flush(_STDOUT) + end + if to then %fail("timeout") + else print("ok") end +end + +local stuffed_body = [[ +This message body needs to be +stuffed because it has a dot +. +by itself on a line. +Otherwise the mailer would +think that the dot +. +is the end of the message +and the remaining will cause +a lot of trouble. +]] + +insert(sent, { + from = from, + rcpt = { + "luasock2@tecgraf.puc-rio.br", + "luasock", + "luasock1" + }, + body = "multiple rcpt body", + title = "multiple rcpt", +}) + +insert(sent, { + from = from, + rcpt = { + "luasock2@tecgraf.puc-rio.br", + "luasock", + "luasock1" + }, + headers = { + header1 = "header 1", + header2 = "header 2", + header3 = "header 3", + header4 = "header 4", + header5 = "header 5", + header6 = "header 6", + }, + body = stuffed_body, + title = "complex message", +}) + +insert(sent, { + from = from, + rcpt = rcpt, + server = server, + body = "simple message body", + title = "simple message" +}) + +insert(sent, { + from = from, + rcpt = rcpt, + server = server, + body = stuffed_body, + title = "stuffed message body" +}) + +insert(sent, { + from = from, + rcpt = rcpt, + headers = { + header1 = "header 1", + header2 = "header 2", + header3 = "header 3", + header4 = "header 4", + header5 = "header 5", + header6 = "header 6", + }, + title = "multiple headers" +}) + +insert(sent, { + from = from, + rcpt = rcpt, + title = "minimum message" +}) + +write("testing host not found: ") +local c, e = connect("wrong.host", 25) +local err = SMTP.mail{ + from = from, + rcpt = rcpt, + server = "wrong.host" +} +if e ~= err then fail("wrong error message") +else print("ok") end + +write("testing invalid from: ") +local err = SMTP.mail{ + from = ' " " (( _ * ', + rcpt = rcpt, +} +if not err then fail("wrong error message") +else print(err) end + +write("testing no rcpt: ") +local err = SMTP.mail{ + from = from, +} +if not err then fail("wrong error message") +else print(err) end + +write("clearing mailbox: ") +empty() +print("ok") + +write("sending messages: ") +for i = 1, getn(sent) do + err = SMTP.mail(sent[i]) + if err then fail(err) end + write("+") + flush(_STDOUT) +end +print("ok") + +wait(mark(), total()) + +write("parsing mailbox: ") +local mbox = parse.mbox(get()) +print(getn(mbox) .. " messages found!") + +for i = 1, getn(mbox) do + check(sent, mbox[i]) +end + + +print("passed all tests") +print(format("done in %.2fs", _time() - t)) diff --git a/test/urltest.lua b/test/urltest.lua new file mode 100644 index 0000000..20a4f7d --- /dev/null +++ b/test/urltest.lua @@ -0,0 +1,388 @@ +function mysetglobal (varname, oldvalue, newvalue) + print("changing " .. varname) + %rawset(%globals(), varname, newvalue) +end +function mygetglobal (varname, newvalue) + print("checking " .. varname) + return %rawget(%globals(), varname) +end +settagmethod(tag(nil), "setglobal", mysetglobal) +settagmethod(tag(nil), "getglobal", mygetglobal) + +assert(dofile "../lua/code.lua") +assert(dofile "../lua/url.lua") + +local check_protect = function(parsed, path) + local built = URL.build_path(parsed) + if built ~= path then + print(built, path) + print("path composition failed.") + exit() + end +end + +local check_invert = function(url) + local parsed = URL.parse_url(url) + parsed.path = URL.build_path(URL.parse_path(parsed.path)) + local rebuilt = URL.build_url(parsed) + if rebuilt ~= url then + print(url, rebuilt) + print("original and rebuilt are different") + exit() + end +end + +local check_parse_path = function(path, expect) + local parsed = URL.parse_path(path) + for i = 1, max(getn(parsed), getn(expect)) do + if parsed[i] ~= expect[i] then + print(path) + write("segment: ", i, " = '", Code.hexa(tostring(parsed[i])), + "' but expected '", Code.hexa(tostring(expect[i])), "'\n") + exit() + end + end + if expect.is_directory ~= parsed.is_directory then + print(path) + print("is_directory mismatch") + exit() + end + if expect.is_absolute ~= parsed.is_absolute then + print(path) + print("is_absolute mismatch") + exit() + end + local built = URL.build_path(expect) + if built ~= path then + print(built, path) + print("path composition failed.") + exit() + end +end + +local check_absolute_url = function(base, relative, absolute) + local res = URL.absolute_url(base, relative) + if res ~= absolute then + write("absolute: In test for '", relative, "' expected '", + absolute, "' but got '", res, "'\n") + exit() + end +end + +local check_parse_url = function(gaba) + local url = gaba.url + gaba.url = nil + local parsed = URL.parse_url(url) + for i, v in gaba do + if v ~= parsed[i] then + write("parse: In test for '", url, "' expected ", i, " = '", + v, "' but got '", tostring(parsed[i]), "'\n") + for i,v in parsed do print(i,v) end + exit() + end + end + for i, v in parsed do + if v ~= gaba[i] then + write("parse: In test for '", url, "' expected ", i, " = '", + tostring(gaba[i]), "' but got '", v, "'\n") + for i,v in parsed do print(i,v) end + exit() + end + end +end + +print("testing URL parsing") +check_parse_url{ + url = "scheme://userinfo@host:port/path;params?query#fragment", + scheme = "scheme", + authority = "userinfo@host:port", + host = "host", + port = "port", + userinfo = "userinfo", + user = "userinfo", + path = "/path", + params = "params", + query = "query", + fragment = "fragment" +} + +check_parse_url{ + url = "scheme://user:password@host:port/path;params?query#fragment", + scheme = "scheme", + authority = "user:password@host:port", + host = "host", + port = "port", + userinfo = "user:password", + user = "user", + password = "password", + path = "/path", + params = "params", + query = "query", + fragment = "fragment", +} + +check_parse_url{ + url = "scheme://userinfo@host:port/path;params?query#", + scheme = "scheme", + authority = "userinfo@host:port", + host = "host", + port = "port", + userinfo = "userinfo", + user = "userinfo", + path = "/path", + params = "params", + query = "query", + fragment = "" +} + +check_parse_url{ + url = "scheme://userinfo@host:port/path;params?#fragment", + scheme = "scheme", + authority = "userinfo@host:port", + host = "host", + port = "port", + userinfo = "userinfo", + user = "userinfo", + path = "/path", + params = "params", + query = "", + fragment = "fragment" +} + +check_parse_url{ + url = "scheme://userinfo@host:port/path;params#fragment", + scheme = "scheme", + authority = "userinfo@host:port", + host = "host", + port = "port", + userinfo = "userinfo", + user = "userinfo", + path = "/path", + params = "params", + fragment = "fragment" +} + +check_parse_url{ + url = "scheme://userinfo@host:port/path;?query#fragment", + scheme = "scheme", + authority = "userinfo@host:port", + host = "host", + port = "port", + userinfo = "userinfo", + user = "userinfo", + path = "/path", + params = "", + query = "query", + fragment = "fragment" +} + +check_parse_url{ + url = "scheme://userinfo@host:port/path?query#fragment", + scheme = "scheme", + authority = "userinfo@host:port", + host = "host", + port = "port", + userinfo = "userinfo", + user = "userinfo", + path = "/path", + query = "query", + fragment = "fragment" +} + +check_parse_url{ + url = "scheme://userinfo@host:port/;params?query#fragment", + scheme = "scheme", + authority = "userinfo@host:port", + host = "host", + port = "port", + userinfo = "userinfo", + user = "userinfo", + path = "/", + params = "params", + query = "query", + fragment = "fragment" +} + +check_parse_url{ + url = "scheme://userinfo@host:port", + scheme = "scheme", + authority = "userinfo@host:port", + host = "host", + port = "port", + userinfo = "userinfo", + user = "userinfo", +} + +check_parse_url{ + url = "//userinfo@host:port/path;params?query#fragment", + authority = "userinfo@host:port", + host = "host", + port = "port", + userinfo = "userinfo", + user = "userinfo", + path = "/path", + params = "params", + query = "query", + fragment = "fragment" +} + +check_parse_url{ + url = "//userinfo@host:port/path", + authority = "userinfo@host:port", + host = "host", + port = "port", + userinfo = "userinfo", + user = "userinfo", + path = "/path", +} + +check_parse_url{ + url = "//userinfo@host/path", + authority = "userinfo@host", + host = "host", + userinfo = "userinfo", + user = "userinfo", + path = "/path", +} + +check_parse_url{ + url = "//user:password@host/path", + authority = "user:password@host", + host = "host", + userinfo = "user:password", + password = "password", + user = "user", + path = "/path", +} + +check_parse_url{ + url = "//user:@host/path", + authority = "user:@host", + host = "host", + userinfo = "user:", + password = "", + user = "user", + path = "/path", +} + +check_parse_url{ + url = "//user@host:port/path", + authority = "user@host:port", + host = "host", + userinfo = "user", + user = "user", + port = "port", + path = "/path", +} + +check_parse_url{ + url = "//host:port/path", + authority = "host:port", + port = "port", + host = "host", + path = "/path", +} + +check_parse_url{ + url = "//host/path", + authority = "host", + host = "host", + path = "/path", +} + +check_parse_url{ + url = "//host", + authority = "host", + host = "host", +} + +check_parse_url{ + url = "/path", + path = "/path", +} + +check_parse_url{ + url = "path", + path = "path", +} + +-- standard RFC tests +print("testing absolute resolution") +check_absolute_url("http://a/b/c/d;p?q#f", "g:h", "g:h") +check_absolute_url("http://a/b/c/d;p?q#f", "g", "http://a/b/c/g") +check_absolute_url("http://a/b/c/d;p?q#f", "./g", "http://a/b/c/g") +check_absolute_url("http://a/b/c/d;p?q#f", "g/", "http://a/b/c/g/") +check_absolute_url("http://a/b/c/d;p?q#f", "/g", "http://a/g") +check_absolute_url("http://a/b/c/d;p?q#f", "//g", "http://g") +check_absolute_url("http://a/b/c/d;p?q#f", "?y", "http://a/b/c/d;p?y") +check_absolute_url("http://a/b/c/d;p?q#f", "g?y", "http://a/b/c/g?y") +check_absolute_url("http://a/b/c/d;p?q#f", "g?y/./x", "http://a/b/c/g?y/./x") +check_absolute_url("http://a/b/c/d;p?q#f", "#s", "http://a/b/c/d;p?q#s") +check_absolute_url("http://a/b/c/d;p?q#f", "g#s", "http://a/b/c/g#s") +check_absolute_url("http://a/b/c/d;p?q#f", "g#s/./x", "http://a/b/c/g#s/./x") +check_absolute_url("http://a/b/c/d;p?q#f", "g?y#s", "http://a/b/c/g?y#s") +check_absolute_url("http://a/b/c/d;p?q#f", ";x", "http://a/b/c/d;x") +check_absolute_url("http://a/b/c/d;p?q#f", "g;x", "http://a/b/c/g;x") +check_absolute_url("http://a/b/c/d;p?q#f", "g;x?y#s", "http://a/b/c/g;x?y#s") +check_absolute_url("http://a/b/c/d;p?q#f", ".", "http://a/b/c/") +check_absolute_url("http://a/b/c/d;p?q#f", "./", "http://a/b/c/") +check_absolute_url("http://a/b/c/d;p?q#f", "..", "http://a/b/") +check_absolute_url("http://a/b/c/d;p?q#f", "../", "http://a/b/") +check_absolute_url("http://a/b/c/d;p?q#f", "../g", "http://a/b/g") +check_absolute_url("http://a/b/c/d;p?q#f", "../..", "http://a/") +check_absolute_url("http://a/b/c/d;p?q#f", "../../", "http://a/") +check_absolute_url("http://a/b/c/d;p?q#f", "../../g", "http://a/g") +check_absolute_url("http://a/b/c/d;p?q#f", "", "http://a/b/c/d;p?q#f") +check_absolute_url("http://a/b/c/d;p?q#f", "/./g", "http://a/./g") +check_absolute_url("http://a/b/c/d;p?q#f", "/../g", "http://a/../g") +check_absolute_url("http://a/b/c/d;p?q#f", "g.", "http://a/b/c/g.") +check_absolute_url("http://a/b/c/d;p?q#f", ".g", "http://a/b/c/.g") +check_absolute_url("http://a/b/c/d;p?q#f", "g..", "http://a/b/c/g..") +check_absolute_url("http://a/b/c/d;p?q#f", "..g", "http://a/b/c/..g") +check_absolute_url("http://a/b/c/d;p?q#f", "./../g", "http://a/b/g") +check_absolute_url("http://a/b/c/d;p?q#f", "./g/.", "http://a/b/c/g/") +check_absolute_url("http://a/b/c/d;p?q#f", "g/./h", "http://a/b/c/g/h") +check_absolute_url("http://a/b/c/d;p?q#f", "g/../h", "http://a/b/c/h") + +-- extra tests +check_absolute_url("//a/b/c/d;p?q#f", "d/e/f", "//a/b/c/d/e/f") +check_absolute_url("/a/b/c/d;p?q#f", "d/e/f", "/a/b/c/d/e/f") +check_absolute_url("a/b/c/d", "d/e/f", "a/b/c/d/e/f") +check_absolute_url("a/b/c/d/../", "d/e/f", "a/b/c/d/e/f") +check_absolute_url("http://velox.telemar.com.br", "/dashboard/index.html", + "http://velox.telemar.com.br/dashboard/index.html") + +print("testing path parsing and composition") +check_parse_path("/eu/tu/ele", { "eu", "tu", "ele"; is_absolute = 1 }) +check_parse_path("/eu/", { "eu"; is_absolute = 1, is_directory = 1 }) +check_parse_path("eu/tu/ele/nos/vos/eles/", + { "eu", "tu", "ele", "nos", "vos", "eles"; is_directory = 1}) +check_parse_path("/", { is_absolute = 1, is_directory = 1}) +check_parse_path("", { }) +check_parse_path("eu%01/%02tu/e%03l%04e/nos/vos%05/e%12les/", + { "eu\1", "\2tu", "e\3l\4e", "nos", "vos\5", "e\18les"; is_directory = 1}) +check_parse_path("eu/tu", { "eu", "tu" }) + +print("testing path protection") +check_protect({ "eu", "-_.!~*'():@&=+$,", "tu" }, "eu/-_.!~*'():@&=+$,/tu") +check_protect({ "eu ", "~diego" }, "eu%20/~diego") +check_protect({ "/eu>", " Date: Wed, 26 Sep 2001 20:29:01 +0000 Subject: [PATCH 075/483] added wrong scheme test --- test/ftptest.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/ftptest.lua b/test/ftptest.lua index 7b0d3ab..7fc1917 100644 --- a/test/ftptest.lua +++ b/test/ftptest.lua @@ -90,6 +90,10 @@ back, err = FTP.get { } check(not err and back == index, err) +write("testing wrong scheme: ") +back, err = FTP.get("wrong://banana.com/lixo") +check(not back and err == "unknown scheme 'wrong'", err) + write("testing invalid url: ") back, err = FTP.get("localhost/dir1/index.html;type=i") local c, e = connect("", 21) From 4f27c376e9e9aac413ad7b30b683ce08ba16fd1e Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 26 Sep 2001 20:29:18 +0000 Subject: [PATCH 076/483] added wrong scheme test --- test/httptest.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/httptest.lua b/test/httptest.lua index 0b61729..86cb8af 100644 --- a/test/httptest.lua +++ b/test/httptest.lua @@ -278,6 +278,18 @@ ignore = { } check_request(request, expect, ignore) +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 write("testing simple get function: ") body = HTTP.get("http://" .. host .. prefix .. "/index.html") From 6eb7f22c4b23a1b8175222001435fa45d7b570b3 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 26 Sep 2001 20:29:32 +0000 Subject: [PATCH 077/483] added unsafe path composition test added new build_url tests --- test/urltest.lua | 152 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 2 deletions(-) diff --git a/test/urltest.lua b/test/urltest.lua index 20a4f7d..b078d03 100644 --- a/test/urltest.lua +++ b/test/urltest.lua @@ -12,8 +12,18 @@ settagmethod(tag(nil), "getglobal", mygetglobal) assert(dofile "../lua/code.lua") assert(dofile "../lua/url.lua") -local check_protect = function(parsed, path) - local built = URL.build_path(parsed) +local check_build_url = function(parsed) + local built = URL.build_url(parsed) + if built ~= parsed.url then + print("built is different from expected") + print(built) + print(expected) + exit() + end +end + +local check_protect = function(parsed, path, unsafe) + local built = URL.build_path(parsed, unsafe) if built ~= path then print(built, path) print("path composition failed.") @@ -306,6 +316,140 @@ check_parse_url{ path = "path", } +print("testing URL building") +check_build_url { + url = "scheme://user:password@host:port/path;params?query#fragment", + scheme = "scheme", + host = "host", + port = "port", + user = "user", + password = "password", + path = "/path", + params = "params", + query = "query", + fragment = "fragment" +} + +check_build_url { + url = "scheme://user:password@host/path;params?query#fragment", + scheme = "scheme", + host = "host", + user = "user", + password = "password", + path = "/path", + params = "params", + query = "query", + fragment = "fragment" +} + +check_build_url { + url = "scheme://user@host/path;params?query#fragment", + scheme = "scheme", + host = "host", + user = "user", + path = "/path", + params = "params", + query = "query", + fragment = "fragment" +} + +check_build_url { + url = "scheme://host/path;params?query#fragment", + scheme = "scheme", + host = "host", + path = "/path", + params = "params", + query = "query", + fragment = "fragment" +} + +check_build_url { + url = "scheme://host/path;params#fragment", + scheme = "scheme", + host = "host", + path = "/path", + params = "params", + fragment = "fragment" +} + +check_build_url { + url = "scheme://host/path#fragment", + scheme = "scheme", + host = "host", + path = "/path", + fragment = "fragment" +} + +check_build_url { + url = "scheme://host/path", + scheme = "scheme", + host = "host", + path = "/path", +} + +check_build_url { + url = "//host/path", + host = "host", + path = "/path", +} + +check_build_url { + url = "/path", + path = "/path", +} + +check_build_url { + url = "scheme://user:password@host:port/path;params?query#fragment", + scheme = "scheme", + host = "host", + port = "port", + user = "user", + userinfo = "not used", + password = "password", + path = "/path", + params = "params", + query = "query", + fragment = "fragment" +} + +check_build_url { + url = "scheme://user:password@host:port/path;params?query#fragment", + scheme = "scheme", + host = "host", + port = "port", + user = "user", + userinfo = "not used", + authority = "not used", + password = "password", + path = "/path", + params = "params", + query = "query", + fragment = "fragment" +} + +check_build_url { + url = "scheme://user:password@host:port/path;params?query#fragment", + scheme = "scheme", + host = "host", + port = "port", + userinfo = "user:password", + authority = "not used", + path = "/path", + params = "params", + query = "query", + fragment = "fragment" +} + +check_build_url { + url = "scheme://user:password@host:port/path;params?query#fragment", + scheme = "scheme", + authority = "user:password@host:port", + path = "/path", + params = "params", + query = "query", + fragment = "fragment" +} + -- standard RFC tests print("testing absolute resolution") check_absolute_url("http://a/b/c/d;p?q#f", "g:h", "g:h") @@ -369,6 +513,10 @@ check_protect({ "eu ", "~diego" }, "eu%20/~diego") check_protect({ "/eu>", "", "/ Date: Wed, 26 Sep 2001 20:31:59 +0000 Subject: [PATCH 078/483] Almost ready for Lua 4.1 --- src/luasocket.c | 184 +++++++++++++++++++++++------------------------- 1 file changed, 87 insertions(+), 97 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index bbf7ca7..5168fbd 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -291,13 +291,7 @@ static int global_tcpconnect(lua_State *L) const char *address = luaL_check_string(L, 1); unsigned short port = (unsigned short) luaL_check_number(L, 2); p_sock sock = push_clienttable(L, tags); - const char *err; - if (!sock) { - lua_pushnil(L); - lua_pushstring(L, "out of memory"); - return 2; - } - err = tcp_tryconnect(sock, address, port); + const char *err = tcp_tryconnect(sock, address, port); if (err) { lua_pushnil(L); lua_pushstring(L, err); @@ -316,18 +310,18 @@ static int global_tcpconnect(lua_State *L) static int global_udpsocket(lua_State *L) { p_tags tags = pop_tags(L); - int top = lua_gettop(L); + int top = lua_gettop(L); p_sock sock = push_udptable(L, tags); if (!sock) return 2; - if (top >= 1 ) { - if (lua_istable(L, 1)) { - lua_pushnil(L); - while (lua_next(L, 1)) { - if (!set_option(L, sock)) lua_error(L, "error setting option"); - lua_pop(L, 1); - } - } else luaL_argerror(L, 1, "invalid options"); - } + if (top >= 1 ) { + if (lua_istable(L, 1)) { + lua_pushnil(L); + while (lua_next(L, 1)) { + if (!set_option(L, sock)) lua_error(L, "error setting option"); + lua_pop(L, 1); + } + } else luaL_argerror(L, 1, "invalid options"); + } return 1; } @@ -1072,63 +1066,63 @@ void set_reuseaddr(p_sock sock) \*-------------------------------------------------------------------------*/ static int set_option(lua_State *L, p_sock sock) { - static const char *const optionnames[] = { - "SO_KEEPALIVE", "SO_DONTROUTE", "SO_BROADCAST", "SO_LINGER", NULL - }; - const char *option; - int err; - if (!lua_isstring(L, -2)) return 0; - option = lua_tostring(L, -2); - switch (luaL_findstring(option, optionnames)) { - case 0: { - int bool; - if (!lua_isnumber(L, -1)) - lua_error(L, "invalid SO_KEEPALIVE value"); - bool = (int) lua_tonumber(L, -1); - err = setsockopt(sock->sock, SOL_SOCKET, SO_KEEPALIVE, + static const char *const optionnames[] = { + "SO_KEEPALIVE", "SO_DONTROUTE", "SO_BROADCAST", "SO_LINGER", NULL + }; + const char *option; + int err; + if (!lua_isstring(L, -2)) return 0; + option = lua_tostring(L, -2); + switch (luaL_findstring(option, optionnames)) { + case 0: { + int bool; + if (!lua_isnumber(L, -1)) + lua_error(L, "invalid SO_KEEPALIVE value"); + bool = (int) lua_tonumber(L, -1); + err = setsockopt(sock->sock, SOL_SOCKET, SO_KEEPALIVE, (char *) &bool, sizeof(bool)); - return err >= 0; - } - case 1: { - int bool; - if (!lua_isnumber(L, -1)) - lua_error(L, "invalid SO_DONTROUTE value"); - bool = (int) lua_tonumber(L, -1); - err = setsockopt(sock->sock, SOL_SOCKET, SO_DONTROUTE, + return err >= 0; + } + case 1: { + int bool; + if (!lua_isnumber(L, -1)) + lua_error(L, "invalid SO_DONTROUTE value"); + bool = (int) lua_tonumber(L, -1); + err = setsockopt(sock->sock, SOL_SOCKET, SO_DONTROUTE, (char *) &bool, sizeof(bool)); - return err >= 0; - } - case 2: { - int bool; - if (!lua_isnumber(L, -1)) - lua_error(L, "invalid SO_BROADCAST value"); - bool = (int) lua_tonumber(L, -1); - err = setsockopt(sock->sock, SOL_SOCKET, SO_BROADCAST, + return err >= 0; + } + case 2: { + int bool; + if (!lua_isnumber(L, -1)) + lua_error(L, "invalid SO_BROADCAST value"); + bool = (int) lua_tonumber(L, -1); + err = setsockopt(sock->sock, SOL_SOCKET, SO_BROADCAST, (char *) &bool, sizeof(bool)); - return err >= 0; - } - case 3: { - struct linger linger; - if (!lua_istable(L, -1)) - lua_error(L, "invalid SO_LINGER value"); - lua_pushstring(L, "l_onoff"); - lua_gettable(L, -2); - if (!lua_isnumber(L, -1)) - lua_error(L, "invalid SO_LINGER (l_onoff) value"); - linger.l_onoff = (int) lua_tonumber(L, -1); - lua_pop(L, 1); - lua_pushstring(L, "l_linger"); - lua_gettable(L, -2); - if (!lua_isnumber(L, -1)) - lua_error(L, "invalid SO_LINGER (l_linger) value"); - linger.l_linger = (int) lua_tonumber(L, -1); - lua_pop(L, 1); - err = setsockopt(sock->sock, SOL_SOCKET, SO_LINGER, + return err >= 0; + } + case 3: { + struct linger linger; + if (!lua_istable(L, -1)) + lua_error(L, "invalid SO_LINGER value"); + lua_pushstring(L, "l_onoff"); + lua_gettable(L, -2); + if (!lua_isnumber(L, -1)) + lua_error(L, "invalid SO_LINGER (l_onoff) value"); + linger.l_onoff = (int) lua_tonumber(L, -1); + lua_pop(L, 1); + lua_pushstring(L, "l_linger"); + lua_gettable(L, -2); + if (!lua_isnumber(L, -1)) + lua_error(L, "invalid SO_LINGER (l_linger) value"); + linger.l_linger = (int) lua_tonumber(L, -1); + lua_pop(L, 1); + err = setsockopt(sock->sock, SOL_SOCKET, SO_LINGER, (char *) &linger, sizeof(linger)); - return err >= 0; - } - default: return 0; - } + return err >= 0; + } + default: return 0; + } } @@ -1675,21 +1669,18 @@ LUASOCKET_API int lua_socketlibopen(lua_State *L) unsigned int i; /* declare new Lua tags for used userdata values */ p_tags tags = (p_tags) lua_newuserdata(L, sizeof(t_tags)); - if (!tags) lua_error(L, "out of memory"); - /* remove tags userdatum from stack but avoid garbage collection */ - lua_ref(L, 1); tags->client = lua_newtag(L); tags->server = lua_newtag(L); tags->table = lua_newtag(L); tags->udp = lua_newtag(L); /* global functions exported */ for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { - lua_pushuserdata(L, tags); + lua_pushvalue(L, -1); lua_pushcclosure(L, funcs[i].func, 1); lua_setglobal(L, funcs[i].name); } /* socket garbage collection */ - lua_pushuserdata(L, tags); + lua_pushvalue(L, -1); lua_pushcclosure(L, gc_table, 1); lua_settagmethod(L, tags->table, "gc"); #ifdef WIN32 @@ -1714,13 +1705,15 @@ LUASOCKET_API int lua_socketlibopen(lua_State *L) unsigned int i; for (i = 0; i < sizeof(global)/sizeof(char *); i++) { lua_pushstring(L, global[i]); - lua_pushuserdata(L, tags); + lua_pushvalue(L, -2); lua_pushcclosure(L, global_callfromtable, 2); lua_setglobal(L, global[i]); } } #endif - return 1; + /* remove tags userdatum from stack */ + lua_pop(L, 1); + return 1; } /*=========================================================================*\ @@ -1745,12 +1738,11 @@ static p_sock push_clienttable(lua_State *L, p_tags tags) {"timeout", table_timeout}, }; unsigned int i; - p_sock sock; + p_sock sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); + lua_settag(L, tags->client); lua_newtable(L); lua_settag(L, tags->table); lua_pushstring(L, P_SOCK); - sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); - if (!sock) return NULL; - lua_settag(L, tags->client); + lua_pushvalue(L, -3); lua_settable(L, -3); sock->sock = INVALID_SOCKET; sock->is_connected = 0; @@ -1759,7 +1751,7 @@ static p_sock push_clienttable(lua_State *L, p_tags tags) sock->bf_first = sock->bf_last = 0; for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { lua_pushstring(L, funcs[i].name); - lua_pushusertag(L, sock, tags->client); + lua_pushvalue(L, -3); lua_pushcclosure(L, funcs[i].func, 1); lua_settable(L, -3); } @@ -1782,12 +1774,11 @@ static p_sock push_servertable(lua_State *L, p_tags tags) {"timeout", table_timeout}, }; unsigned int i; - p_sock sock; + p_sock sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); + lua_settag(L, tags->server); lua_newtable(L); lua_settag(L, tags->table); lua_pushstring(L, P_SOCK); - sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); - if (!sock) return NULL; - lua_settag(L, tags->server); + lua_pushvalue(L, -3); lua_settable(L, -3); sock->sock = INVALID_SOCKET; sock->tm_block = -1; @@ -1795,14 +1786,18 @@ static p_sock push_servertable(lua_State *L, p_tags tags) sock->bf_first = sock->bf_last = 0; for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { lua_pushstring(L, funcs[i].name); - lua_pushusertag(L, sock, tags->client); + lua_pushvalue(L, -3); lua_pushcclosure(L, funcs[i].func, 1); lua_settable(L, -3); } /* the accept method is different, it needs the tags closure too */ lua_pushstring(L, "accept"); +#ifdef LUASOCKET_41FRIENDLY + lua_newuserdatabox(L, tags); +#else lua_pushuserdata(L, tags); - lua_pushusertag(L, sock, tags->client); +#endif + lua_pushvalue(L, -4); lua_pushcclosure(L, table_tcpaccept, 2); lua_settable(L, -3); return sock; @@ -1831,16 +1826,11 @@ static p_sock push_udptable(lua_State *L, p_tags tags) {"timeout", table_timeout}, }; unsigned int i; - p_sock sock; + p_sock sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); + lua_settag(L, tags->udp); lua_newtable(L); lua_settag(L, tags->table); lua_pushstring(L, P_SOCK); - sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); - if (!sock) { - lua_pushnil(L); - lua_pushstring(L, "out of memory"); - return NULL; - } - lua_settag(L, tags->udp); + lua_pushvalue(L, -3); lua_settable(L, -3); sock->sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock->sock == INVALID_SOCKET) { @@ -1854,7 +1844,7 @@ static p_sock push_udptable(lua_State *L, p_tags tags) sock->bf_first = sock->bf_last = 0; for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { lua_pushstring(L, funcs[i].name); - lua_pushusertag(L, sock, tags->udp); + lua_pushvalue(L, -3); lua_pushcclosure(L, funcs[i].func, 1); lua_settable(L, -3); } From ef5322a92af5c193f04b599c01f0566ddc5ed5a2 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 26 Sep 2001 20:38:47 +0000 Subject: [PATCH 079/483] Changed build_url to compose from smaller parts Changed build_path to add an option not to protect path components --- src/url.lua | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/src/url.lua b/src/url.lua index 8cafd9b..181a4bd 100644 --- a/src/url.lua +++ b/src/url.lua @@ -70,7 +70,20 @@ function Public.build_url(parsed) local url = parsed.path or "" if parsed.params then url = url .. ";" .. parsed.params end if parsed.query then url = url .. "?" .. parsed.query end - if parsed.authority then url = "//" .. parsed.authority .. url end + local authority = parsed.authority + if parsed.host then + authority = parsed.host + if parsed.port then authority = authority .. ":" .. parsed.port end + local userinfo = parsed.userinfo + if parsed.user then + userinfo = parsed.user + if parsed.password then + userinfo = userinfo .. ":" .. parsed.password + end + end + if userinfo then authority = userinfo .. "@" .. authority end + end + if authority then url = "//" .. authority .. url end if parsed.scheme then url = parsed.scheme .. ":" .. url end if parsed.fragment then url = url .. "#" .. parsed.fragment end return gsub(url, "%s", "") @@ -119,6 +132,7 @@ end ----------------------------------------------------------------------------- function Public.parse_path(path) local parsed = {} + path = path or "" path = gsub(path, "%s", "") gsub(path, "([^/]+)", function (s) tinsert(%parsed, s) end) for i = 1, getn(parsed) do @@ -133,19 +147,31 @@ end -- Builds a path component from its segments, escaping protected characters. -- Input -- parsed: path segments +-- unsafe: if true, segments are not protected before path is built -- Returns -- path: correspondin path string ----------------------------------------------------------------------------- -function Public.build_path(parsed) +function Public.build_path(parsed, unsafe) local path = "" local n = getn(parsed) - for i = 1, n-1 do - path = path .. %Private.protect_segment(parsed[i]) - path = path .. "/" - end - if n > 0 then - path = path .. %Private.protect_segment(parsed[n]) - if parsed.is_directory then path = path .. "/" end + if unsafe then + for i = 1, n-1 do + path = path .. parsed[i] + path = path .. "/" + end + if n > 0 then + path = path .. parsed[n] + if parsed.is_directory then path = path .. "/" end + end + else + for i = 1, n-1 do + path = path .. %Private.protect_segment(parsed[i]) + path = path .. "/" + end + if n > 0 then + path = path .. %Private.protect_segment(parsed[n]) + if parsed.is_directory then path = path .. "/" end + end end if parsed.is_absolute then path = "/" .. path end return path From 84e503afe3c6ea684663272df1a7de1da85a20b7 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 26 Sep 2001 20:39:46 +0000 Subject: [PATCH 080/483] Changed some variable names. Added correct scheme test. --- src/ftp.lua | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/ftp.lua b/src/ftp.lua index 98b08eb..1af4d30 100644 --- a/src/ftp.lua +++ b/src/ftp.lua @@ -273,11 +273,11 @@ end -- server: server socket bound to local address -- name: file name -- is_directory: is file a directory name? --- download_cb: callback to receive file contents +-- content_cb: callback to receive file contents -- Returns -- err: error message in case of error, nil otherwise ----------------------------------------------------------------------------- -function Private.retrieve(control, server, name, is_directory, download_cb) +function Private.retrieve(control, server, name, is_directory, content_cb) local code, answer local data -- ask server for file or directory listing accordingly @@ -295,7 +295,7 @@ function Private.retrieve(control, server, name, is_directory, download_cb) control:close() return answer end - answer = %Private.receive_indirect(data, download_cb) + answer = %Private.receive_indirect(data, content_cb) if answer then control:close() return answer @@ -380,10 +380,9 @@ end -- err: error message if any ----------------------------------------------------------------------------- function Private.change_type(control, params) - local type - if params == "type=i" then type = "i" - elseif params == "type=a" then type = "a" end - if type then + local type, _ + _, _, type = strfind(params or "", "type=(.)") + if type == "a" or type == "i" then local code, err = %Private.command(control, "type", type, {200}) if not code then return err end end @@ -443,25 +442,25 @@ end -- Input -- control: control connection with server -- request: a table with the fields: --- upload_cb: send callback to send file contents +-- content_cb: send callback to send file contents -- segment: parsed URL path segments -- Returns -- err: error message if any ----------------------------------------------------------------------------- function Private.upload(control, request, segment) - local code, name, upload_cb + local code, name, content_cb -- get remote file name name = segment[getn(segment)] if not name then control:close() return "Invalid file path" end - upload_cb = request.upload_cb + content_cb = request.content_cb -- setup passive connection local server, answer = %Private.port(control) if not server then return answer end -- ask server to receive file - code, answer = %Private.store(control, server, name, upload_cb) + code, answer = %Private.store(control, server, name, content_cb) if not code then return answer end end @@ -470,15 +469,15 @@ end -- Input -- control: control connection with server -- request: a table with the fields: --- download_cb: receive callback to receive file contents +-- content_cb: receive callback to receive file contents -- segment: parsed URL path segments -- Returns -- err: error message if any ----------------------------------------------------------------------------- function Private.download(control, request, segment) - local code, name, is_directory, download_cb + local code, name, is_directory, content_cb is_directory = segment.is_directory - download_cb = request.download_cb + content_cb = request.content_cb -- get remote file name name = segment[getn(segment)] if not name and not is_directory then @@ -490,7 +489,7 @@ function Private.download(control, request, segment) if not server then return answer end -- ask server to send file or directory listing code, answer = %Private.retrieve(control, server, name, - is_directory, download_cb) + is_directory, content_cb) if not code then return answer end end @@ -511,7 +510,8 @@ function Private.parse_url(request) user = "anonymous", port = 21, path = "/", - password = %Public.EMAIL + password = %Public.EMAIL, + scheme = "ftp" }) -- explicit login information overrides that given by URL parsed.user = request.user or parsed.user @@ -560,12 +560,15 @@ end -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory -- user: account user name -- password: account password --- download_cb: receive callback to receive file contents +-- content_cb: receive callback to receive file contents -- Returns -- err: error message if any ----------------------------------------------------------------------------- function Public.get_cb(request) local parsed = %Private.parse_url(request) + if parsed.scheme ~= "ftp" then + return format("unknown scheme '%s'", parsed.scheme) + end local control, err = %Private.open(parsed) if not control then return err end local segment = %Private.parse_path(parsed) @@ -583,12 +586,15 @@ end -- type: "i" for "image" mode, "a" for "ascii" mode or "d" for directory -- user: account user name -- password: account password --- upload_cb: send callback to send file contents +-- content_cb: send callback to send file contents -- Returns -- err: error message if any ----------------------------------------------------------------------------- function Public.put_cb(request) local parsed = %Private.parse_url(request) + if parsed.scheme ~= "ftp" then + return format("unknown scheme '%s'", parsed.scheme) + end local control, err = %Private.open(parsed) if not control then return err end local segment = %Private.parse_path(parsed) @@ -612,7 +618,7 @@ end ----------------------------------------------------------------------------- function Public.put(url_or_request, content) local request = %Private.build_request(url_or_request) - request.upload_cb = function() + request.content_cb = function() return %content, strlen(%content) end return %Public.put_cb(request) @@ -633,7 +639,7 @@ end function Public.get(url_or_request) local cat = Concat.create() local request = %Private.build_request(url_or_request) - request.download_cb = function(chunk, err) + request.content_cb = function(chunk, err) if chunk then %cat:addstring(chunk) end return 1 end From 164d33894e997b179dae93b7883b1f04f937ab8e Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 26 Sep 2001 20:40:13 +0000 Subject: [PATCH 081/483] Removed wrong host header in redirect. Passing location header back to callee after redirect. Added correct scheme test. --- src/http.lua | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/http.lua b/src/http.lua index 2e8c8e9..e4c756f 100644 --- a/src/http.lua +++ b/src/http.lua @@ -396,11 +396,11 @@ function Private.fill_headers(headers, parsed) headers = headers or {} -- set default headers lower["user-agent"] = %Public.USERAGENT - lower["host"] = parsed.host -- override with user values for i,v in headers do lower[strlower(i)] = v end + lower["host"] = parsed.host -- this cannot be overriden lower["connection"] = "close" return lower @@ -482,7 +482,10 @@ function Private.redirect(request, response) body_cb = request.body_cb, headers = request.headers } - return %Public.request_cb(redirect, response) + local response = %Public.request_cb(redirect, response) + -- we pass the location header as a clue we tried to redirect + if response.headers then response.headers.location = redirect.url end + return response end ----------------------------------------------------------------------------- @@ -542,8 +545,13 @@ function Public.request_cb(request, response) local parsed = URL.parse_url(request.url, { host = "", port = %Public.PORT, - path ="/" + path ="/", + scheme = "http" }) + if parsed.scheme ~= "http" then + response.error = format("unknown scheme '%s'", parsed.scheme) + return response + end -- explicit authentication info overrides that given by the URL parsed.user = request.user or parsed.user parsed.password = request.password or parsed.password From 74979295a263b43a943630d0e0985b535a84a95b Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 26 Sep 2001 20:52:23 +0000 Subject: [PATCH 082/483] updated for luasocket 1.4 --- makefile.dist | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/makefile.dist b/makefile.dist index c409acc..2154eb9 100644 --- a/makefile.dist +++ b/makefile.dist @@ -2,7 +2,7 @@ # Distribution makefile #-------------------------------------------------------------------------- -DIST = luasocket-1.3b +DIST = luasocket-1.4 SRC = ~diego/tec/luasocket @@ -19,13 +19,14 @@ dist: cp -vf lua/README $(DIST)/lua cp -vf examples/*.lua $(DIST)/examples cp -vf examples/README $(DIST)/examples - cp -vf html/manual.html $(DIST)/html - cp -vf html/lua.png $(DIST)/html - cp -vf html/vim.png $(DIST)/html - cp -vf html/anybrowser.png $(DIST)/html + cp -vf html/*.html $(DIST)/html + cp -vf html/*.png $(DIST)/html cp -vf test/testclnt.lua $(DIST)/test cp -vf test/testsrvr.lua $(DIST)/test cp -vf test/testcmd.lua $(DIST)/test + cp -vf test/codetest.lua $(DIST)/test + cp -vf test/concattest.lua $(DIST)/test + cp -vf test/urltest.lua $(DIST)/test cp -vf test/README $(DIST)/test tar -zcvf $(DIST).tar.gz $(DIST) zip -r $(DIST).zip $(DIST) From 2f0754972219496fb0216c47522490cb4e4133ad Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 27 Sep 2001 20:02:24 +0000 Subject: [PATCH 083/483] Binds to all interfaces. --- samples/listener.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/listener.lua b/samples/listener.lua index f9ee3bf..8e2c7ce 100644 --- a/samples/listener.lua +++ b/samples/listener.lua @@ -1,4 +1,4 @@ -host = host or "localhost" +host = host or "*" port = port or 8080 if arg then host = arg[1] or host From e5e1f3dc7a750595bb3fbe219c369c3950220986 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 27 Sep 2001 20:02:34 +0000 Subject: [PATCH 084/483] Updated for LuaSocket 1.4 Prints a bunch of information during download. --- etc/get.lua | 169 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 108 insertions(+), 61 deletions(-) diff --git a/etc/get.lua b/etc/get.lua index 4a17cfc..13f983b 100644 --- a/etc/get.lua +++ b/etc/get.lua @@ -1,33 +1,73 @@ -assert(dofile("../lua/buffer.lua")) +-- this examples needs it all +assert(dofile("../lua/code.lua")) assert(dofile("../lua/ftp.lua")) -assert(dofile("../lua/base64.lua")) +assert(dofile("../lua/concat.lua")) +assert(dofile("../lua/url.lua")) assert(dofile("../lua/http.lua")) --- format a number of bytes per second into a human readable form -function strbps(b) - local l = "B/s" - if b > 1024 then - b = b / 1024 - l = "KB/s" - if b > 1024 then - b = b / 1024 - l = "MB/s" - if b > 1024 then - b = b / 1024 - l = "GB/s" -- hmmm +-- formats a number of seconds into human readable form +function nicetime(s) + local l = "s" + if s > 60 then + s = s / 60 + l = "m" + if s > 60 then + s = s / 60 + l = "h" + if s > 24 then + s = s / 24 + l = "d" -- hmmm end end end - return format("%.2f%s ", b, l) + if l == "s" then return format("%2.0f%s", s, l) + else return format("%5.2f%s", s, l) end +end + +-- formats a number of bytes into human readable form +function nicesize(b) + local l = "B" + if b > 1024 then + b = b / 1024 + l = "KB" + if b > 1024 then + b = b / 1024 + l = "MB" + if b > 1024 then + b = b / 1024 + l = "GB" -- hmmm + end + end + end + return format("%7.2f%2s", b, l) +end + +-- returns a string with the current state of the download +function gauge(got, dt, size) + local rate = got / dt + if size and size >= 1 then + return format("%s received, %s/s throughput, " .. + "%.0f%% done, %s remaining", + nicesize(got), + nicesize(rate), + 100*got/size, + nicetime((size-got)/rate)) + else + return format("%s received, %s/s throughput, %s elapsed", + nicesize(got), + nicesize(rate), + nicetime(dt)) + end end -- creates a new instance of a receive_cb that saves to disk -- kind of copied from luasocket's manual callback examples -function receive2disk(file) +function receive2disk(file, size) local aux = { start = _time(), got = 0, - file = openfile(file, "wb") + file = openfile(file, "wb"), + size = size } local receive_cb = function(chunk, err) local dt = _time() - %aux.start -- elapsed time since start @@ -39,62 +79,69 @@ function receive2disk(file) write(%aux.file, chunk) %aux.got = %aux.got + strlen(chunk) -- total bytes received if dt < 0.1 then return 1 end -- not enough time for estimate - local rate = %aux.got / dt -- get download rate - write("\r" .. strbps(rate)) -- print estimate + write("\r", gauge(%aux.got, dt, %aux.size)) return 1 end return receive_cb end --- stolen from http implementation -function split_url(url, default) - -- initialize default parameters - local parsed = default or {} - -- get scheme - url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end) - -- get user name and password. both can be empty! - -- moreover, password can be ommited - url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p) - %parsed.user = u - -- there can be an empty password, but the ':' has to be there - -- or else there is no password - %parsed.pass = nil -- kill default password - if c == ":" then %parsed.pass = p end - end) - -- get host - url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end) - -- get port if any - url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end) - -- whatever is left is the path - if url ~= "" then parsed.path = url end - return parsed -end - --- stolen from http implementation -function get_statuscode(line) - local _,_, code = strfind(line, " (%d%d%d) ") - return tonumber(code) -end - +-- downloads a file using the ftp protocol function getbyftp(url, file) - local err = ftp_getindirect(url, receive2disk(file), "b") - if err then print(err) else print("done.") end + local err = FTP.get_cb { + url = url, + content_cb = receive2disk(file), + type = "i" + } + print() + if err then print(err) end end -function getbyhttp(url, file) - local hdrs, line, err = http_getindirect(url, receive2disk(file)) - if line and get_statuscode(line) == 200 then print("done.") - elseif line then print(line) else print(err) end +-- downloads a file using the http protocol +function getbyhttp(url, file, size) + local response = HTTP.request_cb( + {url = url}, + {body_cb = receive2disk(file, size)} + ) + print() + if response.code ~= 200 then print(response.status or response.error) end end -function get(url, file) - local parsed = split_url(url) - if parsed.scheme == "ftp" then getbyftp(url, file) - else getbyhttp(url, file) end +-- determines the size of a http file +function gethttpsize(url) + local response = HTTP.request { + method = "HEAD", + url = url + } + if response.code == 200 then + return tonumber(response.headers["content-length"]) + end end +-- determines the scheme and the file name of a given url +function getschemeandname(url, name) + -- this is an heuristic to solve a common invalid url poblem + if not strfind(url, "//") then url = "//" .. url end + local parsed = URL.parse_url(url, {scheme = "http"}) + if name then return parsed.scheme, name end + local segment = URL.parse_path(parsed.path) + name = segment[getn(segment)] + if segment.is_directory then name = nil end + return parsed.scheme, name +end + +-- gets a file either by http or url, saving as name +function get(url, name) + local scheme + scheme, name = getschemeandname(url, name) + if not name then print("unknown file name") + elseif scheme == "ftp" then getbyftp(url, name) + elseif scheme == "http" then getbyhttp(url, name, gethttpsize(url)) + else print("unknown scheme" .. scheme) end +end + +-- main program arg = arg or {} -if getn(arg) < 2 then - write("Usage:\n luasocket -f get.lua \n") +if getn(arg) < 1 then + write("Usage:\n luasocket -f get.lua []\n") exit(1) else get(arg[1], arg[2]) end From ad997de5569c28a0877544d9b8dc8da66dd61c37 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 27 Sep 2001 20:02:58 +0000 Subject: [PATCH 085/483] Initial revision --- etc/check-links.lua | 99 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 etc/check-links.lua diff --git a/etc/check-links.lua b/etc/check-links.lua new file mode 100644 index 0000000..5d33732 --- /dev/null +++ b/etc/check-links.lua @@ -0,0 +1,99 @@ +dofile("../lua/http.lua") +HTTP.TIMEOUT = 10 +dofile("../lua/code.lua") +dofile("../lua/url.lua") +dofile("../lua/concat.lua") + +cache = {} + +function readfile(path) + path = Code.unescape(path) + local file, error = openfile(path, "r") + if file then + local body = read(file, "*a") + closefile(file) + return body + else return nil, error end +end + +function getstatus(url) + local parsed = URL.parse_url(url, { scheme = "file" }) + if cache[url] then return cache[url].res end + local res + if parsed.scheme == "http" then + local request = { url = url } + local response = { body_cb = function(chunk, err) + return nil + end } + local blocksize = HTTP.BLOCKSIZE + HTTP.BLOCKSIZE = 1 + response = HTTP.request_cb(request, response) + HTTP.BLOCKSIZE = blocksize + if response.code == 200 then res = nil + else res = response.status or response.error end + elseif parsed.scheme == "file" then + local file, error = openfile(Code.unescape(parsed.path), "r") + if file then + closefile(file) + res = nil + else res = error end + else res = format("unhandled scheme '%s'", parsed.scheme) end + cache[url] = { res = res } + return res +end + +function retrieve(url) + local parsed = URL.parse_url(url, { scheme = "file" }) + local base, body, error + base = url + if parsed.scheme == "http" then + local response = HTTP.request{url = url} + if response.code ~= 200 then + error = response.status or response.error + else + base = response.headers.location or url + body = response.body + end + elseif parsed.scheme == "file" then + body, error = readfile(parsed.path) + else error = format("unhandled scheme '%s'", parsed.scheme) end + return base, body, error +end + +function getlinks(body, base) + -- get rid of comments + body = gsub(body, "%<%!%-%-.-%-%-%>", "") + local links = {} + -- extract links + gsub(body, '[Hh][Rr][Ee][Ff]%s*=%s*"([^"]*)"', function(href) + tinsert(%links, URL.absolute_url(%base, href)) + end) + gsub(body, "[Hh][Rr][Ee][Ff]%s*=%s*'([^']*)'", function(href) + tinsert(%links, URL.absolute_url(%base, href)) + end) + gsub(body, "[Hh][Rr][Ee][Ff]%s*=%s*(%a+)", function(href) + tinsert(%links, URL.absolute_url(%base, href)) + end) + return links +end + +function checklinks(url) + local base, body, error = retrieve(url) + if not body then print(error) return end + local links = getlinks(body, base) + for i = 1, getn(links) do + write(_STDERR, "\t", links[i], "\n") + local err = getstatus(links[i]) + if err then write(links[i], ": ", err, "\n") end + end +end + +arg = arg or {} +if getn(arg) < 1 then + write("Usage:\n luasocket -f check-links.lua {}\n") + exit() +end +for i = 1, getn(arg) do + write("Checking ", arg[i], "\n") + checklinks(URL.absolute_url("file:", arg[i])) +end From 480e66d539c48a7bd49713790ed4602824a4f693 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 27 Sep 2001 20:04:21 +0000 Subject: [PATCH 086/483] Indent broken links. --- etc/check-links.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/check-links.lua b/etc/check-links.lua index 5d33732..24747a9 100644 --- a/etc/check-links.lua +++ b/etc/check-links.lua @@ -84,7 +84,7 @@ function checklinks(url) for i = 1, getn(links) do write(_STDERR, "\t", links[i], "\n") local err = getstatus(links[i]) - if err then write(links[i], ": ", err, "\n") end + if err then write('\t', links[i], ": ", err, "\n") end end end From 27e6140a90b63875becbc27f58d79b0433f3648d Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 27 Sep 2001 20:04:47 +0000 Subject: [PATCH 087/483] Updated for LuaSocket 1.4 --- samples/README | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/samples/README b/samples/README index 07bc7dc..68cb1eb 100644 --- a/samples/README +++ b/samples/README @@ -5,8 +5,11 @@ is not supported. talker.lua -- stdin to socket listener.lua and talker.lua are about the simplest applications you can -write using LuaSocket. Run 'luasocket listen.lua' and 'luasocket -talk.lua' on different terminals. Whatever you type on talk.lua will be +write using LuaSocket. Run + + 'luasocket listen.lua' and 'luasocket talk.lua' + +on different terminals. Whatever you type on talk.lua will be printed by listen.lua. dict.lua -- dict client @@ -44,11 +47,25 @@ to ports 8080 and 8081. get.lua -- file retriever -This module is a client that uses the FTP and HTTP code to implement a -command line file graber. Just run 'luasocket -f get.lua -' to download a remote file (either ftp:// or http://) to -the specified local file. The program also prints the download -throughput during download. +This little program is a client that uses the FTP and HTTP code to +implement a command line file graber. Just run + + luasocket -f get.lua [] + +to download a remote file (either ftp:// or http://) to the specified +local file. The program also prints the download throughput, elapsed +time, bytes already downloaded etc during download. + + check-links.lua -- HTML link checker program + +This little program scans a HTML file and checks for broken links. It is +similar to check-links.pl by Jamie Zawinski, but uses all facilities of +the LuaSocket library and the Lua language. It has not been thoroughly +tested, but it should work. Just run + + luasocket -f check-links.lua {} > output + +and open the result to see a list of broken links. Good luck, Diego. From 29ce2303c8dbbf06015a14dc3a9f8d7e0b814369 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 27 Sep 2001 20:05:30 +0000 Subject: [PATCH 088/483] return gsub... is not a nice ideia, since the thing returns several results. --- src/url.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/url.lua b/src/url.lua index 181a4bd..673c9ac 100644 --- a/src/url.lua +++ b/src/url.lua @@ -86,7 +86,8 @@ function Public.build_url(parsed) if authority then url = "//" .. authority .. url end if parsed.scheme then url = parsed.scheme .. ":" .. url end if parsed.fragment then url = url .. "#" .. parsed.fragment end - return gsub(url, "%s", "") + url = gsub(url, "%s", "") + return url end ----------------------------------------------------------------------------- From e2d21b237d0457fde60c1803ee4153ef7238e0eb Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 21 Mar 2002 13:52:38 +0000 Subject: [PATCH 089/483] Initial revision --- src/inet.c | 352 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 src/inet.c diff --git a/src/inet.c b/src/inet.c new file mode 100644 index 0000000..f512760 --- /dev/null +++ b/src/inet.c @@ -0,0 +1,352 @@ +/*=========================================================================*\ +* Internet socket methods implementation +* Global Lua fuctions: +* toip(hostname) +* tohostname(dotted-quad) +* Socket table methods: +* getpeername() +* getsockname() +\*=========================================================================*/ +#include + +#include +#include + +#include "lsinet.h" +#include "lssock.h" +#include "lscompat.h" + +/*=========================================================================*\ +* Internal function prototypes. +\*=========================================================================*/ +static int inet_lua_toip(lua_State *L); +static int inet_lua_tohostname(lua_State *L); +static int inet_lua_getpeername(lua_State *L); +static int inet_lua_getsockname(lua_State *L); +static void inet_pushresolved(lua_State *L, struct hostent *hp); + +#ifdef COMPAT_INETATON +static int inet_aton(cchar *cp, struct in_addr *inp); +#endif + +/*=========================================================================*\ +* Exported functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +void inet_open(lua_State *L) +{ + lua_pushcfunction(L, inet_lua_toip); + lua_setglobal(L, "toip"); + lua_pushcfunction(L, inet_lua_tohostname); + lua_setglobal(L, "tohostname"); + priv_newglobalmethod(L, "getsockname"); + priv_newglobalmethod(L, "getpeername"); +} + +/*-------------------------------------------------------------------------*\ +* Hook object methods to methods table. +\*-------------------------------------------------------------------------*/ +void inet_inherit(lua_State *L, cchar *lsclass) +{ + unsigned int i; + static struct luaL_reg funcs[] = { + {"getsockname", inet_lua_getsockname}, + {"getpeername", inet_lua_getpeername}, + }; + sock_inherit(L, lsclass); + for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { + lua_pushcfunction(L, funcs[i].func); + priv_setmethod(L, lsclass, funcs[i].name); + } +} + +void inet_construct(lua_State *L, p_inet inet) +{ + sock_construct(L, (p_sock) inet); +} + +/*=========================================================================*\ +* Global Lua functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Returns the list of ip addresses associated with a host name +* Lua Input: address +* address: ip address or hostname to dns lookup +* Lua Returns +* On success: first IP address followed by a resolved table +* On error: nil, followed by an error message +\*-------------------------------------------------------------------------*/ +static int inet_lua_toip(lua_State *L) +{ + cchar *address = luaL_check_string(L, 1); + struct in_addr addr; + struct hostent *hp; + if (inet_aton(address, &addr)) + hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET); + else hp = gethostbyname(address); + if (!hp) { + lua_pushnil(L); + lua_pushstring(L, compat_hoststrerror()); + return 2; + } + addr = *((struct in_addr *) hp->h_addr); + lua_pushstring(L, inet_ntoa(addr)); + inet_pushresolved(L, hp); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Returns the list of host names associated with an ip address +* Lua Input: address +* address: ip address or host name to reverse dns lookup +* Lua Returns +* On success: canonic name followed by a resolved table +* On error: nil, followed by an error message +\*-------------------------------------------------------------------------*/ +static int inet_lua_tohostname(lua_State *L) +{ + cchar *address = luaL_check_string(L, 1); + struct in_addr addr; + struct hostent *hp; + if (inet_aton(address, &addr)) + hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET); + else hp = gethostbyname(address); + if (!hp) { + lua_pushnil(L); + lua_pushstring(L, compat_hoststrerror()); + return 2; + } + lua_pushstring(L, hp->h_name); + inet_pushresolved(L, hp); + return 2; +} + +/*=========================================================================*\ +* Socket table methods +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Retrieves socket peer name +* Lua Input: sock +* sock: socket +* Lua Returns +* On success: ip address and port of peer +* On error: nil +\*-------------------------------------------------------------------------*/ +static int inet_lua_getpeername(lua_State *L) +{ + p_sock sock = (p_sock) lua_touserdata(L, 1); + struct sockaddr_in peer; + size_t peer_len = sizeof(peer); + if (getpeername(sock->fd, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + return 1; + } + lua_pushstring(L, inet_ntoa(peer.sin_addr)); + lua_pushnumber(L, ntohs(peer.sin_port)); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Retrieves socket local name +* Lua Input: sock +* sock: socket +* Lua Returns +* On success: local ip address and port +* On error: nil +\*-------------------------------------------------------------------------*/ +static int inet_lua_getsockname(lua_State *L) +{ + p_sock sock = (p_sock) lua_touserdata(L, 1); + struct sockaddr_in local; + size_t local_len = sizeof(local); + if (getsockname(sock->fd, (SA *) &local, &local_len) < 0) { + lua_pushnil(L); + return 1; + } + lua_pushstring(L, inet_ntoa(local.sin_addr)); + lua_pushnumber(L, ntohs(local.sin_port)); + return 2; +} + +/*=========================================================================*\ +* Internal functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Passes all resolver information to Lua as a table +* Input +* hp: hostent structure returned by resolver +\*-------------------------------------------------------------------------*/ +static void inet_pushresolved(lua_State *L, struct hostent *hp) +{ + char **alias; + struct in_addr **addr; + int i, resolved; + lua_newtable(L); resolved = lua_gettop(L); + lua_pushstring(L, "name"); + lua_pushstring(L, hp->h_name); + lua_settable(L, resolved); + lua_pushstring(L, "ip"); + lua_pushstring(L, "alias"); + i = 1; + alias = hp->h_aliases; + lua_newtable(L); + while (*alias) { + lua_pushnumber(L, i); + lua_pushstring(L, *alias); + lua_settable(L, -3); + i++; alias++; + } + lua_settable(L, resolved); + i = 1; + lua_newtable(L); + addr = (struct in_addr **) hp->h_addr_list; + while (*addr) { + lua_pushnumber(L, i); + lua_pushstring(L, inet_ntoa(**addr)); + lua_settable(L, -3); + i++; addr++; + } + lua_settable(L, resolved); +} + +/*-------------------------------------------------------------------------*\ +* Tries to create a TCP socket and connect to remote address (address, port) +* Input +* client: socket structure to be used +* address: host name or ip address +* port: port number to bind to +* Returns +* NULL in case of success, error message otherwise +\*-------------------------------------------------------------------------*/ +cchar *inet_tryconnect(p_inet inet, cchar *address, ushort port, + int type) +{ + struct sockaddr_in remote; + cchar *err = NULL; + memset(&remote, 0, sizeof(remote)); + if (strlen(address) && inet_aton(address, &remote.sin_addr)) { + remote.sin_family = AF_INET; + remote.sin_port = htons(port); + err = inet_trysocket(inet, type); + if (err) return err; + if (compat_connect(inet->fd, (SA *) &remote, sizeof(remote)) < 0) { + compat_close(inet->fd); + inet->fd = COMPAT_INVALIDFD; + return compat_connectstrerror(); + } else return NULL; + /* go ahead and try by hostname resolution */ + } else { + struct hostent *hp = gethostbyname(address); + struct in_addr **addr; + if (!hp) return compat_hoststrerror(); + addr = (struct in_addr **) hp->h_addr_list; + for (; *addr != NULL; addr++) { + memcpy(&remote.sin_addr, *addr, sizeof(struct in_addr)); + remote.sin_family = AF_INET; + remote.sin_port = htons(port); + err = inet_trysocket(inet, type); + if (err) return err; + if (compat_connect(inet->fd, (SA *)&remote, sizeof(remote)) < 0) { + compat_close(inet->fd); + inet->fd = COMPAT_INVALIDFD; + } else return NULL; + memset(&remote, 0, sizeof(remote)); + } + return compat_connectstrerror(); + } +} + +/*-------------------------------------------------------------------------*\ +* Tries to create a TCP socket and bind it to (address, port) +* Input +* address: host name or ip address +* port: port number to bind to +* backlog: backlog to set +* Returns +* NULL in case of success, error message otherwise +\*-------------------------------------------------------------------------*/ +cchar *inet_trybind(p_inet inet, cchar *address, ushort port, + int type) +{ + struct sockaddr_in local; + cchar *err = NULL; + memset(&local, 0, sizeof(local)); + /* address is either wildcard or a valid ip address */ + local.sin_addr.s_addr = htonl(INADDR_ANY); + if (!strcmp(address, "*") || + (strlen(address) && inet_aton(address, &local.sin_addr))) { + local.sin_port = htons(port); + local.sin_family = AF_INET; + err = inet_trysocket(inet, type); + if (err) return err; + compat_setreuseaddr(inet->fd); + if (compat_bind(inet->fd, (SA *) &local, sizeof(local)) < 0) { + compat_close(inet->fd); + inet->fd = COMPAT_INVALIDFD; + return compat_bindstrerror(); + } else return NULL; + /* otherwise, proceed with domain name resolution */ + } else { + struct hostent *hp = gethostbyname(address); + struct in_addr **addr; + if (!hp) return compat_hoststrerror(); + addr = (struct in_addr **) hp->h_addr_list; + for (; *addr != NULL; addr++) { + memcpy(&local.sin_addr, *addr, sizeof(struct in_addr)); + local.sin_port = htons(port); + local.sin_family = AF_INET; + err = inet_trysocket(inet, type); + if (err) return err; + compat_setreuseaddr(inet->fd); + if (compat_bind(inet->fd, (SA *) &local, sizeof(local)) < 0) { + compat_close(inet->fd); + inet->fd = COMPAT_INVALIDFD; + } else return NULL; + memset(&local, 0, sizeof(local)); + } + return compat_bindstrerror(); + } +} + +/*-------------------------------------------------------------------------*\ +* Tries to create a new inet socket +* Input +* udp: udp structure +* Returns +* NULL if successfull, error message on error +\*-------------------------------------------------------------------------*/ +cchar *inet_trysocket(p_inet inet, int type) +{ + if (inet->fd != COMPAT_INVALIDFD) compat_close(inet->fd); + inet->fd = compat_socket(AF_INET, type, 0); + if (inet->fd == COMPAT_INVALIDFD) return compat_socketstrerror(); + else return NULL; +} + +/*-------------------------------------------------------------------------*\ +* Some systems do not provide this so that we provide our own. It's not +* marvelously fast, but it works just fine. +\*-------------------------------------------------------------------------*/ +#ifdef COMPAT_INETATON +static int inet_aton(const char *cp, struct in_addr *inp) +{ + unsigned int a = 0, b = 0, c = 0, d = 0; + int n = 0, r; + unsigned long int addr = 0; + r = sscanf(cp, "%u.%u.%u.%u%n", &a, &b, &c, &d, &n); + if (r == 0 || n == 0) return 0; + cp += n; + if (*cp) return 0; + if (a > 255 || b > 255 || c > 255 || d > 255) return 0; + if (inp) { + addr += a; addr <<= 8; + addr += b; addr <<= 8; + addr += c; addr <<= 8; + addr += d; + inp->s_addr = htonl(addr); + } + return 1; +} +#endif From 3bd99ec59996653011f0eb40a0be4a523fe82897 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 22 Mar 2002 20:07:43 +0000 Subject: [PATCH 090/483] Initial revision --- src/select.c | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 src/select.c diff --git a/src/select.c b/src/select.c new file mode 100644 index 0000000..1f83b7a --- /dev/null +++ b/src/select.c @@ -0,0 +1,194 @@ +/*-------------------------------------------------------------------------*\ +* Marks type as selectable +* Input +* name: type name +\*-------------------------------------------------------------------------*/ +void slct_addclass(lua_State *L, cchar *lsclass) +{ + lua_pushstring(L, "selectable sockets"); + lua_gettable(L, LUA_REGISTRYINDEX); + lua_pushstring(L, lsclass); + lua_pushnumber(L, 1); + lua_settable(L, -3); + lua_pop(L, 2); +} + +/*-------------------------------------------------------------------------*\ +* Gets a pointer to a socket structure from a userdata +* Input +* pos: userdata stack index +* Returns +* pointer to structure, or NULL if invalid type +\*-------------------------------------------------------------------------*/ +static p_sock ls_toselectable(lua_State *L) +{ + lua_getregistry(L); + lua_pushstring(L, "sock(selectable)"); + lua_gettable(L, -2); + lua_pushstring(L, lua_type(L, -3)); + lua_gettable(L, -2); + if (lua_isnil(L, -1)) { + lua_pop(L, 3); + return NULL; + } else { + lua_pop(L, 3); + return (p_sock) lua_touserdata(L, -1); + } +} + +/*-------------------------------------------------------------------------*\ +* Waits for a set of sockets until a condition is met or timeout. +* Lua Input: {input}, {output} [, timeout] +* {input}: table of sockets to be tested for input +* {output}: table of sockets to be tested for output +* timeout: maximum amount of time to wait for condition, in seconds +* Lua Returns: {input}, {output}, err +* {input}: table with sockets ready for input +* {output}: table with sockets ready for output +* err: "timeout" or nil +\*-------------------------------------------------------------------------*/ +int global_select(lua_State *L) +{ + int ms = lua_isnil(L, 3) ? -1 : (int) (luaL_opt_number(L, 3, -1) * 1000); + fd_set rfds, *prfds = NULL, wfds, *pwfds = NULL; + struct timeval tv, *ptv = NULL; + unsigned max = 0; + int byfds, readable, writable; + int toread = 1, towrite = 2; + lua_newtable(L); byfds = lua_gettop(L); /* sockets indexed by descriptor */ + lua_newtable(L); readable = lua_gettop(L); + lua_newtable(L); writable = lua_gettop(L); + /* collect sockets to be tested into FD_SET structures and fill byfds */ + if (lua_istable(L, toread)) + prfds = tab2rfds(L, toread, &rfds, &max, byfds, readable, &ms); + else if (!lua_isnil(L, toread)) + luaL_argerror(L, toread, "expected table or nil"); + if (lua_istable(L, towrite)) + pwfds = tab2wfds(L, towrite, &wfds, &max, byfds); + else if (!lua_isnil(L, towrite)) + luaL_argerror(L, towrite, "expected table or nil"); + /* fill timeval structure */ + if (ms >= 0) { + tv.tv_sec = ms / 1000; + tv.tv_usec = (ms % 1000) * 1000; + ptv = &tv; + } else ptv = NULL; /* ptv == NULL when we don't have timeout */ + /* see if we can read, write or if we timedout */ + if (select(max+1, prfds, pwfds, NULL, ptv) <= 0 && ms >= 0) { + ls_pusherror(L, LS_TIMEOUT); + return 3; + } + /* collect readable and writable sockets into result tables */ + fds2tab(L, prfds, max+1, byfds, readable); + fds2tab(L, pwfds, max+1, byfds, writable); + lua_pushnil(L); + return 3; +} + +/*=========================================================================*\ +* Select auxiliar functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Converts a FD_SET structure into a socket table set +* Input +* fds: pointer to FD_SET structure +* max: 1 plus the largest descriptor value in FD_SET +* byfds: table indexed by descriptor number, with corresponding socket tables +* can: table to receive corresponding socket table set +\*-------------------------------------------------------------------------*/ +static void fds2tab(lua_State *L, fd_set *fds, int max, int byfds, int can) +{ + int s; + if (!fds) return; + for (s = 0; s < max; s++) { + if (FD_ISSET(s, fds)) { + lua_pushnumber(L, lua_getn(L, can) + 1); + lua_pushnumber(L, s); + lua_gettable(L, byfds); + lua_settable(L, can); + } + } +} + +/*-------------------------------------------------------------------------*\ +* Converts a socket table set ito a FD_SET structure +* Input +* towrite: socket table set +* Output +* wfds: pointer to FD_SET structure to be filled +* max: largest descriptor value found in wfds +* byfds: table indexed by descriptor number, with corresponding socket tables +\*-------------------------------------------------------------------------*/ +static fd_set *tab2wfds(lua_State *L, int towrite, fd_set *wfds, + int *max, int byfds) +{ + int empty = 1; + FD_ZERO(wfds); + lua_pushnil(L); + while (lua_next(L, towrite)) { + p_sock sock = ls_toselectable(L); + if (sock) { /* skip strange fields */ + NET_FD s = sock->fd; + if (s != NET_INVALIDFD) { /* skip closed sockets */ + lua_pushnumber(L, s); + lua_pushvalue(L, -2); + lua_settable(L, byfds); + if (s > *max) *max = s; + FD_SET(s, wfds); + empty = 0; + } + } + /* get rid of value and expose index */ + lua_pop(L, 1); + } + if (empty) return NULL; + else return wfds; +} + +/*-------------------------------------------------------------------------*\ +* Converts a socket table set ito a FD_SET structure +* Input +* toread: socket table set +* Output +* rfds: pointer to FD_SET structure to be filled +* max: largest descriptor value found in rfds +* byfds: table indexed by descriptor number, with corresponding socket tables +* readable: table to receive socket table if socket is obviously readable +* ms: will be zeroed if a readable socket is detected +\*-------------------------------------------------------------------------*/ +static fd_set *tab2rfds(lua_State *L, int toread, fd_set *rfds, + int *max, int byfds, int readable, int *ms) +{ + int empty = 1; + FD_ZERO(rfds); + lua_pushnil(L); + while (lua_next(L, toread)) { + p_sock sock = ls_toselectable(L); + if (sock) { /* skip strange fields */ + NET_FD s = sock->fd; + if (s != NET_INVALID) { /* skip closed sockets */ + /* a socket can have unread data in our internal buffer. we + pass them straight to the readable set, and test only to + find out which of the other sockets can be written to or + read from immediately. */ + if (sock->vt->readable(sock)) { + *ms = 0; + lua_pushnumber(L, lua_getn(L, readable) + 1); + lua_pushvalue(L, -2); + lua_settable(L, readable); + } else { + lua_pushnumber(L, s); + lua_pushvalue(L, -2); + lua_settable(L, byfds); + if (s > *max) *max = s; + FD_SET(s, rfds); + empty = 0; + } + } + } + /* get rid of value and exposed index */ + lua_pop(L, 1); + } + if (empty) return NULL; + else return rfds; +} From 8f05082f77a5bf612291de593f63e09e7fb3a5cb Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 27 Mar 2002 18:00:00 +0000 Subject: [PATCH 091/483] Initial revision --- src/buffer.c | 338 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/buffer.h | 33 +++++ 2 files changed, 371 insertions(+) create mode 100644 src/buffer.c create mode 100644 src/buffer.h diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000..a9a9782 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,338 @@ +/*=========================================================================*\ +* Buffered input/output routines +* Lua methods: +* send: unbuffered send using C base_send +* receive: buffered read using C base_receive +\*=========================================================================*/ +#include +#include + +#include "lsbuf.h" + +/*=========================================================================*\ +* Internal function prototypes. +\*=========================================================================*/ +static int sendraw(lua_State *L, p_buf buf, cchar *data, size_t len, + size_t *done); +static int recvraw(lua_State *L, p_buf buf, size_t wanted); +static int recvdosline(lua_State *L, p_buf buf); +static int recvunixline(lua_State *L, p_buf buf); +static int recvall(lua_State *L, p_buf buf); + +static int buf_contents(lua_State *L, p_buf buf, cchar **data, size_t *len); +static void buf_skip(lua_State *L, p_buf buf, size_t len); + +/*=========================================================================*\ +* Exported functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +void buf_open(lua_State *L) +{ + (void) L; +} + +/*-------------------------------------------------------------------------*\ +* Initializes C structure +* Input +* buf: buffer structure to initialize +* base: socket object to associate with buffer structure +\*-------------------------------------------------------------------------*/ +void buf_init(lua_State *L, p_buf buf, p_base base) +{ + (void) L; + buf->buf_first = buf->buf_last = 0; + buf->buf_base = base; +} + +/*-------------------------------------------------------------------------*\ +* Send data through buffered object +* Input +* buf: buffer structure to be used +* Lua Input: self, a_1 [, a_2, a_3 ... a_n] +* self: socket object +* a_i: strings to be sent. +* Lua Returns +* On success: nil, followed by the total number of bytes sent +* On error: error message +\*-------------------------------------------------------------------------*/ +int buf_send(lua_State *L, p_buf buf) +{ + int top = lua_gettop(L); + size_t total = 0; + int err = PRIV_DONE; + int arg; + p_base base = buf->buf_base; + tm_markstart(&base->base_tm); + for (arg = 2; arg <= top; arg++) { /* first arg is socket object */ + size_t done, len; + cchar *data = luaL_opt_lstr(L, arg, NULL, &len); + if (!data || err != PRIV_DONE) break; + err = sendraw(L, buf, data, len, &done); + total += done; + } + priv_pusherror(L, err); + lua_pushnumber(L, total); +#ifdef _DEBUG + /* push time elapsed during operation as the last return value */ + lua_pushnumber(L, tm_getelapsed(&base->base_tm)/1000.0); +#endif + return lua_gettop(L) - top; +} + +/*-------------------------------------------------------------------------*\ +* Receive data from a buffered object +* Input +* buf: buffer structure to be used +* Lua Input: self [pat_1, pat_2 ... pat_n] +* self: socket object +* pat_i: may be one of the following +* "*l": reads a text line, defined as a string of caracters terminates +* by a LF character, preceded or not by a CR character. This is +* the default pattern +* "*lu": reads a text line, terminanted by a CR character only. (Unix mode) +* "*a": reads until connection closed +* number: reads 'number' characters from the socket object +* Lua Returns +* On success: one string for each pattern +* On error: all strings for which there was no error, followed by one +* nil value for the remaining strings, followed by an error code +\*-------------------------------------------------------------------------*/ +int buf_receive(lua_State *L, p_buf buf) +{ + int top = lua_gettop(L); + int arg, err = PRIV_DONE; + p_base base = buf->buf_base; + tm_markstart(&base->base_tm); + /* push default pattern if need be */ + if (top < 2) { + lua_pushstring(L, "*l"); + top++; + } + /* make sure we have enough stack space */ + luaL_check_stack(L, top+LUA_MINSTACK, "too many arguments"); + /* receive all patterns */ + for (arg = 2; arg <= top && err == PRIV_DONE; arg++) { + if (!lua_isnumber(L, arg)) { + static cchar *patternnames[] = {"*l", "*lu", "*a", "*w", NULL}; + cchar *pattern = luaL_opt_string(L, arg, NULL); + /* get next pattern */ + switch (luaL_findstring(pattern, patternnames)) { + case 0: /* DOS line pattern */ + err = recvdosline(L, buf); break; + case 1: /* Unix line pattern */ + err = recvunixline(L, buf); break; + case 2: /* Until closed pattern */ + err = recvall(L, buf); break; + case 3: /* Word pattern */ + luaL_arg_check(L, 0, arg, "word patterns are deprecated"); + break; + default: /* else it is an error */ + luaL_arg_check(L, 0, arg, "invalid receive pattern"); + break; + } + /* raw pattern */ + } else err = recvraw(L, buf, (size_t) lua_tonumber(L, arg)); + } + /* push nil for each pattern after an error */ + for ( ; arg <= top; arg++) lua_pushnil(L); + /* last return is an error code */ + priv_pusherror(L, err); +#ifdef _DEBUG + /* push time elapsed during operation as the last return value */ + lua_pushnumber(L, tm_getelapsed(&base->base_tm)/1000.0); +#endif + return lua_gettop(L) - top; +} + +/*-------------------------------------------------------------------------*\ +* Determines if there is any data in the read buffer +* Input +* buf: buffer structure to be used +* Returns +* 1 if empty, 0 if there is data +\*-------------------------------------------------------------------------*/ +int buf_isempty(lua_State *L, p_buf buf) +{ + (void) L; + return buf->buf_first >= buf->buf_last; +} + +/*=========================================================================*\ +* Internal functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Sends a raw block of data through a buffered object. +* Input +* buf: buffer structure to be used +* data: data to be sent +* len: number of bytes to send +* Output +* sent: number of bytes sent +* Returns +* operation error code. +\*-------------------------------------------------------------------------*/ +static int sendraw(lua_State *L, p_buf buf, cchar *data, size_t len, + size_t *sent) +{ + p_base base = buf->buf_base; + size_t total = 0; + int err = PRIV_DONE; + while (total < len && err == PRIV_DONE) { + size_t done; + err = base->base_send(L, base, data + total, len - total, &done); + total += done; + } + *sent = total; + return err; +} + +/*-------------------------------------------------------------------------*\ +* Reads a raw block of data from a buffered object. +* Input +* buf: buffer structure +* wanted: number of bytes to be read +* Returns +* operation error code. +\*-------------------------------------------------------------------------*/ +static int recvraw(lua_State *L, p_buf buf, size_t wanted) +{ + int err = PRIV_DONE; + size_t total = 0; + luaL_Buffer b; + luaL_buffinit(L, &b); + while (total < wanted && err == PRIV_DONE) { + size_t len; cchar *data; + err = buf_contents(L, buf, &data, &len); + len = MIN(len, wanted - total); + luaL_addlstring(&b, data, len); + buf_skip(L, buf, len); + total += len; + } + luaL_pushresult(&b); + return err; +} + +/*-------------------------------------------------------------------------*\ +* Reads everything until the connection is closed +* Input +* buf: buffer structure +* Result +* operation error code. +\*-------------------------------------------------------------------------*/ +static int recvall(lua_State *L, p_buf buf) +{ + int err = PRIV_DONE; + luaL_Buffer b; + luaL_buffinit(L, &b); + while (err == PRIV_DONE) { + cchar *data; size_t len; + err = buf_contents(L, buf, &data, &len); + luaL_addlstring(&b, data, len); + buf_skip(L, buf, len); + } + luaL_pushresult(&b); + return err; +} + +/*-------------------------------------------------------------------------*\ +* Reads a line terminated by a CR LF pair or just by a LF. The CR and LF +* are not returned by the function and are discarded from the buffer. +* Input +* buf: buffer structure +* Result +* operation error code. PRIV_DONE, PRIV_TIMEOUT or PRIV_CLOSED +\*-------------------------------------------------------------------------*/ +static int recvdosline(lua_State *L, p_buf buf) +{ + int err = 0; + luaL_Buffer b; + luaL_buffinit(L, &b); + while (err == PRIV_DONE) { + size_t len, pos; cchar *data; + err = buf_contents(L, buf, &data, &len); + pos = 0; + while (pos < len && data[pos] != '\n') { + /* we ignore all \r's */ + if (data[pos] != '\r') luaL_putchar(&b, data[pos]); + pos++; + } + if (pos < len) { /* found '\n' */ + buf_skip(L, buf, pos+1); /* skip '\n' too */ + break; /* we are done */ + } else /* reached the end of the buffer */ + buf_skip(L, buf, pos); + } + luaL_pushresult(&b); + return err; +} + +/*-------------------------------------------------------------------------*\ +* Reads a line terminated by a LF character, which is not returned by +* the function, and is skipped in the buffer. +* Input +* buf: buffer structure +* Returns +* operation error code. PRIV_DONE, PRIV_TIMEOUT or PRIV_CLOSED +\*-------------------------------------------------------------------------*/ +static int recvunixline(lua_State *L, p_buf buf) +{ + int err = PRIV_DONE; + luaL_Buffer b; + luaL_buffinit(L, &b); + while (err == 0) { + size_t pos, len; cchar *data; + err = buf_contents(L, buf, &data, &len); + pos = 0; + while (pos < len && data[pos] != '\n') { + luaL_putchar(&b, data[pos]); + pos++; + } + if (pos < len) { /* found '\n' */ + buf_skip(L, buf, pos+1); /* skip '\n' too */ + break; /* we are done */ + } else /* reached the end of the buffer */ + buf_skip(L, buf, pos); + } + luaL_pushresult(&b); + return err; +} + +/*-------------------------------------------------------------------------*\ +* Skips a given number of bytes in read buffer +* Input +* buf: buffer structure +* len: number of bytes to skip +\*-------------------------------------------------------------------------*/ +static void buf_skip(lua_State *L, p_buf buf, size_t len) +{ + buf->buf_first += len; + if (buf_isempty(L, buf)) buf->buf_first = buf->buf_last = 0; +} + +/*-------------------------------------------------------------------------*\ +* Return any data available in buffer, or get more data from transport layer +* if buffer is empty. +* Input +* buf: buffer structure +* Output +* data: pointer to buffer start +* len: buffer buffer length +* Returns +* PRIV_DONE, PRIV_CLOSED, PRIV_TIMEOUT ... +\*-------------------------------------------------------------------------*/ +static int buf_contents(lua_State *L, p_buf buf, cchar **data, size_t *len) +{ + int err = PRIV_DONE; + p_base base = buf->buf_base; + if (buf_isempty(L, buf)) { + size_t done; + err = base->base_receive(L, base, buf->buf_data, BUF_SIZE, &done); + buf->buf_first = 0; + buf->buf_last = done; + } + *len = buf->buf_last - buf->buf_first; + *data = buf->buf_data + buf->buf_first; + return err; +} diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 0000000..7463a67 --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,33 @@ +/*=========================================================================*\ +* Buffered input/output routines +* RCS ID: $Id$ +\*=========================================================================*/ +#ifndef BUF_H_ +#define BUF_H_ + +#include +#include "lsbase.h" + +/* buffer size in bytes */ +#define BUF_SIZE 8192 + +/*-------------------------------------------------------------------------*\ +* Buffer control structure +\*-------------------------------------------------------------------------*/ +typedef struct t_buf_tag { + size_t buf_first, buf_last; + uchar buf_data[BUF_SIZE]; + p_base buf_base; +} t_buf; +typedef t_buf *p_buf; + +/*-------------------------------------------------------------------------*\ +* Exported functions +\*-------------------------------------------------------------------------*/ +void buf_open(lua_State *L); +void buf_init(lua_State *L, p_buf buf, p_base base); +int buf_send(lua_State *L, p_buf buf); +int buf_receive(lua_State *L, p_buf buf); +int buf_isempty(lua_State *L, p_buf buf); + +#endif /* BUF_H_ */ From 5a92c17d8534e7ac762de1bd6b825039f71099f3 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 10 Jun 2002 18:38:08 +0000 Subject: [PATCH 092/483] Corrigido bug no select. --- src/luasocket.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index 5168fbd..636bec2 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -576,7 +576,7 @@ int global_select(lua_State *L) int ms = lua_isnil(L, 3) ? -1 : (int) (luaL_opt_number(L, 3, -1) * 1000); fd_set readfds, *prfds = NULL, writefds, *pwfds = NULL; struct timeval tm, *ptm = NULL; - int ret; + int ret, dirty = 0; unsigned max = 0; SOCKET s; int byfds, canread, canwrite; @@ -604,7 +604,7 @@ int global_select(lua_State *L) out which of the other sockets can be written to or read from immediately. */ if (!bf_isempty(sock)) { - ms = 0; + ms = 0; dirty = 1; lua_pushnumber(L, lua_getn(L, canread) + 1); lua_pushvalue(L, -2); lua_settable(L, canread); @@ -648,7 +648,7 @@ int global_select(lua_State *L) /* see if we can read, write or if we timedout */ ret = select(max, prfds, pwfds, NULL, ptm); /* did we timeout? */ - if (ret <= 0 && ms >= 0) { + if (ret <= 0 && ms >= 0 && !dirty) { push_error(L, NET_TIMEOUT); return 3; } From b04b81ddd959948143f817ee7230783660e24222 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 3 Jul 2002 19:06:52 +0000 Subject: [PATCH 093/483] main module. just initializes other modules. --- src/luasocket.c | 2190 +---------------------------------------------- 1 file changed, 38 insertions(+), 2152 deletions(-) diff --git a/src/luasocket.c b/src/luasocket.c index 636bec2..d329d4e 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -1,2174 +1,60 @@ /*=========================================================================*\ -* IPv4 Sockets for the Lua language +* Networking support for the Lua language * Diego Nehab * 26/11/1999 * -* Module: luasocket.c -* -* This module is part of an effort to make the most important features -* of the IPv4 Socket layer available to Lua scripts. -* The Lua interface to TCP/IP follows the BSD TCP/IP API closely, -* trying to simplify all tasks involved in setting up both client -* and server connections. -* The provided IO routines, send and receive, follow the Lua style, being -* very similar to the standard Lua read and write functions. -* The module implements both a BSD bind and a Winsock2 bind, and has -* been tested on several Unix flavors, as well as Windows 98 and NT. +* This library is part of an effort to progressively increase the network +* connectivity of the Lua language. The Lua interface to networking +* functions follows the Sockets API closely, trying to simplify all tasks +* involved in setting up both client and server connections. The provided +* IO routines, however, follow the Lua style, being very similar to the +* standard Lua read and write functions. * * RCS ID: $Id$ \*=========================================================================*/ /*=========================================================================*\ -* Common include files +* Standard include files \*=========================================================================*/ -#include -#include -#include -#include -#include - -#include #include +#include +/*=========================================================================*\ +* LuaSocket includes +\*=========================================================================*/ #include "luasocket.h" +#include "lspriv.h" +#include "lsselect.h" +#include "lscompat.h" +#include "lsbase.h" +#include "lstm.h" +#include "lsbuf.h" +#include "lssock.h" +#include "lsinet.h" +#include "lstcpc.h" +#include "lstcps.h" +#include "lstcps.h" +#include "lsudp.h" /*=========================================================================*\ -* WinSock2 include files -\*=========================================================================*/ -#ifdef WIN32 -#include -#include -#else - -/*=========================================================================*\ -* BSD include files -\*=========================================================================*/ -/* close function */ -#include -/* fnctnl function and associated constants */ -#include -/* struct timeval and CLK_TCK */ -#include -/* times function and struct tms */ -#include -/* internet protocol definitions */ -#include -#include -/* struct sockaddr */ -#include -/* socket function */ -#include -/* gethostbyname and gethostbyaddr functions */ -#include -#endif - -/*=========================================================================*\ -* Datatype compatibilization and some simple changes -\*=========================================================================*/ -#ifndef WIN32 -/* WinSock2 has a closesock function instead of the regular close */ -#define closesocket close -/* it defines a SOCKET type instead of using an integer file descriptor */ -#define SOCKET int -/* and uses the this macro to represent and invalid socket */ -#define INVALID_SOCKET (-1) -/* SunOS, does not define CLK_TCK */ -#ifndef CLK_TCK -#define CLK_TCK 60 -#endif -#endif - -/*=========================================================================*\ -* Module definitions +* Exported functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ -* The send and receive function can return one of the following return -* codes. The values are mapped into Lua values by the function -* push_error. -\*-------------------------------------------------------------------------*/ -#define NET_DONE -1 /* operation completed successfully */ -#define NET_TIMEOUT 0 /* operation timed out */ -#define NET_CLOSED 1 /* the connection has been closed */ -#define NET_REFUSED 2 /* the data transfer has been refused */ - -/*-------------------------------------------------------------------------*\ -* Time out mode to be checked -\*-------------------------------------------------------------------------*/ -#define TM_RECEIVE 1 -#define TM_SEND 2 - -/*-------------------------------------------------------------------------*\ -* Each socket is represented by a table with the supported methods and -* the p_sock structure as fields. -\*-------------------------------------------------------------------------*/ -#define P_SOCK "(p_sock)sock" - -/*-------------------------------------------------------------------------*\ -* Both socket types are stored in the same structure to simplify -* implementation. The tag value used is different, though. -* The buffer parameters are not used by server and UDP sockets. -\*-------------------------------------------------------------------------*/ -typedef struct t_sock { - /* operating system socket object */ - SOCKET sock; - /* start time of the current operation */ - int tm_start; - /* return and blocking timeout values (-1 if no limit) */ - int tm_return, tm_block; - /* buffered I/O storage */ - unsigned char bf_buffer[LUASOCKET_TCPBUFFERSIZE]; - /* first and last red bytes not yet passed to application */ - int bf_first, bf_last; - /* is this udp socket in "connected" state? */ - int is_connected; -#ifdef _DEBUG - /* end time of current operation, for debug purposes */ - int tm_end; -#endif -} t_sock; -typedef t_sock *p_sock; - -/*-------------------------------------------------------------------------*\ -* Tags passed as closure values to global LuaSocket API functions -\*-------------------------------------------------------------------------*/ -typedef struct t_tags { - int client, server, table, udp; -} t_tags; -typedef t_tags *p_tags; - -/*-------------------------------------------------------------------------*\ -* Macros and internal declarations -\*-------------------------------------------------------------------------*/ -/* 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 - -/* we are lazy.. */ -typedef struct sockaddr SA; - -/*=========================================================================*\ -* Internal function prototypes -\*=========================================================================*/ -/* luasocket global API functions */ -static int global_tcpconnect(lua_State *L); -static int global_tcpbind(lua_State *L); -static int global_select(lua_State *L); -static int global_toip(lua_State *L); -static int global_tohostname(lua_State *L); -static int global_udpsocket(lua_State *L); - -#ifndef LUASOCKET_NOGLOBALS -static int global_callfromtable(lua_State *L); -#endif - -/* luasocket table method API functions */ -static int table_tcpaccept(lua_State *L); -static int table_tcpsend(lua_State *L); -static int table_tcpreceive(lua_State *L); -static int table_udpsendto(lua_State *L); -static int table_udpreceivefrom(lua_State *L); -static int table_udpsetpeername(lua_State *L); -static int table_timeout(lua_State *L); -static int table_close(lua_State *L); -static int table_getpeername(lua_State *L); -static int table_getsockname(lua_State *L); - -/* buffered I/O management */ -static const unsigned char *bf_receive(p_sock sock, int *length); -static void bf_skip(p_sock sock, int length); -static int bf_isempty(p_sock sock); - -/* timeout management */ -static int tm_timedout(p_sock sock, int mode); -static int tm_gettimeleft(p_sock sock); -static int tm_gettime(void); -static void tm_markstart(p_sock sock); - -/* I/O */ -static int send_raw(p_sock sock, const char *data, int wanted, int *err); -static int receive_raw(lua_State *L, p_sock sock, int wanted); -static int receive_word(lua_State *L, p_sock sock); -static int receive_dosline(lua_State *L, p_sock sock); -static int receive_unixline(lua_State *L, p_sock sock); -static int receive_all(lua_State *L, p_sock sock); - -/* parameter manipulation functions */ -static p_tags pop_tags(lua_State *L); -static p_sock pop_sock(lua_State *L); -static p_sock get_sock(lua_State *L, int s, p_tags tags, int *tag); -static p_sock get_selfsock(lua_State *L, p_tags tags, int *tag); -static p_sock push_servertable(lua_State *L, p_tags tags); -static p_sock push_clienttable(lua_State *L, p_tags tags); -static p_sock push_udptable(lua_State *L, p_tags tags); -static void push_error(lua_State *L, int err); -static void push_resolved(lua_State *L, struct hostent *hp); - -/* error code translations functions */ -static char *host_strerror(void); -static char *bind_strerror(void); -static char *socket_strerror(void); -static char *connect_strerror(void); - -/* socket auxiliary functions */ -static const char *tcp_trybind(p_sock sock, const char *address, - unsigned short port, int backlog); -static const char *tcp_tryconnect(p_sock sock, const char *address, - unsigned short port); -static const char *udp_setpeername(p_sock sock, const char *address, - unsigned short port); -static const char *udp_setsockname(p_sock sock, const char *address, - unsigned short port); -static int set_option(lua_State *L, p_sock sock); -static void set_reuseaddr(p_sock sock); -static void set_blocking(p_sock sock); -static void set_nonblocking(p_sock sock); - -#ifdef WIN32 -static int winsock_open(void); -#define LUASOCKET_ATON -#endif - -#ifdef LUASOCKET_ATON -static int inet_aton(const char *cp, struct in_addr *inp); -#endif - -/* tag methods */ -static int gc_table(lua_State *L); - -/*=========================================================================*\ -* Test support functions -\*=========================================================================*/ -#ifdef _DEBUG -/*-------------------------------------------------------------------------*\ -* Returns the time the system has been up, in secconds. -\*-------------------------------------------------------------------------*/ -static int global_time(lua_State *L); -static int global_time(lua_State *L) -{ - lua_pushnumber(L, tm_gettime()/1000.0); - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Causes a Lua script to sleep for the specified number of secconds -\*-------------------------------------------------------------------------*/ -static int global_sleep(lua_State *L); -static int global_sleep(lua_State *L) -{ - int sec = (int) luaL_check_number(L, 1); -#ifdef WIN32 - Sleep(1000*sec); -#else - sleep(sec); -#endif - return 0; -} - -#endif - -/*=========================================================================*\ -* Lua exported functions -* These functions can be accessed from a Lua script. -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Creates a client socket and returns it to the Lua script. The timeout -* values are initialized as -1 so that the socket will block at any -* IO operation. -* Lua Input: address, port -* address: host name or ip address to connect to -* port: port number on host -* Lua Returns -* On success: client socket object -* On error: nil, followed by an error message -\*-------------------------------------------------------------------------*/ -static int global_tcpconnect(lua_State *L) -{ - p_tags tags = pop_tags(L); - const char *address = luaL_check_string(L, 1); - unsigned short port = (unsigned short) luaL_check_number(L, 2); - p_sock sock = push_clienttable(L, tags); - const char *err = tcp_tryconnect(sock, address, port); - if (err) { - lua_pushnil(L); - lua_pushstring(L, err); - return 2; - } - set_nonblocking(sock); - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Creates a udp socket object and returns it to the Lua script. -* Lua Returns -* On success: udp socket -* On error: nil, followed by an error message -\*-------------------------------------------------------------------------*/ -static int global_udpsocket(lua_State *L) -{ - p_tags tags = pop_tags(L); - int top = lua_gettop(L); - p_sock sock = push_udptable(L, tags); - if (!sock) return 2; - if (top >= 1 ) { - if (lua_istable(L, 1)) { - lua_pushnil(L); - while (lua_next(L, 1)) { - if (!set_option(L, sock)) lua_error(L, "error setting option"); - lua_pop(L, 1); - } - } else luaL_argerror(L, 1, "invalid options"); - } - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Waits for and returns a client socket object attempting connection -* with a server socket. The function blocks until a client shows up or -* until a timeout condition is met. -* Lua Input: sock -* sock: server socket created by the bind function -* Lua Returns -* On success: client socket attempting connection -* On error: nil followed by an error message -\*-------------------------------------------------------------------------*/ -static int table_tcpaccept(lua_State *L) -{ - struct sockaddr_in client_addr; - size_t client_len = sizeof(client_addr); - p_sock server = pop_sock(L); - p_tags tags = pop_tags(L); - p_sock client = push_clienttable(L, tags); - tm_markstart(server); - if (tm_gettimeleft(server) >= 0) { - set_nonblocking(server); - do { - if (tm_timedout(server, TM_RECEIVE)) { - lua_pushnil(L); - push_error(L, NET_TIMEOUT); - return 2; - } - client->sock = accept(server->sock, (SA *) &client_addr, - &client_len); - } while (client->sock == INVALID_SOCKET); - - } else { - set_blocking(server); - client->sock = accept(server->sock, (SA *) &client_addr, &client_len); - } - set_nonblocking(client); - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Associates an address to a server socket. -* Lua Input: address, port [, backlog] -* address: host name or ip address to bind to -* port: port to bind to -* backlog: connection queue length (default: 1) -* Lua Returns -* On success: server socket bound to address -* On error: nil, followed by an error message -\*-------------------------------------------------------------------------*/ -static int global_tcpbind(lua_State *L) -{ - p_tags tags = pop_tags(L); - const char *address = luaL_check_string(L, 1); - unsigned short port = (unsigned short) luaL_check_number(L, 2); - int backlog = (int) luaL_opt_number(L, 3, 1); - p_sock sock = push_servertable(L, tags); - const char *err; - if (!sock) { - lua_pushnil(L); - lua_pushstring(L, "out of memory"); - return 2; - } - err = tcp_trybind(sock, address, port, backlog); - if (err) { - lua_pushnil(L); - lua_pushstring(L, err); - return 2; - } - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Associates a local address to UDP socket -* Lua Input: address, port -* address: host name or ip address to bind to -* port: port to bind to -* Lua Returns -* On success: nil -* On error: error message -\*-------------------------------------------------------------------------*/ -static int table_udpsetsockname(lua_State *L) -{ - p_sock sock = pop_sock(L); - const char *address = luaL_check_string(L, 2); - unsigned short port = (unsigned short) luaL_check_number(L, 3); - const char *err = udp_setsockname(sock, address, port); - if (err) { - lua_pushstring(L, err); - return 1; - } - lua_pushnil(L); - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Sets a peer for a UDP socket -* Lua Input: address, port -* address: remote host name -* port: remote host port -* Lua Returns -* On success: nil -* On error: error message -\*-------------------------------------------------------------------------*/ -static int table_udpsetpeername(lua_State *L) -{ - p_sock sock = pop_sock(L); - const char *address = luaL_check_string(L, 2); - unsigned short port = (unsigned short) luaL_check_number(L, 3); - const char *err = udp_setpeername(sock, address, port); - if (err) { - lua_pushstring(L, err); - return 1; - } - sock->is_connected = 1; - lua_pushnil(L); - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Sets timeout values for IO operations on a socket -* Lua Input: sock, time [, mode] -* sock: client socket created by the connect function -* time: time out value in seconds -* mode: "b" for block timeout, "r" for return timeout. (default: b) -\*-------------------------------------------------------------------------*/ -static int table_timeout(lua_State *L) -{ - p_sock sock = pop_sock(L); - int ms = lua_isnil(L, 2) ? -1 : (int) (luaL_check_number(L, 2)*1000.0); - const char *mode = luaL_opt_string(L, 3, "b"); - switch (*mode) { - case 'b': - sock->tm_block = ms; - break; - case 'r': - sock->tm_return = ms; - break; - default: - luaL_arg_check(L, 0, 3, "invalid timeout mode"); - break; - } - return 0; -} - -/*-------------------------------------------------------------------------*\ -* Send data through a TCP socket -* Lua Input: sock, a_1 [, a_2, a_3 ... a_n] -* sock: client socket created by the connect function -* a_i: strings to be sent. The strings will be sent on the order they -* appear as parameters -* Lua Returns -* On success: nil, followed by the total number of bytes sent -* On error: error message -\*-------------------------------------------------------------------------*/ -static int table_tcpsend(lua_State *L) -{ - int arg; - p_sock sock = pop_sock(L); - int top = lua_gettop(L); - int total = 0; - int err = NET_DONE; - tm_markstart(sock); - for (arg = 2; arg <= top; arg++) { /* skip self table */ - int sent; - size_t wanted; - const char *data = luaL_opt_lstr(L, arg, NULL, &wanted); - if (!data || err != NET_DONE) break; - err = send_raw(sock, data, wanted, &sent); - total += sent; - } - push_error(L, err); - lua_pushnumber(L, total); -#ifdef _DEBUG - /* push time elapsed during operation as the last return value */ - lua_pushnumber(L, (sock->tm_end - sock->tm_start)/1000.0); -#endif - return lua_gettop(L) - top; -} - -/*-------------------------------------------------------------------------*\ -* Send data through a unconnected UDP socket -* Lua Input: sock, data, ip, port -* sock: udp socket -* data: data to be sent -* ip: ip address of target -* port: port in target -* Lua Returns -* On success: nil, followed by the total number of bytes sent -* On error: error message -\*-------------------------------------------------------------------------*/ -static int table_udpsendto(lua_State *L) -{ - p_sock sock = pop_sock(L); - size_t wanted; - const char *data = luaL_check_lstr(L, 2, &wanted); - const char *ip = luaL_check_string(L, 3); - unsigned short port = (unsigned short) luaL_check_number(L, 4); - struct sockaddr_in peer; - int sent; - if (sock->is_connected) lua_error(L, "sendto on connected socket"); - tm_markstart(sock); - if (tm_timedout(sock, TM_SEND)) { - push_error(L, NET_TIMEOUT); - return 1; - } - memset(&peer, 0, sizeof(peer)); - peer.sin_family = AF_INET; - peer.sin_port = htons(port); - if (!inet_aton(ip, &peer.sin_addr)) lua_error(L, "invalid ip address"); - sent = sendto(sock->sock, data, wanted, 0, (SA *) &peer, sizeof(peer)); - if (sent >= 0) { - lua_pushnil(L); - lua_pushnumber(L, sent); - return 2; - } else { - push_error(L, NET_REFUSED); - return 1; - } -} - -/*-------------------------------------------------------------------------*\ -* Global function that calls corresponding table method. -\*-------------------------------------------------------------------------*/ -#ifndef LUASOCKET_NOGLOBALS -int global_callfromtable(lua_State *L) -{ - p_tags tags = pop_tags(L); - if (lua_tag(L, 1) != tags->table) lua_error(L, "invalid socket object"); - lua_gettable(L, 1); - lua_insert(L, 1); - lua_call(L, lua_gettop(L)-1, LUA_MULTRET); - return lua_gettop(L); -} -#endif - -/*-------------------------------------------------------------------------*\ -* Waits for a set of sockets until a condition is met or timeout. -* Lua Input: {input}, {output} [, timeout] -* {input}: table of sockets to be tested for input -* {output}: table of sockets to be tested for output -* timeout: maximum amount of time to wait for condition, in seconds -* Lua Returns: {input}, {output}, err -* {input}: table with sockets ready for input -* {output}: table with sockets ready for output -* err: "timeout" or nil -\*-------------------------------------------------------------------------*/ -int global_select(lua_State *L) -{ - p_tags tags = pop_tags(L); - int ms = lua_isnil(L, 3) ? -1 : (int) (luaL_opt_number(L, 3, -1) * 1000); - fd_set readfds, *prfds = NULL, writefds, *pwfds = NULL; - struct timeval tm, *ptm = NULL; - int ret, dirty = 0; - unsigned max = 0; - SOCKET s; - int byfds, canread, canwrite; - /* reset the file descriptor sets */ - FD_ZERO(&readfds); FD_ZERO(&writefds); - /* all sockets, indexed by socket number, for internal use */ - lua_newtable(L); byfds = lua_gettop(L); - /* readable sockets table to be returned */ - lua_newtable(L); canread = lua_gettop(L); - /* writable sockets table to be returned */ - lua_newtable(L); canwrite = lua_gettop(L); - /* get sockets we will test for readability into fd_set */ - if (lua_istable(L, 1)) { - lua_pushnil(L); - while (lua_next(L, 1)) { - if (lua_tag(L, -1) == tags->table) { /* skip strange fields */ - p_sock sock = get_sock(L, -1, tags, NULL); - if (sock->sock != INVALID_SOCKET) { /* skip closed sockets */ - lua_pushnumber(L, sock->sock); - lua_pushvalue(L, -2); - lua_settable(L, byfds); - if (sock->sock > max) max = sock->sock; - /* a socket can have unread data in our internal - buffer. in that case, we only call select to find - out which of the other sockets can be written to - or read from immediately. */ - if (!bf_isempty(sock)) { - ms = 0; dirty = 1; - lua_pushnumber(L, lua_getn(L, canread) + 1); - lua_pushvalue(L, -2); - lua_settable(L, canread); - } else { - FD_SET(sock->sock, &readfds); - prfds = &readfds; - } - } - } - /* get rid of lua_next value and expose index */ - lua_pop(L, 1); - } - } else if (!lua_isnil(L, 1)) luaL_argerror(L, 1, "expected table or nil"); - /* get sockets we will test for writability into fd_set */ - if (lua_istable(L, 2)) { - lua_pushnil(L); - while (lua_next(L, 2)) { - if (lua_tag(L, -1) == tags->table) { /* skip strange fields */ - p_sock sock = get_sock(L, -1, tags, NULL); - if (sock->sock != INVALID_SOCKET) { /* skip closed sockets */ - lua_pushnumber(L, sock->sock); - lua_pushvalue(L, -2); - lua_settable(L, byfds); - if (sock->sock > max) max = sock->sock; - FD_SET(sock->sock, &writefds); - pwfds = &writefds; - } - } - /* get rid of lua_next value and expose index */ - lua_pop(L, 1); - } - } else if (!lua_isnil(L, 2)) luaL_argerror(L, 2, "expected table or nil"); - max++; - /* configure timeout value */ - if (ms >= 0) { - ptm = &tm; /* ptm == NULL when we don't have timeout */ - /* fill timeval structure */ - tm.tv_sec = ms / 1000; - tm.tv_usec = (ms % 1000) * 1000; - } - /* see if we can read, write or if we timedout */ - ret = select(max, prfds, pwfds, NULL, ptm); - /* did we timeout? */ - if (ret <= 0 && ms >= 0 && !dirty) { - push_error(L, NET_TIMEOUT); - return 3; - } - /* collect readable sockets */ - if (prfds) { - for (s = 0; s < max; s++) { - if (FD_ISSET(s, prfds)) { - lua_pushnumber(L, lua_getn(L, canread) + 1); - lua_pushnumber(L, s); - lua_gettable(L, byfds); - lua_settable(L, canread); - } - } - } - /* collect writable sockets */ - if (pwfds) { - for (s = 0; s < max; s++) { - if (FD_ISSET(s, pwfds)) { - lua_pushnumber(L, lua_getn(L, canwrite) + 1); - lua_pushnumber(L, s); - lua_gettable(L, byfds); - lua_settable(L, canwrite); - } - } - } - lua_pushnil(L); - return 3; -} - -/*-------------------------------------------------------------------------*\ -* Returns the list of ip addresses associated with a host name -* Lua Input: address -* address: ip address or hostname to dns lookup -* Lua Returns -* On success: first IP address followed by a resolved table -* On error: nil, followed by an error message -\*-------------------------------------------------------------------------*/ -static int global_toip(lua_State *L) -{ - const char *address = luaL_check_string(L, 1); - struct in_addr addr; - struct hostent *hp; - if (inet_aton(address, &addr)) - hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET); - else hp = gethostbyname(address); - if (!hp) { - lua_pushnil(L); - lua_pushstring(L, host_strerror()); - return 2; - } - addr = *((struct in_addr *) hp->h_addr); - lua_pushstring(L, inet_ntoa(addr)); - push_resolved(L, hp); - return 2; -} - -/*-------------------------------------------------------------------------*\ -* Returns the list of host names associated with an ip address -* Lua Input: address -* address: ip address or host name to reverse dns lookup -* Lua Returns -* On success: canonic name followed by a resolved table -* On error: nil, followed by an error message -\*-------------------------------------------------------------------------*/ -static int global_tohostname(lua_State *L) -{ - const char *address = luaL_check_string(L, 1); - struct in_addr addr; - struct hostent *hp; - if (inet_aton(address, &addr)) - hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET); - else hp = gethostbyname(address); - if (!hp) { - lua_pushnil(L); - lua_pushstring(L, host_strerror()); - return 2; - } - lua_pushstring(L, hp->h_name); - push_resolved(L, hp); - return 2; -} - -/*-------------------------------------------------------------------------*\ -* Send data through a connected UDP socket -* Lua Input: sock, data -* sock: udp socket -* data: data to be sent -* Lua Returns -* On success: nil, followed by the total number of bytes sent -* On error: error message -\*-------------------------------------------------------------------------*/ -static int table_udpsend(lua_State *L) -{ - p_sock sock = pop_sock(L); - size_t wanted; - int sent; - const char *data = luaL_check_lstr(L, 2, &wanted); - if (!sock->is_connected) lua_error(L, "send on unconnected socket"); - tm_markstart(sock); - if (tm_timedout(sock, TM_SEND)) { - push_error(L, NET_TIMEOUT); - return 1; - } - sent = send(sock->sock, data, wanted, 0); - if (sent >= 0) { - lua_pushnil(L); - lua_pushnumber(L, sent); - return 2; - } else { - push_error(L, NET_REFUSED); - return 1; - } -} - -/*-------------------------------------------------------------------------*\ -* Receives a datagram from a UDP socket -* Lua Input: sock [, wanted] -* sock: client socket created by the connect function -* wanted: the number of bytes expected (default: LUASOCKET_UDPBUFFERSIZE) -* Lua Returns -* On success: datagram received, ip and port of sender -* On error: nil, followed by an error message -\*-------------------------------------------------------------------------*/ -static int table_udpreceivefrom(lua_State *L) -{ - p_sock sock = pop_sock(L); - size_t wanted = (int) luaL_opt_number(L, 2, LUASOCKET_UDPBUFFERSIZE); - struct sockaddr_in peer; - size_t peer_len = sizeof(peer); - unsigned char buffer[LUASOCKET_UDPBUFFERSIZE]; - int got; - if (sock->is_connected) lua_error(L, "receivefrom on connected socket"); - tm_markstart(sock); - if (tm_timedout(sock, TM_RECEIVE)) { - lua_pushnil(L); - push_error(L, NET_TIMEOUT); - return 2; - } - wanted = MIN(wanted, sizeof(buffer)); - got = recvfrom(sock->sock, buffer, wanted, 0, (SA *) &peer, &peer_len); - if (got >= 0) { - lua_pushlstring(L, buffer, got); - lua_pushstring(L, inet_ntoa(peer.sin_addr)); - lua_pushnumber(L, ntohs(peer.sin_port)); - return 3; - } else { - lua_pushnil(L); - push_error(L, NET_REFUSED); - return 2; - } -} - -/*-------------------------------------------------------------------------*\ -* Receives data from a UDP socket -* Lua Input: sock [, wanted] -* sock: client socket created by the connect function -* wanted: the number of bytes expected (default: LUASOCKET_UDPBUFFERSIZE) -* Lua Returns -* On success: datagram received -* On error: nil, followed by an error message -\*-------------------------------------------------------------------------*/ -static int table_udpreceive(lua_State *L) -{ - p_sock sock = pop_sock(L); - size_t wanted = (size_t) luaL_opt_number(L, 2, LUASOCKET_UDPBUFFERSIZE); - unsigned char buffer[LUASOCKET_UDPBUFFERSIZE]; - int got; - tm_markstart(sock); - if (tm_timedout(sock, TM_RECEIVE)) { - lua_pushnil(L); - push_error(L, NET_TIMEOUT); - return 2; - } - got = recv(sock->sock, buffer, MIN(wanted, sizeof(buffer)), 0); - if (got >= 0) { - lua_pushlstring(L, buffer, got); - return 1; - } else { - lua_pushnil(L); - push_error(L, NET_REFUSED); - return 2; - } -} - -/*-------------------------------------------------------------------------*\ -* Receive data from a TCP socket -* Lua Input: sock [pat_1, pat_2 ... pat_n] -* sock: client socket created by the connect function -* pat_i: may be one of the following -* "*l": reads a text line, defined as a string of caracters terminates -* by a LF character, preceded or not by a CR character. This is -* the default pattern -* "*lu": reads a text line, terminanted by a CR character only. (Unix mode) -* "*a": reads until connection closed -* number: reads 'number' characters from the socket -* Lua Returns -* On success: one string for each pattern -* On error: all strings for which there was no error, followed by one -* nil value for the remaining strings, followed by an error code -\*-------------------------------------------------------------------------*/ -static int table_tcpreceive(lua_State *L) -{ - static const char *const modenames[] = {"*l", "*lu", "*a", "*w", NULL}; - const char *mode; - int err = NET_DONE; - int arg; - p_sock sock = pop_sock(L); - int top = lua_gettop(L); - tm_markstart(sock); - /* push default pattern if need be */ - if (top < 2) { - lua_pushstring(L, "*l"); - top++; - } - /* make sure we have enough stack space */ - luaL_checkstack(L, top+LUA_MINSTACK, "too many arguments"); - /* receive all patterns */ - for (arg = 2; arg <= top; arg++) { - /* if one pattern fails, we just skip all other patterns */ - if (err != NET_DONE) { - lua_pushnil(L); - continue; - } - if (lua_isnumber(L, arg)) { - int size = (int) lua_tonumber(L, arg); - err = receive_raw(L, sock, size); - } else { - mode = luaL_opt_string(L, arg, NULL); - /* get next pattern */ - switch (luaL_findstring(mode, modenames)) { - /* DOS line mode */ - case 0: err = receive_dosline(L, sock); break; - /* Unix line mode */ - case 1: err = receive_unixline(L, sock); break; - /* until closed mode */ - case 2: err = receive_all(L, sock); break; - /* word */ - case 3: err = receive_word(L, sock); break; - /* else it is an error */ - default: - luaL_arg_check(L, 0, arg, "invalid receive pattern"); - break; - } - } - } - /* last return is an error code */ - push_error(L, err); -#ifdef _DEBUG - /* push time elapsed during operation as the last return value */ - lua_pushnumber(L, (sock->tm_end - sock->tm_start)/1000.0); -#endif - return lua_gettop(L) - top; -} - -/*-------------------------------------------------------------------------*\ -* Retrieves socket peer name -* Lua Input: sock -* sock: socket -* Lua Returns -* On success: ip address and port of peer -* On error: nil -\*-------------------------------------------------------------------------*/ -static int table_getpeername(lua_State *L) -{ - p_sock sock = pop_sock(L); - struct sockaddr_in peer; - size_t peer_len = sizeof(peer); - if (getpeername(sock->sock, (SA *) &peer, &peer_len) < 0) { - lua_pushnil(L); - return 1; - } - lua_pushstring(L, inet_ntoa(peer.sin_addr)); - lua_pushnumber(L, ntohs(peer.sin_port)); - return 2; -} - -/*-------------------------------------------------------------------------*\ -* Retrieves socket local name -* Lua Input: sock -* sock: socket -* Lua Returns -* On success: local ip address and port -* On error: nil -\*-------------------------------------------------------------------------*/ -static int table_getsockname(lua_State *L) -{ - p_sock sock = pop_sock(L); - struct sockaddr_in local; - size_t local_len = sizeof(local); - if (getsockname(sock->sock, (SA *) &local, &local_len) < 0) { - lua_pushnil(L); - return 1; - } - lua_pushstring(L, inet_ntoa(local.sin_addr)); - lua_pushnumber(L, ntohs(local.sin_port)); - return 2; -} - -/*-------------------------------------------------------------------------*\ -* Closes a socket. -* Lua Input -* sock: socket to be closed -\*-------------------------------------------------------------------------*/ -static int table_close(lua_State *L) -{ - /* close socket and set value to INVALID_SOCKET so that - ** pop_sock can later detect the use of a closed socket */ - p_sock sock = (p_sock) lua_touserdata(L, -1); - if (!sock) lua_error(L, "invalid socket object"); - if (sock->sock != INVALID_SOCKET) closesocket(sock->sock); - sock->sock = INVALID_SOCKET; - return 0; -} - -/*-------------------------------------------------------------------------*\ -* Garbage collection fallback for the socket objects. This function -* makes sure that all collected sockets are closed. -\*-------------------------------------------------------------------------*/ -static int gc_table(lua_State *L) -{ - p_tags tags = pop_tags(L); - p_sock sock = get_selfsock(L, tags, NULL); - /* sock might have been closed before */ - if (sock->sock != INVALID_SOCKET) { - closesocket(sock->sock); - sock->sock = INVALID_SOCKET; - } - return 0; -} - -/*=========================================================================*\ -* Internal functions -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Instals a handler to ignore sigpipe. That is, unless the signal had -* already been redefined. This function is not needed on the WinSock2, -* since it's sockets don't raise this signal. -\*-------------------------------------------------------------------------*/ -#ifndef WIN32 -static void handle_sigpipe(void); -static void handle_sigpipe(void) -{ - struct sigaction new; - memset(&new, 0, sizeof(new)); - new.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &new, NULL); -} -#endif - -/*-------------------------------------------------------------------------*\ -* Tries to create a TCP socket and connect to remote address (address, port) -* Input -* address: host name or ip address -* port: port number to bind to -* Returns -* NULL in case of success, error message otherwise -\*-------------------------------------------------------------------------*/ -static const char *tcp_tryconnect(p_sock sock, const char *address, - unsigned short port) -{ - struct sockaddr_in remote; - memset(&remote, 0, sizeof(remote)); - if (inet_aton(address, &remote.sin_addr)) { - remote.sin_family = AF_INET; - remote.sin_port = htons(port); - sock->sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock->sock == INVALID_SOCKET) return socket_strerror(); - if (connect(sock->sock, (SA *) &remote, sizeof(remote)) < 0) { - closesocket(sock->sock); - sock->sock = INVALID_SOCKET; - return connect_strerror(); - } - /* go ahead and try by hostname resolution */ - } else { - struct hostent *hp = gethostbyname(address); - struct in_addr **addr; - if (!hp) return host_strerror(); - addr = (struct in_addr **) hp->h_addr_list; - for (; *addr != NULL; addr++) { - memcpy(&remote.sin_addr, *addr, sizeof(struct in_addr)); - remote.sin_family = AF_INET; - remote.sin_port = htons(port); - sock->sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock->sock == INVALID_SOCKET) return socket_strerror(); - if (connect(sock->sock, (SA *) &remote, sizeof(remote)) == 0) - break; - closesocket(sock->sock); - sock->sock = INVALID_SOCKET; - memset(&remote, 0, sizeof(remote)); - } - } - if (sock->sock == INVALID_SOCKET) return connect_strerror(); - return NULL; -} - -/*-------------------------------------------------------------------------*\ -* Sets the SO_REUSEADDR socket option -* Input -* sock: socket to set option -\*-------------------------------------------------------------------------*/ -void set_reuseaddr(p_sock sock) -{ - int val = 1; - setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)); -} - -/*-------------------------------------------------------------------------*\ -* Set socket options from a table on top of Lua stack. -* Supports SO_KEEPALIVE, SO_DONTROUTE, SO_BROADCAST, and SO_LINGER options. -* Input -* L: Lua state to use -* sock: socket to set option -* Returns -* 1 if successful, 0 otherwise -\*-------------------------------------------------------------------------*/ -static int set_option(lua_State *L, p_sock sock) -{ - static const char *const optionnames[] = { - "SO_KEEPALIVE", "SO_DONTROUTE", "SO_BROADCAST", "SO_LINGER", NULL - }; - const char *option; - int err; - if (!lua_isstring(L, -2)) return 0; - option = lua_tostring(L, -2); - switch (luaL_findstring(option, optionnames)) { - case 0: { - int bool; - if (!lua_isnumber(L, -1)) - lua_error(L, "invalid SO_KEEPALIVE value"); - bool = (int) lua_tonumber(L, -1); - err = setsockopt(sock->sock, SOL_SOCKET, SO_KEEPALIVE, - (char *) &bool, sizeof(bool)); - return err >= 0; - } - case 1: { - int bool; - if (!lua_isnumber(L, -1)) - lua_error(L, "invalid SO_DONTROUTE value"); - bool = (int) lua_tonumber(L, -1); - err = setsockopt(sock->sock, SOL_SOCKET, SO_DONTROUTE, - (char *) &bool, sizeof(bool)); - return err >= 0; - } - case 2: { - int bool; - if (!lua_isnumber(L, -1)) - lua_error(L, "invalid SO_BROADCAST value"); - bool = (int) lua_tonumber(L, -1); - err = setsockopt(sock->sock, SOL_SOCKET, SO_BROADCAST, - (char *) &bool, sizeof(bool)); - return err >= 0; - } - case 3: { - struct linger linger; - if (!lua_istable(L, -1)) - lua_error(L, "invalid SO_LINGER value"); - lua_pushstring(L, "l_onoff"); - lua_gettable(L, -2); - if (!lua_isnumber(L, -1)) - lua_error(L, "invalid SO_LINGER (l_onoff) value"); - linger.l_onoff = (int) lua_tonumber(L, -1); - lua_pop(L, 1); - lua_pushstring(L, "l_linger"); - lua_gettable(L, -2); - if (!lua_isnumber(L, -1)) - lua_error(L, "invalid SO_LINGER (l_linger) value"); - linger.l_linger = (int) lua_tonumber(L, -1); - lua_pop(L, 1); - err = setsockopt(sock->sock, SOL_SOCKET, SO_LINGER, - (char *) &linger, sizeof(linger)); - return err >= 0; - } - default: return 0; - } -} - - -/*-------------------------------------------------------------------------*\ -* Tries to create a TCP socket and bind it to (address, port) -* Input -* address: host name or ip address -* port: port number to bind to -* backlog: backlog to set -* Returns -* NULL in case of success, error message otherwise -\*-------------------------------------------------------------------------*/ -static const char *tcp_trybind(p_sock sock, const char *address, - unsigned short port, int backlog) -{ - struct sockaddr_in local; - memset(&local, 0, sizeof(local)); - local.sin_port = htons(port); - local.sin_family = AF_INET; - local.sin_addr.s_addr = htonl(INADDR_ANY); - sock->sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock->sock == INVALID_SOCKET) return socket_strerror(); - set_reuseaddr(sock); - /* address is either wildcard or a valid ip address */ - if (!strcmp(address, "*") || inet_aton(address, &local.sin_addr)) { - if (bind(sock->sock, (SA *) &local, sizeof(local)) < 0) { - closesocket(sock->sock); - sock->sock = INVALID_SOCKET; - return bind_strerror(); - } - /* otherwise, proceed with domain name resolution */ - } else { - struct hostent *hp = gethostbyname(address); - struct in_addr **addr; - if (!hp) return host_strerror(); - addr = (struct in_addr **) hp->h_addr_list; - for (; *addr != NULL; addr++) { - memcpy(&local.sin_addr, *addr, sizeof(struct in_addr)); - if (bind(sock->sock, (SA *) &local, sizeof(local)) < 0) { - closesocket(sock->sock); - sock->sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock->sock == INVALID_SOCKET) return socket_strerror(); - set_reuseaddr(sock); - } else break; - } - if (*addr == NULL) return bind_strerror(); - } - /* set connection queue length */ - if (listen(sock->sock, backlog) < 0) { - closesocket(sock->sock); - sock->sock = INVALID_SOCKET; - return "listen error"; - } - /* no errors found */ - return NULL; -} - -/*-------------------------------------------------------------------------*\ -* Tries to bind the UDP socket to (address, port) -* Input -* address: host name or ip address -* port: port number to bind to -* Returns -* NULL in case of success, error message otherwise -\*-------------------------------------------------------------------------*/ -static const char *udp_setsockname(p_sock sock, const char *address, - unsigned short port) -{ - struct sockaddr_in local; - memset(&local, 0, sizeof(local)); - local.sin_port = htons(port); - local.sin_family = AF_INET; - local.sin_addr.s_addr = htonl(INADDR_ANY); - set_reuseaddr(sock); - /* address is either wildcard or a valid ip address */ - if (!strcmp(address, "*") || inet_aton(address, &local.sin_addr)) { - if (bind(sock->sock, (SA *) &local, sizeof(local)) < 0) { - closesocket(sock->sock); - sock->sock = INVALID_SOCKET; - return bind_strerror(); - } - /* otherwise, proceed with domain name resolution */ - } else { - struct hostent *hp = gethostbyname(address); - struct in_addr **addr; - if (!hp) return host_strerror(); - addr = (struct in_addr **) hp->h_addr_list; - for (; *addr != NULL; addr++) { - memcpy(&local.sin_addr, *addr, sizeof(struct in_addr)); - if (bind(sock->sock, (SA *) &local, sizeof(local)) < 0) { - closesocket(sock->sock); - sock->sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock->sock == INVALID_SOCKET) return socket_strerror(); - set_reuseaddr(sock); - } else break; - } - if (*addr == NULL) return bind_strerror(); - } - /* no errors found */ - return NULL; -} - -/*-------------------------------------------------------------------------*\ -* Tries to connect a UDP to remote address (address, port) -* Input -* address: host name or ip address -* port: port number to bind to -* Returns -* NULL in case of success, error message otherwise -\*-------------------------------------------------------------------------*/ -static const char *udp_setpeername(p_sock sock, const char *address, - unsigned short port) -{ - struct sockaddr_in local; - memset(&local, 0, sizeof(local)); - local.sin_port = htons(port); - local.sin_family = AF_INET; - local.sin_addr.s_addr = htonl(INADDR_ANY); - /* address is a valid ip address */ - if (inet_aton(address, &local.sin_addr)) { - if (connect(sock->sock, (SA *) &local, sizeof(local)) < 0) { - closesocket(sock->sock); - sock->sock = INVALID_SOCKET; - return connect_strerror(); - } - /* otherwise, proceed with domain name resolution */ - } else { - struct hostent *hp = gethostbyname(address); - struct in_addr **addr; - if (!hp) return host_strerror(); - addr = (struct in_addr **) hp->h_addr_list; - for (; *addr != NULL; addr++) { - memcpy(&local.sin_addr, *addr, sizeof(struct in_addr)); - if (connect(sock->sock, (SA *) &local, sizeof(local)) < 0) { - closesocket(sock->sock); - sock->sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock->sock == INVALID_SOCKET) return socket_strerror(); - } else break; - } - if (*addr == NULL) return connect_strerror(); - } - /* no errors found */ - return NULL; -} - -/*=========================================================================*\ -* Timeout management functions -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Determines how much time we have left for the current io operation -* an IO write operation. -* Input -* sock: socket structure being used in operation -* Returns -* the number of ms left or -1 if there is no time limit -\*-------------------------------------------------------------------------*/ -static int tm_gettimeleft(p_sock sock) -{ - /* no timeout */ - if (sock->tm_block < 0 && sock->tm_return < 0) - return -1; - /* there is no block timeout, we use the return timeout */ - else if (sock->tm_block < 0) - return MAX(sock->tm_return - tm_gettime() + sock->tm_start, 0); - /* there is no return timeout, we use the block timeout */ - else if (sock->tm_return < 0) - return sock->tm_block; - /* both timeouts are specified */ - else return MIN(sock->tm_block, - MAX(sock->tm_return - tm_gettime() + sock->tm_start, 0)); -} - -/*-------------------------------------------------------------------------*\ -* Determines if we have a timeout condition or if we can proceed with -* an IO operation. -* Input -* sock: socket structure being used in operation -* mode: TM_RECEIVE or TM_SEND -* Returns -* 1 if we can proceed, 0 if a timeout has occured -\*-------------------------------------------------------------------------*/ -static int tm_timedout(p_sock sock, int mode) -{ - fd_set fds; - int ret; - fd_set *preadfds = NULL, *pwritefds = NULL; - struct timeval tm; - struct timeval *ptm = NULL; - /* find out how much time we have left, in ms */ - int ms = tm_gettimeleft(sock); - /* fill file descriptor set */ - FD_ZERO(&fds); FD_SET(sock->sock, &fds); - /* fill timeval structure */ - tm.tv_sec = ms / 1000; - tm.tv_usec = (ms % 1000) * 1000; - /* define function parameters */ - if (ms >= 0) ptm = &tm; /* ptm == NULL when we don't have timeout */ - if (mode == TM_RECEIVE) preadfds = &fds; - else pwritefds = &fds; - /* see if we can read, write or if we timedout */ - ret = select(sock->sock+1, preadfds, pwritefds, NULL, ptm); -#ifdef _DEBUG - /* store end time for this operation next call to OS */ - sock->tm_end = tm_gettime(); -#endif - return ret <= 0; -} - -/*-------------------------------------------------------------------------*\ -* Marks the operation start time in sock structure -* Input -* sock: socket structure being used in operation -\*-------------------------------------------------------------------------*/ -static void tm_markstart(p_sock sock) -{ - sock->tm_start = tm_gettime(); -#ifdef _DEBUG - sock->tm_end = sock->tm_start; -#endif -} - -/*-------------------------------------------------------------------------*\ -* Gets time in ms, relative to system startup. -* Returns -* time in ms. -\*-------------------------------------------------------------------------*/ -static int tm_gettime(void) -{ -#ifdef WIN32 - return GetTickCount(); -#else - struct tms t; - return (times(&t)*1000)/CLK_TCK; -#endif -} - -/*=========================================================================*\ -* Buffered I/O management functions -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Determines of there is any data in the read buffer -* Input -* sock: socket structure being used in operation -* Returns -* 1 if empty, 0 if there is data -\*-------------------------------------------------------------------------*/ -static int bf_isempty(p_sock sock) -{ - return sock->bf_first >= sock->bf_last; -} - -/*-------------------------------------------------------------------------*\ -* Skip a given number of bytes in read buffer -* Input -* sock: socket structure being used in operation -* length: number of bytes to skip -\*-------------------------------------------------------------------------*/ -static void bf_skip(p_sock sock, int length) -{ - sock->bf_first += length; - if (bf_isempty(sock)) sock->bf_first = sock->bf_last = 0; -} - -/*-------------------------------------------------------------------------*\ -* Return any data avilable in buffer, or get more data from transport layer -* if buffer is empty. -* Input -* sock: socket structure being used in operation -* Output -* length: number of bytes available in buffer -* Returns -* pointer to start of data -\*-------------------------------------------------------------------------*/ -static const unsigned char *bf_receive(p_sock sock, int *length) -{ - if (bf_isempty(sock)) { - int got = recv(sock->sock, sock->bf_buffer, LUASOCKET_TCPBUFFERSIZE, 0); - sock->bf_first = 0; - if (got >= 0) sock->bf_last = got; - else sock->bf_last = 0; - } - *length = sock->bf_last - sock->bf_first; - return sock->bf_buffer + sock->bf_first; -} - -/*=========================================================================*\ -* These are the function that are called for each I/O pattern -* The read patterns leave their result on the Lua stack -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Sends a raw block of data through a socket. The operations are all -* non-blocking and the function respects the timeout values in sock. -* Input -* sock: socket structure being used in operation -* data: buffer to be sent -* wanted: number of bytes in buffer -* Output -* total: Number of bytes written -* Returns -* operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED -\*-------------------------------------------------------------------------*/ -static int send_raw(p_sock sock, const char *data, int wanted, int *total) -{ - int put = 0; - *total = 0; - while (wanted > 0) { - if (tm_timedout(sock, TM_SEND)) return NET_TIMEOUT; - put = send(sock->sock, data, wanted, 0); - if (put <= 0) { -#ifdef WIN32 - /* a bug in WinSock forces us to do a busy wait until we manage - ** to write, because select returns immediately even though it - ** should have blocked us until we could write... */ - if (put < 0 && WSAGetLastError() == WSAEWOULDBLOCK) - continue; -#endif -#ifdef __CYGWIN__ - /* this is for CYGWIN, which is like Unix but with Win32 Bugs */ - if (put < 0 && errno == EWOULDBLOCK) - continue; -#endif - - return NET_CLOSED; - } - wanted -= put; - data += put; - *total += put; - } - return NET_DONE; -} - -/*-------------------------------------------------------------------------*\ -* Reads a raw block of data from a socket. The operations are all -* non-blocking and the function respects the timeout values in sock. -* Input -* sock: socket structure being used in operation -* wanted: number of bytes to be read -* Returns -* operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED -\*-------------------------------------------------------------------------*/ -static int receive_raw(lua_State *L, p_sock sock, int wanted) -{ - int got = 0; - const unsigned char *buffer = NULL; - luaL_Buffer b; - luaL_buffinit(L, &b); - while (wanted > 0) { - if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { - luaL_pushresult(&b); - return NET_TIMEOUT; - } - buffer = bf_receive(sock, &got); - if (got <= 0) { - luaL_pushresult(&b); - return NET_CLOSED; - } - got = MIN(got, wanted); - luaL_addlstring(&b, buffer, got); - bf_skip(sock, got); - wanted -= got; - } - luaL_pushresult(&b); - return NET_DONE; -} - -/*-------------------------------------------------------------------------*\ -* Reads everything until the connection is closed -* Input -* sock: socket structure being used in operation -* Result -* operation error code. NET_DONE, NET_TIMEOUT -\*-------------------------------------------------------------------------*/ -static int receive_all(lua_State *L, p_sock sock) -{ - int got = 0; - const unsigned char *buffer = NULL; - luaL_Buffer b; - luaL_buffinit(L, &b); - for ( ;; ) { - if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { - luaL_pushresult(&b); - return NET_TIMEOUT; - } - buffer = bf_receive(sock, &got); - if (got <= 0) { - luaL_pushresult(&b); - return NET_DONE; - } - luaL_addlstring(&b, buffer, got); - bf_skip(sock, got); - } -} - -/*-------------------------------------------------------------------------*\ -* Reads a line terminated by a CR LF pair or just by a LF. The CR and LF -* are not returned by the function and are discarded from the stream. All -* operations are non-blocking and the function respects the timeout -* values in sock. -* Input -* sock: socket structure being used in operation -* Result -* operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED -\*-------------------------------------------------------------------------*/ -static int receive_dosline(lua_State *L, p_sock sock) -{ - int got, pos; - const unsigned char *buffer = NULL; - luaL_Buffer b; - luaL_buffinit(L, &b); - for ( ;; ) { - if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { - luaL_pushresult(&b); - return NET_TIMEOUT; - } - buffer = bf_receive(sock, &got); - if (got <= 0) { - luaL_pushresult(&b); - return NET_CLOSED; - } - pos = 0; - while (pos < got && buffer[pos] != '\n') { - /* we ignore all \r's */ - if (buffer[pos] != '\r') luaL_putchar(&b, buffer[pos]); - pos++; - } - if (pos < got) { - luaL_pushresult(&b); - bf_skip(sock, pos+1); /* skip '\n' too */ - return NET_DONE; - } else bf_skip(sock, pos); - } -} - -/*-------------------------------------------------------------------------*\ -* Reads a line terminated by a LF character, which is not returned by -* the function, and is skipped in the stream. All operations are -* non-blocking and the function respects the timeout values in sock. -* Input -* sock: socket structure being used in operation -* Returns -* operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED -\*-------------------------------------------------------------------------*/ -static int receive_unixline(lua_State *L, p_sock sock) -{ - int got, pos; - const unsigned char *buffer = NULL; - luaL_Buffer b; - luaL_buffinit(L, &b); - for ( ;; ) { - if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { - luaL_pushresult(&b); - return NET_TIMEOUT; - } - buffer = bf_receive(sock, &got); - if (got <= 0) { - luaL_pushresult(&b); - return NET_CLOSED; - } - pos = 0; - while (pos < got && buffer[pos] != '\n') pos++; - luaL_addlstring(&b, buffer, pos); - if (pos < got) { - luaL_pushresult(&b); - bf_skip(sock, pos+1); /* skip '\n' too */ - return NET_DONE; - } else bf_skip(sock, pos); - } -} - -/*-------------------------------------------------------------------------*\ -* Reads a word (maximal sequence of non--white-space characters), skipping -* white-spaces if needed. -* Input -* sock: socket structure being used in operation -* Result -* operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED -\*-------------------------------------------------------------------------*/ -static int receive_word(lua_State *L, p_sock sock) -{ - int pos, got; - const unsigned char *buffer = NULL; - luaL_Buffer b; - luaL_buffinit(L, &b); - /* skip leading white-spaces */ - for ( ;; ) { - if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { - lua_pushstring(L, ""); - return NET_TIMEOUT; - } - buffer = bf_receive(sock, &got); - if (got <= 0) { - lua_pushstring(L, ""); - return NET_CLOSED; - } - pos = 0; - while (pos < got && isspace(buffer[pos])) pos++; - bf_skip(sock, pos); - if (pos < got) { - buffer += pos; - got -= pos; - pos = 0; - break; - } - } - /* capture word */ - for ( ;; ) { - while (pos < got && !isspace(buffer[pos])) pos++; - luaL_addlstring(&b, buffer, pos); - bf_skip(sock, pos); - if (pos < got) { - luaL_pushresult(&b); - return NET_DONE; - } - if (bf_isempty(sock) && tm_timedout(sock, TM_RECEIVE)) { - luaL_pushresult(&b); - return NET_TIMEOUT; - } - buffer = bf_receive(sock, &got); - if (got <= 0) { - luaL_pushresult(&b); - return NET_CLOSED; - } - pos = 0; - } -} - -/*=========================================================================*\ -* Module exported functions -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Initializes the library interface with Lua and the socket library. -* Defines the symbols exported to Lua. +* Initializes all library modules. \*-------------------------------------------------------------------------*/ LUASOCKET_API int lua_socketlibopen(lua_State *L) { - struct luaL_reg funcs[] = { - {"bind", global_tcpbind}, - {"connect", global_tcpconnect}, - {"select", global_select}, - {"toip", global_toip}, - {"tohostname", global_tohostname}, - {"udpsocket", global_udpsocket}, - }; - unsigned int i; - /* declare new Lua tags for used userdata values */ - p_tags tags = (p_tags) lua_newuserdata(L, sizeof(t_tags)); - tags->client = lua_newtag(L); - tags->server = lua_newtag(L); - tags->table = lua_newtag(L); - tags->udp = lua_newtag(L); - /* global functions exported */ - for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { - lua_pushvalue(L, -1); - lua_pushcclosure(L, funcs[i].func, 1); - lua_setglobal(L, funcs[i].name); - } - /* socket garbage collection */ - lua_pushvalue(L, -1); - lua_pushcclosure(L, gc_table, 1); - lua_settagmethod(L, tags->table, "gc"); -#ifdef WIN32 - /* WinSock needs special initialization */ - if (!winsock_open()) return 0; -#else - /* avoid getting killed by a SIGPIPE signal thrown by send */ - handle_sigpipe(); -#endif -#ifdef _DEBUG - /* test support functions */ - lua_pushcfunction(L, global_sleep); lua_setglobal(L, "_sleep"); - lua_pushcfunction(L, global_time); lua_setglobal(L, "_time"); -#endif -#ifndef LUASOCKET_NOGLOBALS - { - char *global[] = { - "accept", "close", "getpeername", - "getsockname", "receive", "send", - "receivefrom", "sendto" - }; - unsigned int i; - for (i = 0; i < sizeof(global)/sizeof(char *); i++) { - lua_pushstring(L, global[i]); - lua_pushvalue(L, -2); - lua_pushcclosure(L, global_callfromtable, 2); - lua_setglobal(L, global[i]); - } - } -#endif - /* remove tags userdatum from stack */ - lua_pop(L, 1); + priv_open(L); + select_open(L); + base_open(L); + tm_open(L); + compat_open(L); + fd_open(L); + sock_open(L); + inet_open(L); + tcpc_open(L); + buf_open(L); + tcps_open(L); + udp_open(L); return 1; } - -/*=========================================================================*\ -* Lua Stack manipulation functions -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Creates a t_sock structure with default values for a client sock. -* Pushes the Lua table with sock fields and appropriate methods -* Input -* tags: tags structure -* Returns -* pointer to allocated t_sock structure, NULL in case of error -\*-------------------------------------------------------------------------*/ -static p_sock push_clienttable(lua_State *L, p_tags tags) -{ - static struct luaL_reg funcs[] = { - {"close", table_close}, - {"getsockname", table_getsockname}, - {"getpeername", table_getpeername}, - {"receive", table_tcpreceive}, - {"send", table_tcpsend}, - {"timeout", table_timeout}, - }; - unsigned int i; - p_sock sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); - lua_settag(L, tags->client); - lua_newtable(L); lua_settag(L, tags->table); - lua_pushstring(L, P_SOCK); - lua_pushvalue(L, -3); - lua_settable(L, -3); - sock->sock = INVALID_SOCKET; - sock->is_connected = 0; - sock->tm_block = -1; - sock->tm_return = -1; - sock->bf_first = sock->bf_last = 0; - for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { - lua_pushstring(L, funcs[i].name); - lua_pushvalue(L, -3); - lua_pushcclosure(L, funcs[i].func, 1); - lua_settable(L, -3); - } - return sock; -} - -/*-------------------------------------------------------------------------*\ -* Creates a t_sock structure with default values for a server sock. -* Pushes the Lua table with sock fields and appropriate methods -* Input -* tags: tags structure -* Returns -* pointer to allocated t_sock structure, NULL in case of error -\*-------------------------------------------------------------------------*/ -static p_sock push_servertable(lua_State *L, p_tags tags) -{ - static struct luaL_reg funcs[] = { - {"close", table_close}, - {"getsockname", table_getsockname}, - {"timeout", table_timeout}, - }; - unsigned int i; - p_sock sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); - lua_settag(L, tags->server); - lua_newtable(L); lua_settag(L, tags->table); - lua_pushstring(L, P_SOCK); - lua_pushvalue(L, -3); - lua_settable(L, -3); - sock->sock = INVALID_SOCKET; - sock->tm_block = -1; - sock->tm_return = -1; - sock->bf_first = sock->bf_last = 0; - for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { - lua_pushstring(L, funcs[i].name); - lua_pushvalue(L, -3); - lua_pushcclosure(L, funcs[i].func, 1); - lua_settable(L, -3); - } - /* the accept method is different, it needs the tags closure too */ - lua_pushstring(L, "accept"); -#ifdef LUASOCKET_41FRIENDLY - lua_newuserdatabox(L, tags); -#else - lua_pushuserdata(L, tags); -#endif - lua_pushvalue(L, -4); - lua_pushcclosure(L, table_tcpaccept, 2); - lua_settable(L, -3); - return sock; -} - -/*-------------------------------------------------------------------------*\ -* Creates a t_sock structure with default values for a udp sock. -* Pushes the Lua table with sock fields and appropriate methods -* Input -* tags: tags structure -* Returns -* pointer to allocated t_sock structure, NULL in case of error -\*-------------------------------------------------------------------------*/ -static p_sock push_udptable(lua_State *L, p_tags tags) -{ - static struct luaL_reg funcs[] = { - {"sendto", table_udpsendto}, - {"setpeername", table_udpsetpeername}, - {"setsockname", table_udpsetsockname}, - {"getpeername", table_getpeername}, - {"getsockname", table_getsockname}, - {"receivefrom", table_udpreceivefrom}, - {"receive", table_udpreceive}, - {"send", table_udpsend}, - {"close", table_close}, - {"timeout", table_timeout}, - }; - unsigned int i; - p_sock sock = (p_sock) lua_newuserdata(L, sizeof(t_sock)); - lua_settag(L, tags->udp); - lua_newtable(L); lua_settag(L, tags->table); - lua_pushstring(L, P_SOCK); - lua_pushvalue(L, -3); - lua_settable(L, -3); - sock->sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock->sock == INVALID_SOCKET) { - lua_pushnil(L); - lua_pushstring(L, socket_strerror()); - return NULL; - } - sock->is_connected = 0; - sock->tm_block = -1; - sock->tm_return = -1; - sock->bf_first = sock->bf_last = 0; - for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { - lua_pushstring(L, funcs[i].name); - lua_pushvalue(L, -3); - lua_pushcclosure(L, funcs[i].func, 1); - lua_settable(L, -3); - } - return sock; -} - -/*-------------------------------------------------------------------------*\ -* Passes all resolver information to Lua as a table -* Input -* hp: hostent structure returned by resolver -\*-------------------------------------------------------------------------*/ -static void push_resolved(lua_State *L, struct hostent *hp) -{ - char **alias; - struct in_addr **addr; - int i, resolved; - - lua_newtable(L); resolved = lua_gettop(L); - - lua_pushstring(L, "name"); - lua_pushstring(L, hp->h_name); - lua_settable(L, resolved); - - lua_pushstring(L, "ip"); - lua_pushstring(L, "alias"); - - i = 1; - alias = hp->h_aliases; - lua_newtable(L); - while (*alias) { - lua_pushnumber(L, i); - lua_pushstring(L, *alias); - lua_settable(L, -3); - i++; alias++; - } - lua_settable(L, resolved); - - i = 1; - lua_newtable(L); - addr = (struct in_addr **) hp->h_addr_list; - while (*addr) { - lua_pushnumber(L, i); - lua_pushstring(L, inet_ntoa(**addr)); - lua_settable(L, -3); - i++; addr++; - } - lua_settable(L, resolved); -} - -/*-------------------------------------------------------------------------*\ -* Passes an error code to Lua. The NET_DONE error is translated to nil. -* Input -* err: error code to be passed to Lua -\*-------------------------------------------------------------------------*/ -static void push_error(lua_State *L, int err) -{ - switch (err) { - case NET_DONE: - lua_pushnil(L); - break; - case NET_TIMEOUT: - lua_pushstring(L, "timeout"); - break; - case NET_CLOSED: - lua_pushstring(L, "closed"); - break; - case NET_REFUSED: - lua_pushstring(L, "refused"); - break; - } -} - -static p_tags pop_tags(lua_State *L) -{ - p_tags tags = (p_tags) lua_touserdata(L, -1); - if (!tags) lua_error(L, "invalid closure! (probably misuse of library)"); - lua_pop(L, 1); - return tags; -} - -static p_sock pop_sock(lua_State *L) -{ - p_sock sock = (p_sock) lua_touserdata(L, -1); - if (!sock) lua_error(L, "invalid socket object"); - if (sock->sock == INVALID_SOCKET) - lua_error(L, "operation on closed socket"); - lua_pop(L, 1); - return sock; -} - -static p_sock get_sock(lua_State *L, int s, p_tags tags, int *tag) -{ - p_sock sock; - if (lua_tag(L, s) != tags->table) lua_error(L, "invalid socket object"); - lua_pushstring(L, P_SOCK); - lua_gettable(L, s > 0 ? s : s-1); - sock = lua_touserdata(L, -1); - if (!sock) lua_error(L, "invalid socket object"); - if (tag) *tag = lua_tag(L, -1); - lua_pop(L, 1); - return sock; -} - -static p_sock get_selfsock(lua_State *L, p_tags tags, int *tag) -{ - return get_sock(L, 1, tags, tag); -} - -/*=========================================================================*\ -* WinSock2 specific functions. -\*=========================================================================*/ -#ifdef WIN32 -/*-------------------------------------------------------------------------*\ -* Initializes WinSock2 library. -* Returns -* 1 in case of success. 0 in case of error. -\*-------------------------------------------------------------------------*/ -static int winsock_open(void) -{ - WORD wVersionRequested; - WSADATA wsaData; - int err; - wVersionRequested = MAKEWORD(2, 0); - err = WSAStartup(wVersionRequested, &wsaData ); - if (err != 0) { - return 0; - } - if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0) { - WSACleanup(); - return 0; - } - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Put socket into blocking mode. -\*-------------------------------------------------------------------------*/ -static void set_blocking(p_sock sock) -{ - u_long argp = 0; - ioctlsocket(sock->sock, FIONBIO, &argp); -} - -/*-------------------------------------------------------------------------*\ -* Put socket into non-blocking mode. -\*-------------------------------------------------------------------------*/ -static void set_nonblocking(p_sock sock) -{ - u_long argp = 1; - ioctlsocket(sock->sock, FIONBIO, &argp); -} - -/*-------------------------------------------------------------------------*\ -* Returns a string describing the last host manipulation error. -\*-------------------------------------------------------------------------*/ -static char *host_strerror(void) -{ - switch (WSAGetLastError()) { - case HOST_NOT_FOUND: return "host not found"; - case NO_ADDRESS: return "unable to resolve host name"; - case NO_RECOVERY: return "name server error"; - case TRY_AGAIN: return "name server unavailable, try again later."; - default: return "unknown error"; - } -} - -/*-------------------------------------------------------------------------*\ -* Returns a string describing the last socket manipulation error. -\*-------------------------------------------------------------------------*/ -static char *socket_strerror(void) -{ - switch (WSAGetLastError()) { - case WSANOTINITIALISED: return "not initialized"; - case WSAENETDOWN: return "network is down"; - case WSAEMFILE: return "descriptor table is full"; - case WSAENOBUFS: return "insufficient buffer space"; - default: return "unknown error"; - } -} - -/*-------------------------------------------------------------------------*\ -* Returns a string describing the last bind operation error. -\*-------------------------------------------------------------------------*/ -static char *bind_strerror(void) -{ - switch (WSAGetLastError()) { - case WSANOTINITIALISED: return "not initialized"; - case WSAENETDOWN: return "network is down"; - case WSAEADDRINUSE: return "address already in use"; - case WSAEINVAL: return "socket already bound"; - case WSAENOBUFS: return "too many connections"; - case WSAEFAULT: return "invalid address"; - case WSAENOTSOCK: return "not a socket descriptor"; - default: return "unknown error"; - } -} - -/*-------------------------------------------------------------------------*\ -* Returns a string describing the last connect operationerror. -\*-------------------------------------------------------------------------*/ -static char *connect_strerror(void) -{ - switch (WSAGetLastError()) { - case WSANOTINITIALISED: return "not initialized"; - case WSAENETDOWN: return "network is down"; - case WSAEADDRINUSE: return "address already in use"; - case WSAEADDRNOTAVAIL: return "address unavailable"; - case WSAECONNREFUSED: return "connection refused"; - case WSAENETUNREACH: return "network is unreachable"; - default: return "unknown error"; - } -} -#else - -/*=========================================================================*\ -* BSD specific functions. -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Put socket into blocking mode. -\*-------------------------------------------------------------------------*/ -static void set_blocking(p_sock sock) -{ - int flags = fcntl(sock->sock, F_GETFL, 0); - flags &= (~(O_NONBLOCK)); - fcntl(sock->sock, F_SETFL, flags); -} - -/*-------------------------------------------------------------------------*\ -* Put socket into non-blocking mode. -\*-------------------------------------------------------------------------*/ -static void set_nonblocking(p_sock sock) -{ - int flags = fcntl(sock->sock, F_GETFL, 0); - flags |= O_NONBLOCK; - fcntl(sock->sock, F_SETFL, flags); -} - -/*-------------------------------------------------------------------------*\ -* Returns a string describing the last host manipulation error. -\*-------------------------------------------------------------------------*/ -static char *host_strerror(void) -{ - switch (h_errno) { - case HOST_NOT_FOUND: return "host not found"; - case NO_ADDRESS: return "unable to resolve host name"; - case NO_RECOVERY: return "name server error"; - case TRY_AGAIN: return "name server unavailable, try again later"; - default: return "unknown error"; - } -} - -/*-------------------------------------------------------------------------*\ -* Returns a string describing the last socket manipulation error. -\*-------------------------------------------------------------------------*/ -static char *socket_strerror(void) -{ - switch (errno) { - case EACCES: return "access denied"; - case EMFILE: return "descriptor table is full"; - case ENFILE: return "too many open files"; - case ENOBUFS: return "insuffucient buffer space"; - default: return "unknown error"; - } -} - -/*-------------------------------------------------------------------------*\ -* Returns a string describing the last bind command error. -\*-------------------------------------------------------------------------*/ -static char *bind_strerror(void) -{ - switch (errno) { - case EBADF: return "invalid descriptor"; - case EINVAL: return "socket already bound"; - case EACCES: return "access denied"; - case ENOTSOCK: return "not a socket descriptor"; - case EADDRINUSE: return "address already in use"; - case EADDRNOTAVAIL: return "address unavailable"; - case ENOMEM: return "out of memory"; - default: return "unknown error"; - } -} - -/*-------------------------------------------------------------------------*\ -* Returns a string describing the last connect error. -\*-------------------------------------------------------------------------*/ -static char *connect_strerror(void) -{ - switch (errno) { - case EBADF: return "invalid descriptor"; - case ENOTSOCK: return "not a socket descriptor"; - case EADDRNOTAVAIL: return "address not availabe"; - case ETIMEDOUT: return "connection timed out"; - case ECONNREFUSED: return "connection refused"; - case EACCES: return "access denied"; - case ENETUNREACH: return "network is unreachable"; - case EADDRINUSE: return "address already in use"; - default: return "unknown error"; - } -} -#endif - -/*-------------------------------------------------------------------------*\ -* Some systems do not provide this so that we provide our own. It's not -* marvelously fast, but it works just fine. -\*-------------------------------------------------------------------------*/ -#ifdef LUASOCKET_ATON -static int inet_aton(const char *cp, struct in_addr *inp) -{ - unsigned int a = 0, b = 0, c = 0, d = 0; - int n = 0, r; - unsigned long int addr = 0; - r = sscanf(cp, "%u.%u.%u.%u%n", &a, &b, &c, &d, &n); - if (r == 0 || n == 0) return 0; - cp += n; - if (*cp) return 0; - if (a > 255 || b > 255 || c > 255 || d > 255) return 0; - if (inp) { - addr += a; addr <<= 8; - addr += b; addr <<= 8; - addr += c; addr <<= 8; - addr += d; - inp->s_addr = htonl(addr); - } - return 1; -} -#endif - From 9b8bce6465d2c7de84782f9e12f529a020a16444 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 3 Jul 2002 19:06:53 +0000 Subject: [PATCH 094/483] select implementation. --- src/select.c | 248 ++++++++++++++++++++------------------------------- 1 file changed, 95 insertions(+), 153 deletions(-) diff --git a/src/select.c b/src/select.c index 1f83b7a..9a24dbb 100644 --- a/src/select.c +++ b/src/select.c @@ -1,39 +1,43 @@ +#include +#include "lspriv.h" +#include "lsselect.h" +#include "lsfd.h" + +/* auxiliar functions */ +static int local_select(lua_State *L); +static int local_getfd(lua_State *L); +static int local_pending(lua_State *L); +static int local_FD_SET(lua_State *L); +static int local_FD_ISSET(lua_State *L); + +static int select_lua_select(lua_State *L); + /*-------------------------------------------------------------------------*\ * Marks type as selectable * Input * name: type name \*-------------------------------------------------------------------------*/ -void slct_addclass(lua_State *L, cchar *lsclass) +void select_addclass(lua_State *L, cchar *lsclass) { - lua_pushstring(L, "selectable sockets"); + lua_pushstring(L, "luasocket(select)"); lua_gettable(L, LUA_REGISTRYINDEX); lua_pushstring(L, lsclass); lua_pushnumber(L, 1); lua_settable(L, -3); - lua_pop(L, 2); + lua_pop(L, 1); } -/*-------------------------------------------------------------------------*\ -* Gets a pointer to a socket structure from a userdata -* Input -* pos: userdata stack index -* Returns -* pointer to structure, or NULL if invalid type -\*-------------------------------------------------------------------------*/ -static p_sock ls_toselectable(lua_State *L) +void select_open(lua_State *L) { - lua_getregistry(L); - lua_pushstring(L, "sock(selectable)"); - lua_gettable(L, -2); - lua_pushstring(L, lua_type(L, -3)); - lua_gettable(L, -2); - if (lua_isnil(L, -1)) { - lua_pop(L, 3); - return NULL; - } else { - lua_pop(L, 3); - return (p_sock) lua_touserdata(L, -1); - } + /* push select auxiliar lua function and register + * select_lua_select with it as an upvalue */ +#include "lsselect.loh" + lua_pushcclosure(L, select_lua_select, 1); + lua_setglobal(L, "select"); + /* create luasocket(select) table */ + lua_pushstring(L, "luasocket(select)"); + lua_newtable(L); + lua_settable(L, LUA_REGISTRYINDEX); } /*-------------------------------------------------------------------------*\ @@ -47,148 +51,86 @@ static p_sock ls_toselectable(lua_State *L) * {output}: table with sockets ready for output * err: "timeout" or nil \*-------------------------------------------------------------------------*/ -int global_select(lua_State *L) +static int select_lua_select(lua_State *L) { - int ms = lua_isnil(L, 3) ? -1 : (int) (luaL_opt_number(L, 3, -1) * 1000); - fd_set rfds, *prfds = NULL, wfds, *pwfds = NULL; - struct timeval tv, *ptv = NULL; - unsigned max = 0; - int byfds, readable, writable; - int toread = 1, towrite = 2; - lua_newtable(L); byfds = lua_gettop(L); /* sockets indexed by descriptor */ - lua_newtable(L); readable = lua_gettop(L); - lua_newtable(L); writable = lua_gettop(L); - /* collect sockets to be tested into FD_SET structures and fill byfds */ - if (lua_istable(L, toread)) - prfds = tab2rfds(L, toread, &rfds, &max, byfds, readable, &ms); - else if (!lua_isnil(L, toread)) - luaL_argerror(L, toread, "expected table or nil"); - if (lua_istable(L, towrite)) - pwfds = tab2wfds(L, towrite, &wfds, &max, byfds); - else if (!lua_isnil(L, towrite)) - luaL_argerror(L, towrite, "expected table or nil"); - /* fill timeval structure */ - if (ms >= 0) { - tv.tv_sec = ms / 1000; - tv.tv_usec = (ms % 1000) * 1000; - ptv = &tv; - } else ptv = NULL; /* ptv == NULL when we don't have timeout */ - /* see if we can read, write or if we timedout */ - if (select(max+1, prfds, pwfds, NULL, ptv) <= 0 && ms >= 0) { - ls_pusherror(L, LS_TIMEOUT); - return 3; - } - /* collect readable and writable sockets into result tables */ - fds2tab(L, prfds, max+1, byfds, readable); - fds2tab(L, pwfds, max+1, byfds, writable); - lua_pushnil(L); + fd_set read, write; + FD_ZERO(&read); + FD_ZERO(&write); + /* push select lua auxiliar function */ + lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, 1); + /* make sure we have enough arguments (nil is the default) */ + lua_settop(L, 4); + /* pass FD_SET and manipulation functions */ + lua_newuserdatabox(L, &read); + lua_newuserdatabox(L, &write); + lua_pushcfunction(L, local_FD_SET); + lua_pushcfunction(L, local_FD_ISSET); + /* pass getfd function with selectable table as upvalue */ + lua_pushstring(L, "luasocket(select)"); lua_gettable(L, LUA_REGISTRYINDEX); + lua_pushcclosure(L, local_getfd, 1); + /* pass pending function */ + lua_pushstring(L, "luasocket(select)"); lua_gettable(L, LUA_REGISTRYINDEX); + lua_pushcclosure(L, local_pending, 1); + /* pass select auxiliar C function */ + lua_pushcfunction(L, local_select); + /* call select auxiliar lua function */ + lua_call(L, 10, 3); return 3; } -/*=========================================================================*\ -* Select auxiliar functions -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Converts a FD_SET structure into a socket table set -* Input -* fds: pointer to FD_SET structure -* max: 1 plus the largest descriptor value in FD_SET -* byfds: table indexed by descriptor number, with corresponding socket tables -* can: table to receive corresponding socket table set -\*-------------------------------------------------------------------------*/ -static void fds2tab(lua_State *L, fd_set *fds, int max, int byfds, int can) +static int local_getfd(lua_State *L) { - int s; - if (!fds) return; - for (s = 0; s < max; s++) { - if (FD_ISSET(s, fds)) { - lua_pushnumber(L, lua_getn(L, can) + 1); - lua_pushnumber(L, s); - lua_gettable(L, byfds); - lua_settable(L, can); - } + priv_pushclass(L, 1); + lua_gettable(L, lua_upvalueindex(1)); + if (!lua_isnil(L, -1)) { + p_fd sock = (p_fd) lua_touserdata(L, 1); + lua_pushnumber(L, sock->fd); } + return 1; } -/*-------------------------------------------------------------------------*\ -* Converts a socket table set ito a FD_SET structure -* Input -* towrite: socket table set -* Output -* wfds: pointer to FD_SET structure to be filled -* max: largest descriptor value found in wfds -* byfds: table indexed by descriptor number, with corresponding socket tables -\*-------------------------------------------------------------------------*/ -static fd_set *tab2wfds(lua_State *L, int towrite, fd_set *wfds, - int *max, int byfds) +static int local_pending(lua_State *L) { - int empty = 1; - FD_ZERO(wfds); - lua_pushnil(L); - while (lua_next(L, towrite)) { - p_sock sock = ls_toselectable(L); - if (sock) { /* skip strange fields */ - NET_FD s = sock->fd; - if (s != NET_INVALIDFD) { /* skip closed sockets */ - lua_pushnumber(L, s); - lua_pushvalue(L, -2); - lua_settable(L, byfds); - if (s > *max) *max = s; - FD_SET(s, wfds); - empty = 0; - } - } - /* get rid of value and expose index */ - lua_pop(L, 1); + priv_pushclass(L, 1); + lua_gettable(L, lua_upvalueindex(1)); + if (!lua_isnil(L, -1)) { + p_fd sock = (p_fd) lua_touserdata(L, 1); + if (sock->fd_pending(L, sock)) lua_pushnumber(L, 1); + else lua_pushnil(L); } - if (empty) return NULL; - else return wfds; + return 1; } -/*-------------------------------------------------------------------------*\ -* Converts a socket table set ito a FD_SET structure -* Input -* toread: socket table set -* Output -* rfds: pointer to FD_SET structure to be filled -* max: largest descriptor value found in rfds -* byfds: table indexed by descriptor number, with corresponding socket tables -* readable: table to receive socket table if socket is obviously readable -* ms: will be zeroed if a readable socket is detected -\*-------------------------------------------------------------------------*/ -static fd_set *tab2rfds(lua_State *L, int toread, fd_set *rfds, - int *max, int byfds, int readable, int *ms) +static int local_select(lua_State *L) { - int empty = 1; - FD_ZERO(rfds); - lua_pushnil(L); - while (lua_next(L, toread)) { - p_sock sock = ls_toselectable(L); - if (sock) { /* skip strange fields */ - NET_FD s = sock->fd; - if (s != NET_INVALID) { /* skip closed sockets */ - /* a socket can have unread data in our internal buffer. we - pass them straight to the readable set, and test only to - find out which of the other sockets can be written to or - read from immediately. */ - if (sock->vt->readable(sock)) { - *ms = 0; - lua_pushnumber(L, lua_getn(L, readable) + 1); - lua_pushvalue(L, -2); - lua_settable(L, readable); - } else { - lua_pushnumber(L, s); - lua_pushvalue(L, -2); - lua_settable(L, byfds); - if (s > *max) *max = s; - FD_SET(s, rfds); - empty = 0; - } - } - } - /* get rid of value and exposed index */ - lua_pop(L, 1); + int max_fd = (int) lua_tonumber(L, 1); + fd_set *read_set = (fd_set *) lua_touserdata(L, 2); + fd_set *write_set = (fd_set *) lua_touserdata(L, 3); + int deadline = lua_isnil(L, 4) ? -1 : (int)(lua_tonumber(L, 4) * 1000); + struct timeval tv; + if (deadline >= 0) { + tv.tv_sec = deadline / 1000; + tv.tv_usec = (deadline % 1000) * 1000; + lua_pushnumber(L, select(max_fd, read_set, write_set, NULL, &tv)); + } else { + lua_pushnumber(L, select(max_fd, read_set, write_set, NULL, NULL)); } - if (empty) return NULL; - else return rfds; + return 1; +} + +static int local_FD_SET(lua_State *L) +{ + COMPAT_FD fd = (COMPAT_FD) lua_tonumber(L, 1); + fd_set *set = (fd_set *) lua_touserdata(L, 2); + if (fd >= 0) FD_SET(fd, set); + return 0; +} + +static int local_FD_ISSET(lua_State *L) +{ + COMPAT_FD fd = (COMPAT_FD) lua_tonumber(L, 1); + fd_set *set = (fd_set *) lua_touserdata(L, 2); + if (fd >= 0 && FD_ISSET(fd, set)) lua_pushnumber(L, 1); + else lua_pushnil(L); + return 1; } From 88026bef8a5ca586e354965a79134646fb566c72 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 3 Jul 2002 19:06:54 +0000 Subject: [PATCH 095/483] Initial revision --- src/inet.h | 36 +++++++ src/select.h | 7 ++ src/socket.h | 18 ++++ src/timeout.c | 160 ++++++++++++++++++++++++++++ src/timeout.h | 20 ++++ src/udp.c | 287 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/udp.h | 24 +++++ 7 files changed, 552 insertions(+) create mode 100644 src/inet.h create mode 100644 src/select.h create mode 100644 src/socket.h create mode 100644 src/timeout.c create mode 100644 src/timeout.h create mode 100644 src/udp.c create mode 100644 src/udp.h diff --git a/src/inet.h b/src/inet.h new file mode 100644 index 0000000..3b0453e --- /dev/null +++ b/src/inet.h @@ -0,0 +1,36 @@ +/*=========================================================================*\ +* Internet domain class +* RCS ID: $Id$ +\*=========================================================================*/ +#ifndef INET_H_ +#define INET_H_ + +#include +#include "lssock.h" + +/* class name */ +#define INET_CLASS "luasocket(inet)" + +/*-------------------------------------------------------------------------*\ +* Socket fields +\*-------------------------------------------------------------------------*/ +#define INET_FIELDS SOCK_FIELDS + +/*-------------------------------------------------------------------------*\ +* Socket structure +\*-------------------------------------------------------------------------*/ +typedef t_sock t_inet; +typedef t_inet *p_inet; + +/*-------------------------------------------------------------------------*\ +* Exported functions +\*-------------------------------------------------------------------------*/ +void inet_open(lua_State *L); +void inet_construct(lua_State *L, p_inet inet); +void inet_inherit(lua_State *L, cchar *lsclass); + +cchar *inet_tryconnect(p_sock sock, cchar *address, ushort); +cchar *inet_trybind(p_sock sock, cchar *address, ushort); +cchar *inet_trysocket(p_inet inet, int type); + +#endif /* INET_H_ */ diff --git a/src/select.h b/src/select.h new file mode 100644 index 0000000..c3267ad --- /dev/null +++ b/src/select.h @@ -0,0 +1,7 @@ +#ifndef SLCT_H_ +#define SLCT_H_ + +void select_addclass(lua_State *L, cchar *lsclass); +void select_open(lua_State *L); + +#endif diff --git a/src/socket.h b/src/socket.h new file mode 100644 index 0000000..c9dee20 --- /dev/null +++ b/src/socket.h @@ -0,0 +1,18 @@ +#ifndef SOCK_H_ +#define SOCK_H_ + +#include +#include "lsfd.h" + +#define SOCK_CLASS "luasocket(sock)" + +#define SOCK_FIELDS FD_FIELDS + +typedef t_fd t_sock; +typedef t_sock *p_sock; + +void sock_open(lua_State *L); +void sock_construct(lua_State *L, p_sock sock); +void sock_inherit(lua_State *L, cchar *lsclass); + +#endif /* SOCK_H_ */ diff --git a/src/timeout.c b/src/timeout.c new file mode 100644 index 0000000..266a86e --- /dev/null +++ b/src/timeout.c @@ -0,0 +1,160 @@ +/*=========================================================================*\ +* Timeout management functions +\*=========================================================================*/ +#include +#include + +#include "lspriv.h" +#include "lstm.h" + +#include + +#ifdef WIN32 +#include +#else +#include +#include +#include +#endif + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +#ifdef _DEBUG +static int tm_lua_time(lua_State *L); +static int tm_lua_sleep(lua_State *L); +#endif + +/*=========================================================================*\ +* Exported functions. +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Sets timeout limits +* Input +* tm: timeout control structure +* mode: block or return timeout +* value: timeout value in miliseconds +\*-------------------------------------------------------------------------*/ +void tm_set(p_tm tm, int tm_block, int tm_return) +{ + tm->tm_block = tm_block; + tm->tm_return = tm_return; +} + +/*-------------------------------------------------------------------------*\ +* Returns timeout limits +* Input +* tm: timeout control structure +* mode: block or return timeout +* value: timeout value in miliseconds +\*-------------------------------------------------------------------------*/ +void tm_get(p_tm tm, int *tm_block, int *tm_return) +{ + if (tm_block) *tm_block = tm->tm_block; + if (tm_return) *tm_return = tm->tm_return; +} + +/*-------------------------------------------------------------------------*\ +* Determines how much time we have left for the current io operation +* an IO write operation. +* Input +* tm: timeout control structure +* Returns +* the number of ms left or -1 if there is no time limit +\*-------------------------------------------------------------------------*/ +int tm_getremaining(p_tm tm) +{ + /* no timeout */ + if (tm->tm_block < 0 && tm->tm_return < 0) + return -1; + /* there is no block timeout, we use the return timeout */ + else if (tm->tm_block < 0) + return MAX(tm->tm_return - tm_gettime() + tm->tm_start, 0); + /* there is no return timeout, we use the block timeout */ + else if (tm->tm_return < 0) + return tm->tm_block; + /* both timeouts are specified */ + else return MIN(tm->tm_block, + MAX(tm->tm_return - tm_gettime() + tm->tm_start, 0)); +} + +/*-------------------------------------------------------------------------*\ +* Marks the operation start time in sock structure +* Input +* tm: timeout control structure +\*-------------------------------------------------------------------------*/ +void tm_markstart(p_tm tm) +{ + tm->tm_start = tm_gettime(); + tm->tm_end = tm->tm_start; +} + +/*-------------------------------------------------------------------------*\ +* Returns the length of the operation in ms +* Input +* tm: timeout control structure +\*-------------------------------------------------------------------------*/ +int tm_getelapsed(p_tm tm) +{ + return tm->tm_end - tm->tm_start; +} + +/*-------------------------------------------------------------------------*\ +* Gets time in ms, relative to system startup. +* Returns +* time in ms. +\*-------------------------------------------------------------------------*/ +#ifdef WIN32 +int tm_gettime(void) +{ + return GetTickCount(); +} +#else +int tm_gettime(void) +{ + struct tms t; + return (times(&t)*1000)/CLK_TCK; +} +#endif + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +void tm_open(lua_State *L) +{ + (void) L; +#ifdef _DEBUG + lua_pushcfunction(L, tm_lua_time); + lua_setglobal(L, "_time"); + lua_pushcfunction(L, tm_lua_sleep); + lua_setglobal(L, "_sleep"); +#endif +} + +/*=========================================================================*\ +* Test support functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Returns the time the system has been up, in secconds. +\*-------------------------------------------------------------------------*/ +#ifdef _DEBUG +static int tm_lua_time(lua_State *L) +{ + lua_pushnumber(L, tm_gettime()/1000.0); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Sleep for n seconds. +\*-------------------------------------------------------------------------*/ +int tm_lua_sleep(lua_State *L) +{ + double n = luaL_check_number(L, 1); +#ifdef WIN32 + Sleep(n*1000); +#else + sleep(n); +#endif + return 0; +} +#endif diff --git a/src/timeout.h b/src/timeout.h new file mode 100644 index 0000000..af7e591 --- /dev/null +++ b/src/timeout.h @@ -0,0 +1,20 @@ +#ifndef _TM_H +#define _TM_H + +typedef struct t_tm_tag { + int tm_return; + int tm_block; + int tm_start; + int tm_end; +} t_tm; +typedef t_tm *p_tm; + +void tm_set(p_tm tm, int tm_block, int tm_return); +int tm_getremaining(p_tm tm); +int tm_getelapsed(p_tm tm); +int tm_gettime(void); +void tm_get(p_tm tm, int *tm_block, int *tm_return); +void tm_markstart(p_tm tm); +void tm_open(lua_State *L); + +#endif diff --git a/src/udp.c b/src/udp.c new file mode 100644 index 0000000..0dc0df8 --- /dev/null +++ b/src/udp.c @@ -0,0 +1,287 @@ +/*=========================================================================*\ +* UDP socket object implementation (inherits from sock and inet) +\*=========================================================================*/ +#include + +#include +#include + +#include "lsinet.h" +#include "lsudp.h" +#include "lscompat.h" +#include "lsselect.h" + +/*=========================================================================*\ +* Internal function prototypes. +\*=========================================================================*/ +static int udp_lua_send(lua_State *L); +static int udp_lua_sendto(lua_State *L); +static int udp_lua_receive(lua_State *L); +static int udp_lua_receivefrom(lua_State *L); +static int udp_lua_setpeername(lua_State *L); +static int udp_lua_setsockname(lua_State *L); + +static int udp_global_udpsocket(lua_State *L); + +static struct luaL_reg funcs[] = { + {"send", udp_lua_send}, + {"sendto", udp_lua_sendto}, + {"receive", udp_lua_receive}, + {"receivefrom", udp_lua_receivefrom}, + {"setpeername", udp_lua_setpeername}, + {"setsockname", udp_lua_setsockname}, +}; + +/*=========================================================================*\ +* Exported functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +void udp_open(lua_State *L) +{ + unsigned int i; + priv_newclass(L, UDP_CLASS); + udp_inherit(L, UDP_CLASS); + /* declare global functions */ + lua_pushcfunction(L, udp_global_udpsocket); + lua_setglobal(L, "udpsocket"); + for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) + priv_newglobalmethod(L, funcs[i].name); + /* make class selectable */ + select_addclass(L, UDP_CLASS); +} + +/*-------------------------------------------------------------------------*\ +* Hook object methods to methods table. +\*-------------------------------------------------------------------------*/ +void udp_inherit(lua_State *L, cchar *lsclass) +{ + unsigned int i; + inet_inherit(L, lsclass); + for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { + lua_pushcfunction(L, funcs[i].func); + priv_setmethod(L, lsclass, funcs[i].name); + } +} + +/*-------------------------------------------------------------------------*\ +* Initializes socket structure +\*-------------------------------------------------------------------------*/ +void udp_construct(lua_State *L, p_udp udp) +{ + inet_construct(L, (p_inet) udp); + udp->udp_connected = 0; +} + +/*-------------------------------------------------------------------------*\ +* Creates a socket structure and initializes it. A socket object is +* left in the Lua stack. +* Returns +* pointer to allocated structure +\*-------------------------------------------------------------------------*/ +p_udp udp_push(lua_State *L) +{ + p_udp udp = (p_udp) lua_newuserdata(L, sizeof(t_udp)); + priv_setclass(L, UDP_CLASS); + udp_construct(L, udp); + return udp; +} + +/*=========================================================================*\ +* Socket table constructors +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a udp socket object and returns it to the Lua script. +* Lua Input: [options] +* options: socket options table +* Lua Returns +* On success: udp socket +* On error: nil, followed by an error message +\*-------------------------------------------------------------------------*/ +static int udp_global_udpsocket(lua_State *L) +{ + int oldtop = lua_gettop(L); + p_udp udp = udp_push(L); + cchar *err = inet_trysocket((p_inet) udp, SOCK_DGRAM); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + if (oldtop < 1) return 1; + err = compat_trysetoptions(L, udp->fd); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + return 1; +} + +/*=========================================================================*\ +* Socket table methods +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Receives data from a UDP socket +* Lua Input: sock [, wanted] +* sock: client socket created by the connect function +* wanted: the number of bytes expected (default: LUASOCKET_UDPBUFFERSIZE) +* Lua Returns +* On success: datagram received +* On error: nil, followed by an error message +\*-------------------------------------------------------------------------*/ +static int udp_lua_receive(lua_State *L) +{ + p_udp udp = (p_udp) lua_touserdata(L, 1); + unsigned char buffer[UDP_DATAGRAMSIZE]; + size_t got, wanted = (size_t) luaL_opt_number(L, 2, sizeof(buffer)); + int err; + p_tm tm = &udp->base_tm; + wanted = MIN(wanted, sizeof(buffer)); + tm_markstart(tm); + err = compat_recv(udp->fd, buffer, wanted, &got, tm_getremaining(tm)); + if (err == PRIV_CLOSED) err = PRIV_REFUSED; + if (err != PRIV_DONE) lua_pushnil(L); + else lua_pushlstring(L, buffer, got); + priv_pusherror(L, err); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Receives a datagram from a UDP socket +* Lua Input: sock [, wanted] +* sock: client socket created by the connect function +* wanted: the number of bytes expected (default: LUASOCKET_UDPBUFFERSIZE) +* Lua Returns +* On success: datagram received, ip and port of sender +* On error: nil, followed by an error message +\*-------------------------------------------------------------------------*/ +static int udp_lua_receivefrom(lua_State *L) +{ + p_udp udp = (p_udp) lua_touserdata(L, 1); + p_tm tm = &udp->base_tm; + struct sockaddr_in peer; + size_t peer_len = sizeof(peer); + unsigned char buffer[UDP_DATAGRAMSIZE]; + size_t wanted = (size_t) luaL_opt_number(L, 2, sizeof(buffer)); + size_t got; + int err; + if (udp->udp_connected) lua_error(L, "receivefrom on connected socket"); + tm_markstart(tm); + wanted = MIN(wanted, sizeof(buffer)); + err = compat_recvfrom(udp->fd, buffer, wanted, &got, tm_getremaining(tm), + (SA *) &peer, &peer_len); + if (err == PRIV_CLOSED) err = PRIV_REFUSED; + if (err == PRIV_DONE) { + lua_pushlstring(L, buffer, got); + lua_pushstring(L, inet_ntoa(peer.sin_addr)); + lua_pushnumber(L, ntohs(peer.sin_port)); + return 3; + } else { + lua_pushnil(L); + priv_pusherror(L, err); + return 2; + } +} + +/*-------------------------------------------------------------------------*\ +* Send data through a connected UDP socket +* Lua Input: sock, data +* sock: udp socket +* data: data to be sent +* Lua Returns +* On success: nil, followed by the total number of bytes sent +* On error: error message +\*-------------------------------------------------------------------------*/ +static int udp_lua_send(lua_State *L) +{ + p_udp udp = (p_udp) lua_touserdata(L, 1); + p_tm tm = &udp->base_tm; + size_t wanted, sent = 0; + int err; + cchar *data = luaL_check_lstr(L, 2, &wanted); + if (!udp->udp_connected) lua_error(L, "send on unconnected socket"); + tm_markstart(tm); + err = compat_send(udp->fd, data, wanted, &sent, tm_getremaining(tm)); + priv_pusherror(L, err == PRIV_CLOSED ? PRIV_REFUSED : err); + lua_pushnumber(L, sent); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Send data through a unconnected UDP socket +* Lua Input: sock, data, ip, port +* sock: udp socket +* data: data to be sent +* ip: ip address of target +* port: port in target +* Lua Returns +* On success: nil, followed by the total number of bytes sent +* On error: error message +\*-------------------------------------------------------------------------*/ +static int udp_lua_sendto(lua_State *L) +{ + p_udp udp = (p_udp) lua_touserdata(L, 1); + size_t wanted, sent = 0; + cchar *data = luaL_check_lstr(L, 2, &wanted); + cchar *ip = luaL_check_string(L, 3); + ushort port = (ushort) luaL_check_number(L, 4); + p_tm tm = &udp->base_tm; + struct sockaddr_in peer; + int err; + if (udp->udp_connected) lua_error(L, "sendto on connected socket"); + memset(&peer, 0, sizeof(peer)); + if (!inet_aton(ip, &peer.sin_addr)) lua_error(L, "invalid ip address"); + peer.sin_family = AF_INET; + peer.sin_port = htons(port); + tm_markstart(tm); + err = compat_sendto(udp->fd, data, wanted, &sent, tm_getremaining(tm), + (SA *) &peer, sizeof(peer)); + priv_pusherror(L, err == PRIV_CLOSED ? PRIV_REFUSED : err); + lua_pushnumber(L, sent); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Associates a local address to an UDP socket +* Lua Input: address, port +* address: host name or ip address to bind to +* port: port to bind to +* Lua Returns +* On success: nil +* On error: error message +\*-------------------------------------------------------------------------*/ +static int udp_lua_setsockname(lua_State * L) +{ + p_udp udp = (p_udp) lua_touserdata(L, 1); + cchar *address = luaL_check_string(L, 2); + ushort port = (ushort) luaL_check_number(L, 3); + cchar *err = inet_trybind((p_inet) udp, address, port); + if (err) lua_pushstring(L, err); + else lua_pushnil(L); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Sets a peer for a UDP socket +* Lua Input: address, port +* address: remote host name +* port: remote host port +* Lua Returns +* On success: nil +* On error: error message +\*-------------------------------------------------------------------------*/ +static int udp_lua_setpeername(lua_State *L) +{ + p_udp udp = (p_udp) lua_touserdata(L, 1); + cchar *address = luaL_check_string(L, 2); + ushort port = (ushort) luaL_check_number(L, 3); + cchar *err = inet_tryconnect((p_inet) udp, address, port); + if (!err) { + udp->udp_connected = 1; + lua_pushnil(L); + } else lua_pushstring(L, err); + return 1; +} + diff --git a/src/udp.h b/src/udp.h new file mode 100644 index 0000000..3c82c29 --- /dev/null +++ b/src/udp.h @@ -0,0 +1,24 @@ +#ifndef UDP_H_ +#define UDP_H_ + +#include "lsinet.h" + +#define UDP_CLASS "luasocket(UDP socket)" + +#define UDP_DATAGRAMSIZE 576 + +#define UDP_FIELDS \ + INET_FIELDS; \ + int udp_connected + +typedef struct t_udp_tag { + UDP_FIELDS; +} t_udp; +typedef t_udp *p_udp; + +void udp_inherit(lua_State *L, cchar *lsclass); +void udp_construct(lua_State *L, p_udp udp); +void udp_open(lua_State *L); +p_udp udp_push(lua_State *L); + +#endif From 46828c1a7d02f029cf3fa1b3c6d56cf054100c98 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 3 Jul 2002 19:06:55 +0000 Subject: [PATCH 096/483] internet class. --- src/inet.c | 116 +++++++++++++++++++++-------------------------------- 1 file changed, 46 insertions(+), 70 deletions(-) diff --git a/src/inet.c b/src/inet.c index f512760..5003ed9 100644 --- a/src/inet.c +++ b/src/inet.c @@ -1,11 +1,11 @@ /*=========================================================================*\ -* Internet socket methods implementation +* Internet domain class +* Lua methods: +* getpeername: gets socket peer ip address and port +* getsockname: gets local socket ip address and port * Global Lua fuctions: -* toip(hostname) -* tohostname(dotted-quad) -* Socket table methods: -* getpeername() -* getsockname() +* toip: gets resolver info on host name +* tohostname: gets resolver info on dotted-quad \*=========================================================================*/ #include @@ -46,7 +46,9 @@ void inet_open(lua_State *L) } /*-------------------------------------------------------------------------*\ -* Hook object methods to methods table. +* Hook lua methods to methods table. +* Input +* lsclass: class name \*-------------------------------------------------------------------------*/ void inet_inherit(lua_State *L, cchar *lsclass) { @@ -62,6 +64,9 @@ void inet_inherit(lua_State *L, cchar *lsclass) } } +/*-------------------------------------------------------------------------*\ +* Constructs the object +\*-------------------------------------------------------------------------*/ void inet_construct(lua_State *L, p_inet inet) { sock_construct(L, (p_sock) inet); @@ -71,7 +76,8 @@ void inet_construct(lua_State *L, p_inet inet) * Global Lua functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ -* Returns the list of ip addresses associated with a host name +* Returns all information provided by the resolver given a host name +* or ip address * Lua Input: address * address: ip address or hostname to dns lookup * Lua Returns @@ -98,7 +104,8 @@ static int inet_lua_toip(lua_State *L) } /*-------------------------------------------------------------------------*\ -* Returns the list of host names associated with an ip address +* Returns all information provided by the resolver given a host name +* or ip address * Lua Input: address * address: ip address or host name to reverse dns lookup * Lua Returns @@ -124,7 +131,7 @@ static int inet_lua_tohostname(lua_State *L) } /*=========================================================================*\ -* Socket table methods +* Lua methods \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Retrieves socket peer name @@ -220,42 +227,28 @@ static void inet_pushresolved(lua_State *L, struct hostent *hp) * Returns * NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ -cchar *inet_tryconnect(p_inet inet, cchar *address, ushort port, - int type) +cchar *inet_tryconnect(p_inet inet, cchar *address, ushort port) { struct sockaddr_in remote; - cchar *err = NULL; memset(&remote, 0, sizeof(remote)); - if (strlen(address) && inet_aton(address, &remote.sin_addr)) { - remote.sin_family = AF_INET; - remote.sin_port = htons(port); - err = inet_trysocket(inet, type); - if (err) return err; - if (compat_connect(inet->fd, (SA *) &remote, sizeof(remote)) < 0) { - compat_close(inet->fd); - inet->fd = COMPAT_INVALIDFD; - return compat_connectstrerror(); - } else return NULL; - /* go ahead and try by hostname resolution */ - } else { + remote.sin_family = AF_INET; + remote.sin_port = htons(port); + if (!strlen(address) || !inet_aton(address, &remote.sin_addr)) { struct hostent *hp = gethostbyname(address); struct in_addr **addr; if (!hp) return compat_hoststrerror(); addr = (struct in_addr **) hp->h_addr_list; - for (; *addr != NULL; addr++) { - memcpy(&remote.sin_addr, *addr, sizeof(struct in_addr)); - remote.sin_family = AF_INET; - remote.sin_port = htons(port); - err = inet_trysocket(inet, type); - if (err) return err; - if (compat_connect(inet->fd, (SA *)&remote, sizeof(remote)) < 0) { - compat_close(inet->fd); - inet->fd = COMPAT_INVALIDFD; - } else return NULL; - memset(&remote, 0, sizeof(remote)); - } - return compat_connectstrerror(); + memcpy(&remote.sin_addr, *addr, sizeof(struct in_addr)); } + compat_setblocking(inet->fd); + if (compat_connect(inet->fd, (SA *) &remote, sizeof(remote)) < 0) { + const char *err = compat_connectstrerror(); + compat_close(inet->fd); + inet->fd = COMPAT_INVALIDFD; + return err; + } + compat_setnonblocking(inet->fd); + return NULL; } /*-------------------------------------------------------------------------*\ @@ -263,51 +256,34 @@ cchar *inet_tryconnect(p_inet inet, cchar *address, ushort port, * Input * address: host name or ip address * port: port number to bind to -* backlog: backlog to set * Returns * NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ -cchar *inet_trybind(p_inet inet, cchar *address, ushort port, - int type) +cchar *inet_trybind(p_inet inet, cchar *address, ushort port) { struct sockaddr_in local; - cchar *err = NULL; memset(&local, 0, sizeof(local)); /* address is either wildcard or a valid ip address */ local.sin_addr.s_addr = htonl(INADDR_ANY); - if (!strcmp(address, "*") || - (strlen(address) && inet_aton(address, &local.sin_addr))) { - local.sin_port = htons(port); - local.sin_family = AF_INET; - err = inet_trysocket(inet, type); - if (err) return err; - compat_setreuseaddr(inet->fd); - if (compat_bind(inet->fd, (SA *) &local, sizeof(local)) < 0) { - compat_close(inet->fd); - inet->fd = COMPAT_INVALIDFD; - return compat_bindstrerror(); - } else return NULL; - /* otherwise, proceed with domain name resolution */ - } else { + local.sin_port = htons(port); + local.sin_family = AF_INET; + if (strcmp(address, "*") && + (!strlen(address) || !inet_aton(address, &local.sin_addr))) { struct hostent *hp = gethostbyname(address); struct in_addr **addr; if (!hp) return compat_hoststrerror(); addr = (struct in_addr **) hp->h_addr_list; - for (; *addr != NULL; addr++) { - memcpy(&local.sin_addr, *addr, sizeof(struct in_addr)); - local.sin_port = htons(port); - local.sin_family = AF_INET; - err = inet_trysocket(inet, type); - if (err) return err; - compat_setreuseaddr(inet->fd); - if (compat_bind(inet->fd, (SA *) &local, sizeof(local)) < 0) { - compat_close(inet->fd); - inet->fd = COMPAT_INVALIDFD; - } else return NULL; - memset(&local, 0, sizeof(local)); - } - return compat_bindstrerror(); + memcpy(&local.sin_addr, *addr, sizeof(struct in_addr)); } + compat_setblocking(inet->fd); + if (compat_bind(inet->fd, (SA *) &local, sizeof(local)) < 0) { + const char *err = compat_bindstrerror(); + compat_close(inet->fd); + inet->fd = COMPAT_INVALIDFD; + return err; + } + compat_setnonblocking(inet->fd); + return NULL; } /*-------------------------------------------------------------------------*\ From cbffc42f299575e56d88527a48e26377f3d1f800 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 8 Jul 2002 20:14:09 +0000 Subject: [PATCH 097/483] Initial revision --- src/unix.c | 325 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/unix.h | 69 ++++++++++++ 2 files changed, 394 insertions(+) create mode 100644 src/unix.c create mode 100644 src/unix.h diff --git a/src/unix.c b/src/unix.c new file mode 100644 index 0000000..d50d98c --- /dev/null +++ b/src/unix.c @@ -0,0 +1,325 @@ +/*=========================================================================*\ +* Network compatibilization module +\*=========================================================================*/ +#include + +#include +#include + +#include "lscompat.h" + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static cchar *try_setoption(lua_State *L, COMPAT_FD sock); +static cchar *try_setbooloption(lua_State *L, COMPAT_FD sock, int name); + +/*=========================================================================*\ +* Exported functions. +\*=========================================================================*/ +void compat_open(lua_State *L) +{ + /* Instals a handler to ignore sigpipe. This function is not + needed on the WinSock2, since it's sockets don't raise signals. */ + struct sigaction new; + memset(&new, 0, sizeof(new)); + new.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &new, NULL); +} + +COMPAT_FD compat_accept(COMPAT_FD s, struct sockaddr *addr, + socklen_t *len, int deadline) +{ + struct timeval tv; + fd_set fds; + tv.tv_sec = deadline / 1000; + tv.tv_usec = (deadline % 1000) * 1000; + FD_ZERO(&fds); + FD_SET(s, &fds); + select(s+1, &fds, NULL, NULL, deadline >= 0 ? &tv : NULL); + return accept(s, addr, len); +} + +int compat_send(COMPAT_FD c, cchar *data, size_t count, size_t *sent, + int deadline) +{ + struct timeval tv; + fd_set fds; + ssize_t put = 0; + int err; + int ret; + tv.tv_sec = deadline / 1000; + tv.tv_usec = (deadline % 1000) * 1000; + FD_ZERO(&fds); + FD_SET(c, &fds); + ret = select(c+1, NULL, &fds, NULL, deadline >= 0 ? &tv : NULL); + if (ret > 0) { + put = write(c, data, count); + if (put <= 0) { + err = PRIV_CLOSED; +#ifdef __CYGWIN__ + /* this is for CYGWIN, which is like Unix but has Win32 bugs */ + if (sent < 0 && errno == EWOULDBLOCK) err = PRIV_DONE; +#endif + *sent = 0; + } else { + *sent = put; + err = PRIV_DONE; + } + return err; + } else { + *sent = 0; + return PRIV_TIMEOUT; + } +} + +int compat_sendto(COMPAT_FD c, cchar *data, size_t count, size_t *sent, + int deadline, SA *addr, socklen_t len) +{ + struct timeval tv; + fd_set fds; + ssize_t put = 0; + int err; + int ret; + tv.tv_sec = deadline / 1000; + tv.tv_usec = (deadline % 1000) * 1000; + FD_ZERO(&fds); + FD_SET(c, &fds); + ret = select(c+1, NULL, &fds, NULL, deadline >= 0 ? &tv : NULL); + if (ret > 0) { + put = sendto(c, data, count, 0, addr, len); + if (put <= 0) { + err = PRIV_CLOSED; +#ifdef __CYGWIN__ + /* this is for CYGWIN, which is like Unix but has Win32 bugs */ + if (sent < 0 && errno == EWOULDBLOCK) err = PRIV_DONE; +#endif + *sent = 0; + } else { + *sent = put; + err = PRIV_DONE; + } + return err; + } else { + *sent = 0; + return PRIV_TIMEOUT; + } +} + +int compat_recv(COMPAT_FD c, uchar *data, size_t count, size_t *got, + int deadline) +{ + struct timeval tv; + fd_set fds; + int ret; + ssize_t taken = 0; + tv.tv_sec = deadline / 1000; + tv.tv_usec = (deadline % 1000) * 1000; + FD_ZERO(&fds); + FD_SET(c, &fds); + ret = select(c+1, &fds, NULL, NULL, deadline >= 0 ? &tv : NULL); + if (ret > 0) { + taken = read(c, data, count); + if (taken <= 0) { + *got = 0; + return PRIV_CLOSED; + } else { + *got = taken; + return PRIV_DONE; + } + } else { + *got = 0; + return PRIV_TIMEOUT; + } +} + +int compat_recvfrom(COMPAT_FD c, uchar *data, size_t count, size_t *got, + int deadline, SA *addr, socklen_t *len) +{ + struct timeval tv; + fd_set fds; + int ret; + ssize_t taken = 0; + tv.tv_sec = deadline / 1000; + tv.tv_usec = (deadline % 1000) * 1000; + FD_ZERO(&fds); + FD_SET(c, &fds); + ret = select(c+1, &fds, NULL, NULL, deadline >= 0 ? &tv : NULL); + if (ret > 0) { + taken = recvfrom(c, data, count, 0, addr, len); + if (taken <= 0) { + *got = 0; + return PRIV_CLOSED; + } else { + *got = taken; + return PRIV_DONE; + } + } else { + *got = 0; + return PRIV_TIMEOUT; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last host manipulation error. +\*-------------------------------------------------------------------------*/ +const char *compat_hoststrerror(void) +{ + switch (h_errno) { + case HOST_NOT_FOUND: return "host not found"; + case NO_ADDRESS: return "unable to resolve host name"; + case NO_RECOVERY: return "name server error"; + case TRY_AGAIN: return "name server unavailable, try again later"; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last socket manipulation error. +\*-------------------------------------------------------------------------*/ +const char *compat_socketstrerror(void) +{ + switch (errno) { + case EACCES: return "access denied"; + case EMFILE: return "descriptor table is full"; + case ENFILE: return "too many open files"; + case ENOBUFS: return "insuffucient buffer space"; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last bind command error. +\*-------------------------------------------------------------------------*/ +const char *compat_bindstrerror(void) +{ + switch (errno) { + case EBADF: return "invalid descriptor"; + case EINVAL: return "socket already bound"; + case EACCES: return "access denied"; + case ENOTSOCK: return "not a socket descriptor"; + case EADDRINUSE: return "address already in use"; + case EADDRNOTAVAIL: return "address unavailable"; + case ENOMEM: return "out of memory"; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last connect error. +\*-------------------------------------------------------------------------*/ +const char *compat_connectstrerror(void) +{ + switch (errno) { + case EBADF: return "invalid descriptor"; + case ENOTSOCK: return "not a socket descriptor"; + case EADDRNOTAVAIL: return "address not availabe"; + case ETIMEDOUT: return "connection timed out"; + case ECONNREFUSED: return "connection refused"; + case EACCES: return "access denied"; + case ENETUNREACH: return "network is unreachable"; + case EADDRINUSE: return "address already in use"; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Sets the SO_REUSEADDR socket option +* Input +* sock: socket descriptor +\*-------------------------------------------------------------------------*/ +void compat_setreuseaddr(COMPAT_FD sock) +{ + int val = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)); +} + +COMPAT_FD compat_socket(int domain, int type, int protocol) +{ + COMPAT_FD sock = socket(domain, type, protocol); + if (sock != COMPAT_INVALIDFD) { + compat_setnonblocking(sock); + compat_setreuseaddr(sock); + } + return sock; +} + +/*-------------------------------------------------------------------------*\ +* Put socket into blocking mode. +\*-------------------------------------------------------------------------*/ +void compat_setblocking(COMPAT_FD sock) +{ + int flags = fcntl(sock, F_GETFL, 0); + flags &= (~(O_NONBLOCK)); + fcntl(sock, F_SETFL, flags); +} + +/*-------------------------------------------------------------------------*\ +* Put socket into non-blocking mode. +\*-------------------------------------------------------------------------*/ +void compat_setnonblocking(COMPAT_FD sock) +{ + int flags = fcntl(sock, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(sock, F_SETFL, flags); +} + +/*-------------------------------------------------------------------------*\ +* Tries to set extended udp socket options +* Input +* udp: udp structure +* oldtop: top of stack +* Returns +* NULL if successfull, error message on error +\*-------------------------------------------------------------------------*/ +cchar *compat_trysetoptions(lua_State *L, COMPAT_FD sock) +{ + if (!lua_istable(L, 1)) luaL_argerror(L, 1, "invalid options table"); + lua_pushnil(L); + while (lua_next(L, 1)) { + cchar *err = try_setoption(L, sock); + lua_pop(L, 1); + if (err) return err; + } + return NULL; +} + +/*=========================================================================*\ +* Internal functions. +\*=========================================================================*/ +static cchar *try_setbooloption(lua_State *L, COMPAT_FD sock, int name) +{ + int bool, res; + if (!lua_isnumber(L, -1)) lua_error(L, "invalid option value"); + bool = (int) lua_tonumber(L, -1); + res = setsockopt(sock, SOL_SOCKET, name, (char *) &bool, sizeof(bool)); + if (res < 0) return "error setting option"; + else return NULL; +} + + +/*-------------------------------------------------------------------------*\ +* Set socket options from a table on top of Lua stack. +* Supports SO_KEEPALIVE, SO_DONTROUTE, SO_BROADCAST, and SO_LINGER options. +* Input +* L: Lua state to use +* sock: socket descriptor +* Returns +* 1 if successful, 0 otherwise +\*-------------------------------------------------------------------------*/ +static cchar *try_setoption(lua_State *L, COMPAT_FD sock) +{ + static cchar *options[] = { + "SO_KEEPALIVE", "SO_DONTROUTE", "SO_BROADCAST", "SO_LINGER", NULL + }; + cchar *option = lua_tostring(L, -2); + if (!lua_isstring(L, -2)) return "invalid option"; + switch (luaL_findstring(option, options)) { + case 0: return try_setbooloption(L, sock, SO_KEEPALIVE); + case 1: return try_setbooloption(L, sock, SO_DONTROUTE); + case 2: return try_setbooloption(L, sock, SO_BROADCAST); + case 3: return "SO_LINGER is deprecated"; + default: return "unsupported option"; + } +} + diff --git a/src/unix.h b/src/unix.h new file mode 100644 index 0000000..e317b06 --- /dev/null +++ b/src/unix.h @@ -0,0 +1,69 @@ +#ifndef COMPAT_H_ +#define COMPAT_H_ + +#include "lspriv.h" + +/*=========================================================================*\ +* BSD include files +\*=========================================================================*/ +/* error codes */ +#include +/* close function */ +#include +/* fnctnl function and associated constants */ +#include +/* struct timeval and CLK_TCK */ +#include +/* times function and struct tms */ +#include +/* struct sockaddr */ +#include +/* socket function */ +#include +/* gethostbyname and gethostbyaddr functions */ +#include +/* sigpipe handling */ +#include + +#include +#include + +#define COMPAT_FD int +#define COMPAT_INVALIDFD (-1) + +/* we are lazy... */ +typedef struct sockaddr SA; + +/*=========================================================================*\ +* Exported functions +\*=========================================================================*/ +void compat_open(lua_State *L); + +#define compat_bind bind +#define compat_connect connect +#define compat_listen listen +#define compat_close close +#define compat_select select + +COMPAT_FD compat_socket(int domain, int type, int protocol); +COMPAT_FD compat_accept(COMPAT_FD s, SA *addr, socklen_t *len, int deadline); +int compat_send(COMPAT_FD c, cchar *data, size_t count, size_t *done, + int deadline); +int compat_recv(COMPAT_FD c, uchar *data, size_t count, size_t *done, + int deadline); +int compat_sendto(COMPAT_FD c, cchar *data, size_t count, size_t *done, + int deadline, SA *addr, socklen_t len); +int compat_recvfrom(COMPAT_FD c, uchar *data, size_t count, size_t *got, + int deadline, SA *addr, socklen_t *len); +void compat_setnonblocking(COMPAT_FD sock); +void compat_setblocking(COMPAT_FD sock); +void compat_setreuseaddr(COMPAT_FD sock); + +const char *compat_hoststrerror(void); +const char *compat_socketstrerror(void); +const char *compat_bindstrerror(void); +const char *compat_connectstrerror(void); + +cchar *compat_trysetoptions(lua_State *L, COMPAT_FD sock); + +#endif /* COMPAT_H_ */ From c27087ca95e15ede5a2dcc0b160341fb9bed04ca Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 8 Jul 2002 20:44:12 +0000 Subject: [PATCH 098/483] LuaSocket 1.5 (4.1w3) --- src/luasocket.h | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/luasocket.h b/src/luasocket.h index 319bffb..95720e2 100644 --- a/src/luasocket.h +++ b/src/luasocket.h @@ -1,24 +1,17 @@ /*=========================================================================*\ -* TCP/IP support for LUA +* Networking support for the Lua language * Diego Nehab * 9/11/1999 +* +* RCS ID: $Id$ \*=========================================================================*/ - #ifndef _LUASOCKET_H_ #define _LUASOCKET_H_ -/* Current luasocket version */ -#define LUASOCKET_VERSION "LuaSocket 1.4" - /*-------------------------------------------------------------------------*\ -* These can be changed to according to the applications' needs. +* Current luasocket version \*-------------------------------------------------------------------------*/ -/* TCP input buffer size */ -#define LUASOCKET_TCPBUFFERSIZE 8192 - -/* The largest datagram handled by LuaSocket */ -#define LUASOCKET_UDPBUFFERSIZE 4096 -/* note that 576 bytes is the maximum safe value */ +#define LUASOCKET_VERSION "LuaSocket 1.5" /*-------------------------------------------------------------------------*\ * This macro prefixes all exported API functions @@ -28,8 +21,7 @@ #endif /*-------------------------------------------------------------------------*\ -* Initializes the library interface with Lua and the socket library. -* Defines the symbols exported to Lua. +* Initializes the library. \*-------------------------------------------------------------------------*/ LUASOCKET_API int lua_socketlibopen(lua_State *L); From 112e51be589e5333c12787d4dd0d22fcb4f7ae8e Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 8 Jul 2002 20:56:36 +0000 Subject: [PATCH 099/483] Sockets are not tables anymore. --- samples/tinyirc.lua | 71 +++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/samples/tinyirc.lua b/samples/tinyirc.lua index f38ab73..b0c4168 100644 --- a/samples/tinyirc.lua +++ b/samples/tinyirc.lua @@ -20,49 +20,52 @@ if arg then port2 = arg[3] or port2 end -server1 = bind(host, port1) +server1, error = bind(host, port1) +if not server1 then print(error) exit() end server1:timeout(1) -server1.is_server = 1 -server2 = bind(host, port2) +server2, error = bind(host, port2) +if not server2 then print(error) exit() end server2:timeout(1) -server2.is_server = 1 -set = {server1, server2} -number = 1 +sock_set = {server1, server2} + +sock_id = {} +sock_id[server1] = 1 +sock_id[server2] = 2 +next_id = 3 while 1 do - local r, s, e, l, n - r, _, e = select(set, nil) - for i, v in r do - if v.is_server then - s = v:accept() - if s then - s:timeout(1) - s.number = number - number = number + 1 - set_add(set, s) - write("Added client number ", s.number, ". ", - getn(set)-2, " total.\n") + local readable, _, error = select(sock_set, nil) + for _, sock in readable do + -- is it a server socket + if sock_id[sock] < 3 then + local incomming = sock:accept() + if incomming then + incomming:timeout(1) + sock_id[incomming] = next_id + set_add(sock_set, incomming) + write("Added client id ", next_id, ". ", + getn(sock_set)-2, " total.\n") + next_id = next_id + 1 end + -- it is a client socket else - l, e = v:receive() - n = v.number - if e then - v:close() - set_remove(set, v) - write("Removed client number ", n, ". ", - getn(set)-2, " total.\n") + local line, error = sock:receive() + local id = sock_id[sock] + if error then + sock:close() + set_remove(sock_set, sock) + write("Removed client number ", id, ". ", + getn(sock_set)-2, " total.\n") else - write("Broadcasting line '", tostring(n), "> ", - tostring(l), "'.\n") - _, s, e = select(nil, set, 1) - if not e then - for i,v in s do - v:send(tostring(n), "> ", l, "\r\n") + write("Broadcasting line '", id, "> ", line, "'.\n") + _, writable, error = select(nil, sock_set, 1) + if not error then + for _, outgoing in writable do + write("Sending to client ", sock_id[outgoing], "\n") + outgoing:send(id, "> ", line, "\r\n") end - else - write("No one ready to listen!!!\n") - end + else write("No one ready to listen!!!\n") end end end end From 9d7f43768aaa6d9f4b604c7e837a33755078ae15 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 8 Jul 2002 20:58:36 +0000 Subject: [PATCH 100/483] Do not load modules anymore. --- etc/get.lua | 7 ------- 1 file changed, 7 deletions(-) diff --git a/etc/get.lua b/etc/get.lua index 13f983b..ecfecd1 100644 --- a/etc/get.lua +++ b/etc/get.lua @@ -1,10 +1,3 @@ --- this examples needs it all -assert(dofile("../lua/code.lua")) -assert(dofile("../lua/ftp.lua")) -assert(dofile("../lua/concat.lua")) -assert(dofile("../lua/url.lua")) -assert(dofile("../lua/http.lua")) - -- formats a number of seconds into human readable form function nicetime(s) local l = "s" From 60e7bf48b077377c11022f34e24bbbfb596397bf Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 8 Jul 2002 21:01:18 +0000 Subject: [PATCH 101/483] Updated for Lua 4.1-w3. Dealing with 100 response codes. --- src/http.lua | 109 ++++++++++++++++++++++++++------------------------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/src/http.lua b/src/http.lua index e4c756f..9832a6b 100644 --- a/src/http.lua +++ b/src/http.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- HTTP/1.1 client support for the Lua language. --- LuaSocket 1.4 toolkit. +-- LuaSocket 1.5 toolkit. -- Author: Diego Nehab -- Date: 26/12/2000 -- Conforming to: RFC 2616, LTN7 @@ -18,17 +18,10 @@ Public.TIMEOUT = 60 -- default port for document retrieval Public.PORT = 80 -- user agent field sent in request -Public.USERAGENT = "LuaSocket 1.4" +Public.USERAGENT = "LuaSocket 1.5" -- block size used in transfers Public.BLOCKSIZE = 8192 ------------------------------------------------------------------------------ --- Required libraries ------------------------------------------------------------------------------ -dofile "concat.lua" -dofile "url.lua" -dofile "code.lua" - ----------------------------------------------------------------------------- -- Tries to get a pattern from the server and closes socket on error -- sock: socket connected to the server @@ -84,8 +77,8 @@ end ----------------------------------------------------------------------------- function Private.receive_status(sock) local line, err - line, err = %Private.try_receive(sock) - if not err then return %Private.get_statuscode(line), line + line, err = Private.try_receive(sock) + if not err then return Private.get_statuscode(line), line else return nil, nil, err end end @@ -104,7 +97,7 @@ function Private.receive_headers(sock, headers) local line, err local name, value, _ -- get first line - line, err = %Private.try_receive(sock) + line, err = Private.try_receive(sock) if err then return nil, err end -- headers go until a blank line is found while line ~= "" do @@ -116,12 +109,12 @@ function Private.receive_headers(sock, headers) end name = strlower(name) -- get next line (value might be folded) - line, err = %Private.try_receive(sock) + line, err = Private.try_receive(sock) if err then return nil, err end -- unfold any folded values while not err and strfind(line, "^%s") do value = value .. line - line, err = %Private.try_receive(sock) + line, err = Private.try_receive(sock) if err then return nil, err end end -- save pair in table @@ -144,7 +137,7 @@ function Private.receivebody_bychunks(sock, headers, receive_cb) local chunk, size, line, err, go, uerr, _ while 1 do -- get chunk size, skip extention - line, err = %Private.try_receive(sock) + line, err = Private.try_receive(sock) if err then local go, uerr = receive_cb(nil, err) return uerr or err @@ -159,7 +152,7 @@ function Private.receivebody_bychunks(sock, headers, receive_cb) -- was it the last chunk? if size <= 0 then break end -- get chunk - chunk, err = %Private.try_receive(sock, size) + chunk, err = Private.try_receive(sock, size) if err then go, uerr = receive_cb(nil, err) return uerr or err @@ -171,7 +164,7 @@ function Private.receivebody_bychunks(sock, headers, receive_cb) return uerr or "aborted by callback" end -- skip CRLF on end of chunk - _, err = %Private.try_receive(sock) + _, err = Private.try_receive(sock) if err then go, uerr = receive_cb(nil, err) return uerr or err @@ -180,7 +173,7 @@ function Private.receivebody_bychunks(sock, headers, receive_cb) -- the server should not send trailer headers because we didn't send a -- header informing it we know how to deal with them. we do not risk -- being caught unprepaired. - headers, err = %Private.receive_headers(sock, headers) + headers, err = Private.receive_headers(sock, headers) if err then go, uerr = receive_cb(nil, err) return uerr or err @@ -202,7 +195,7 @@ end function Private.receivebody_bylength(sock, length, receive_cb) local uerr, go while length > 0 do - local size = min(%Public.BLOCKSIZE, length) + local size = min(Public.BLOCKSIZE, length) local chunk, err = sock:receive(size) if err then go, uerr = receive_cb(nil, err) @@ -230,7 +223,7 @@ end function Private.receivebody_untilclosed(sock, receive_cb) local err, go, uerr while 1 do - local chunk, err = sock:receive(%Public.BLOCKSIZE) + local chunk, err = sock:receive(Public.BLOCKSIZE) if err == "closed" or not err then go, uerr = receive_cb(chunk) if not go then @@ -260,14 +253,14 @@ function Private.receive_body(sock, headers, receive_cb) local te = headers["transfer-encoding"] if te and te ~= "identity" then -- get by chunked transfer-coding of message body - return %Private.receivebody_bychunks(sock, headers, receive_cb) + return Private.receivebody_bychunks(sock, headers, receive_cb) elseif tonumber(headers["content-length"]) then -- get by content-length local length = tonumber(headers["content-length"]) - return %Private.receivebody_bylength(sock, length, receive_cb) + return Private.receivebody_bylength(sock, length, receive_cb) else -- get it all until connection closes - return %Private.receivebody_untilclosed(sock, receive_cb) + return Private.receivebody_untilclosed(sock, receive_cb) end end @@ -280,7 +273,7 @@ end -- nil if successfull or an error message in case of error ----------------------------------------------------------------------------- function Private.drop_body(sock, headers) - return %Private.receive_body(sock, headers, function (c, e) return 1 end) + return Private.receive_body(sock, headers, function (c, e) return 1 end) end ----------------------------------------------------------------------------- @@ -325,11 +318,11 @@ function Private.send_headers(sock, headers) headers = headers or {} -- send request headers for i, v in headers do - err = %Private.try_send(sock, i .. ": " .. v .. "\r\n") + err = Private.try_send(sock, i .. ": " .. v .. "\r\n") if err then return err end end -- mark end of request headers - return %Private.try_send(sock, "\r\n") + return Private.try_send(sock, "\r\n") end ----------------------------------------------------------------------------- @@ -346,7 +339,7 @@ end function Private.send_request(sock, method, uri, headers, body_cb) local chunk, size, done, err -- send request line - err = %Private.try_send(sock, method .. " " .. uri .. " HTTP/1.1\r\n") + err = Private.try_send(sock, method .. " " .. uri .. " HTTP/1.1\r\n") if err then return err end -- if there is a request message body, add content-length header if body_cb then @@ -360,11 +353,11 @@ function Private.send_request(sock, method, uri, headers, body_cb) end end -- send request headers - err = %Private.send_headers(sock, headers) + err = Private.send_headers(sock, headers) if err then return err end -- send request message body, if any if body_cb then - return %Private.send_indirect(sock, body_cb, chunk, size) + return Private.send_indirect(sock, body_cb, chunk, size) end end @@ -395,7 +388,7 @@ function Private.fill_headers(headers, parsed) local lower = {} headers = headers or {} -- set default headers - lower["user-agent"] = %Public.USERAGENT + lower["user-agent"] = Public.USERAGENT -- override with user values for i,v in headers do lower[strlower(i)] = v @@ -442,7 +435,7 @@ function Private.authorize(request, parsed, response) body_cb = request.body_cb, headers = request.headers } - return %Public.request_cb(authorize, response) + return Public.request_cb(authorize, response) end ----------------------------------------------------------------------------- @@ -482,7 +475,7 @@ function Private.redirect(request, response) body_cb = request.body_cb, headers = request.headers } - local response = %Public.request_cb(redirect, response) + local response = Public.request_cb(redirect, response) -- we pass the location header as a clue we tried to redirect if response.headers then response.headers.location = redirect.url end return response @@ -544,7 +537,7 @@ end function Public.request_cb(request, response) local parsed = URL.parse_url(request.url, { host = "", - port = %Public.PORT, + port = Public.PORT, path ="/", scheme = "http" }) @@ -558,35 +551,43 @@ function Public.request_cb(request, response) -- default method request.method = request.method or "GET" -- fill default headers - request.headers = %Private.fill_headers(request.headers, parsed) + request.headers = Private.fill_headers(request.headers, parsed) -- try to connect to server local sock sock, response.error = connect(parsed.host, parsed.port) if not sock then return response end -- set connection timeout so that we do not hang forever - sock:timeout(%Public.TIMEOUT) + sock:timeout(Public.TIMEOUT) -- send request message - response.error = %Private.send_request(sock, request.method, - %Private.request_uri(parsed), request.headers, request.body_cb) + response.error = Private.send_request(sock, request.method, + Private.request_uri(parsed), request.headers, request.body_cb) if response.error then return response end -- get server response message response.code, response.status, response.error = - %Private.receive_status(sock) + Private.receive_status(sock) if response.error then return response end + -- deal with 1xx status + if response.code == 100 then + response.headers, response.error = Private.receive_headers(sock, {}) + if response.error then return response end + response.code, response.status, response.error = + Private.receive_status(sock) + if response.error then return response end + end -- receive all headers - response.headers, response.error = %Private.receive_headers(sock, {}) + response.headers, response.error = Private.receive_headers(sock, {}) if response.error then return response end -- decide what to do based on request and response parameters - if %Private.should_redirect(request, response) then - %Private.drop_body(sock, response.headers) + if Private.should_redirect(request, response) then + Private.drop_body(sock, response.headers) sock:close() - return %Private.redirect(request, response) - elseif %Private.should_authorize(request, parsed, response) then - %Private.drop_body(sock, response.headers) + return Private.redirect(request, response) + elseif Private.should_authorize(request, parsed, response) then + Private.drop_body(sock, response.headers) sock:close() - return %Private.authorize(request, parsed, response) - elseif %Private.has_body(request, response) then - response.error = %Private.receive_body(sock, response.headers, + return Private.authorize(request, parsed, response) + elseif Private.has_body(request, response) then + response.error = Private.receive_body(sock, response.headers, response.body_cb) if response.error then return response end sock:close() @@ -618,15 +619,15 @@ function Public.request(request) local response = {} if request.body then request.body_cb = function() - return %request.body, strlen(%request.body) + return request.body, strlen(request.body) end end local cat = Concat.create() response.body_cb = function(chunk, err) - if chunk then %cat:addstring(chunk) end + if chunk then cat:addstring(chunk) end return 1 end - response = %Public.request_cb(request, response) + response = Public.request_cb(request, response) response.body = cat:getresult() response.body_cb = nil return response @@ -646,9 +647,9 @@ end -- error: error message if any ----------------------------------------------------------------------------- function Public.get(url_or_request) - local request = %Private.build_request(url_or_request) + local request = Private.build_request(url_or_request) request.method = "GET" - local response = %Public.request(request) + local response = Public.request(request) return response.body, response.headers, response.code, response.error end @@ -669,10 +670,10 @@ end -- error: error message, or nil if successfull ----------------------------------------------------------------------------- function Public.post(url_or_request, body) - local request = %Private.build_request(url_or_request) + local request = Private.build_request(url_or_request) request.method = "POST" request.body = request.body or body - local response = %Public.request(request) + local response = Public.request(request) return response.body, response.headers, response.code, response.error end From 5b2a124305f26d36e95b639b1c70a32368f03261 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 8 Jul 2002 21:01:45 +0000 Subject: [PATCH 102/483] Updated for Lua 4.1-w3. --- src/ftp.lua | 117 ++++++++++++++++++++++++--------------------------- src/smtp.lua | 64 ++++++++++++++-------------- src/url.lua | 36 ++++++++-------- 3 files changed, 105 insertions(+), 112 deletions(-) diff --git a/src/ftp.lua b/src/ftp.lua index 1af4d30..1fa48f7 100644 --- a/src/ftp.lua +++ b/src/ftp.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- FTP support for the Lua language --- LuaSocket 1.4 toolkit. +-- LuaSocket 1.5 toolkit. -- Author: Diego Nehab -- Date: 26/12/2000 -- Conforming to: RFC 959, LTN7 @@ -23,13 +23,6 @@ Public.EMAIL = "anonymous@anonymous.org" -- block size used in transfers Public.BLOCKSIZE = 8192 ------------------------------------------------------------------------------ --- Required libraries ------------------------------------------------------------------------------ -dofile "concat.lua" -dofile "url.lua" -dofile "code.lua" - ----------------------------------------------------------------------------- -- Tries to send DOS mode lines. Closes socket on error. -- Input @@ -91,7 +84,7 @@ function Private.send_command(control, cmd, arg) local line if arg then line = cmd .. " " .. arg else line = cmd end - return %Private.try_sendline(control, line) + return Private.try_sendline(control, line) end ----------------------------------------------------------------------------- @@ -104,14 +97,14 @@ end ----------------------------------------------------------------------------- function Private.get_answer(control) local code, lastcode, sep, _ - local line, err = %Private.try_receive(control) + local line, err = Private.try_receive(control) local answer = line if err then return nil, err end _,_, code, sep = strfind(line, "^(%d%d%d)(.)") if not code or not sep then return nil, answer end if sep == "-" then -- answer is multiline repeat - line, err = %Private.try_receive(control) + line, err = Private.try_receive(control) if err then return nil, err end _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)") answer = answer .. "\n" .. line @@ -130,7 +123,7 @@ end -- answer: server complete answer or system error message ----------------------------------------------------------------------------- function Private.check_answer(control, success) - local answer, code = %Private.get_answer(control) + local answer, code = Private.get_answer(control) if not answer then return nil, code end if type(success) ~= "table" then success = {success} end for i = 1, getn(success) do @@ -155,9 +148,9 @@ end -- answer: server complete answer or system error message ----------------------------------------------------------------------------- function Private.command(control, cmd, arg, success) - local err = %Private.send_command(control, cmd, arg) + local err = Private.send_command(control, cmd, arg) if err then return nil, err end - return %Private.check_answer(control, success) + return Private.check_answer(control, success) end ----------------------------------------------------------------------------- @@ -169,9 +162,9 @@ end -- answer: server answer or error message ----------------------------------------------------------------------------- function Private.greet(control) - local code, answer = %Private.check_answer(control, {120, 220}) + local code, answer = Private.check_answer(control, {120, 220}) if code == 120 then -- please try again, somewhat busy now... - return %Private.check_answer(control, {220}) + return Private.check_answer(control, {220}) end return code, answer end @@ -187,9 +180,9 @@ end -- answer: server answer or error message ----------------------------------------------------------------------------- function Private.login(control, user, password) - local code, answer = %Private.command(control, "user", user, {230, 331}) + local code, answer = Private.command(control, "user", user, {230, 331}) if code == 331 and password then -- need pass and we have pass - return %Private.command(control, "pass", password, {230, 202}) + return Private.command(control, "pass", password, {230, 202}) end return code, answer end @@ -204,7 +197,7 @@ end -- answer: server answer or error message ----------------------------------------------------------------------------- function Private.cwd(control, path) - if path then return %Private.command(control, "cwd", path, {250}) + if path then return Private.command(control, "cwd", path, {250}) else return 250, nil end end @@ -221,13 +214,13 @@ function Private.port(control) local server, ctl_ip ctl_ip, answer = control:getsockname() server, answer = bind(ctl_ip, 0) - server:timeout(%Public.TIMEOUT) + server:timeout(Public.TIMEOUT) local ip, p, ph, pl ip, p = server:getsockname() pl = mod(p, 256) ph = (p - pl)/256 local arg = gsub(format("%s,%d,%d", ip, ph, pl), "%.", ",") - code, answer = %Private.command(control, "port", arg, {200}) + code, answer = Private.command(control, "port", arg, {200}) if not code then server:close() return nil, answer @@ -243,7 +236,7 @@ end -- answer: server answer or error message ----------------------------------------------------------------------------- function Private.logout(control) - local code, answer = %Private.command(control, "quit", nil, {221}) + local code, answer = Private.command(control, "quit", nil, {221}) if code then control:close() end return code, answer end @@ -259,7 +252,7 @@ end function Private.receive_indirect(data, callback) local chunk, err, res while not err do - chunk, err = %Private.try_receive(data, %Public.BLOCKSIZE) + chunk, err = Private.try_receive(data, Public.BLOCKSIZE) if err == "closed" then err = "done" end res = callback(chunk, err) if not res then break end @@ -282,11 +275,11 @@ function Private.retrieve(control, server, name, is_directory, content_cb) local data -- ask server for file or directory listing accordingly if is_directory then - code, answer = %Private.cwd(control, name) + code, answer = Private.cwd(control, name) if not code then return answer end - code, answer = %Private.command(control, "nlst", nil, {150, 125}) + code, answer = Private.command(control, "nlst", nil, {150, 125}) else - code, answer = %Private.command(control, "retr", name, {150, 125}) + code, answer = Private.command(control, "retr", name, {150, 125}) end if not code then return nil, answer end data, answer = server:accept() @@ -295,14 +288,14 @@ function Private.retrieve(control, server, name, is_directory, content_cb) control:close() return answer end - answer = %Private.receive_indirect(data, content_cb) + answer = Private.receive_indirect(data, content_cb) if answer then control:close() return answer end data:close() -- make sure file transfered ok - return %Private.check_answer(control, {226, 250}) + return Private.check_answer(control, {226, 250}) end ----------------------------------------------------------------------------- @@ -347,7 +340,7 @@ end ----------------------------------------------------------------------------- function Private.store(control, server, file, send_cb) local data, err - local code, answer = %Private.command(control, "stor", file, {150, 125}) + local code, answer = Private.command(control, "stor", file, {150, 125}) if not code then control:close() return nil, answer @@ -360,7 +353,7 @@ function Private.store(control, server, file, send_cb) return nil, answer end -- send whole file - err = %Private.send_indirect(data, send_cb, send_cb()) + err = Private.send_indirect(data, send_cb, send_cb()) if err then control:close() return nil, err @@ -368,7 +361,7 @@ function Private.store(control, server, file, send_cb) -- close connection to inform that file transmission is complete data:close() -- check if file was received correctly - return %Private.check_answer(control, {226, 250}) + return Private.check_answer(control, {226, 250}) end ----------------------------------------------------------------------------- @@ -383,7 +376,7 @@ function Private.change_type(control, params) local type, _ _, _, type = strfind(params or "", "type=(.)") if type == "a" or type == "i" then - local code, err = %Private.command(control, "type", type, {200}) + local code, err = Private.command(control, "type", type, {200}) if not code then return err end end end @@ -401,12 +394,12 @@ function Private.open(parsed) local control, err = connect(parsed.host, parsed.port) if not control then return nil, err end -- make sure we don't block forever - control:timeout(%Public.TIMEOUT) + control:timeout(Public.TIMEOUT) -- check greeting - local code, answer = %Private.greet(control) + local code, answer = Private.greet(control) if not code then return nil, answer end -- try to log in - code, err = %Private.login(control, parsed.user, parsed.password) + code, err = Private.login(control, parsed.user, parsed.password) if not code then return nil, err else return control end end @@ -418,7 +411,7 @@ end ----------------------------------------------------------------------------- function Private.close(control) -- disconnect - %Private.logout(control) + Private.logout(control) end ----------------------------------------------------------------------------- @@ -432,7 +425,7 @@ end function Private.change_dir(control, segment) local n = getn(segment) for i = 1, n-1 do - local code, answer = %Private.cwd(control, segment[i]) + local code, answer = Private.cwd(control, segment[i]) if not code then return answer end end end @@ -457,10 +450,10 @@ function Private.upload(control, request, segment) end content_cb = request.content_cb -- setup passive connection - local server, answer = %Private.port(control) + local server, answer = Private.port(control) if not server then return answer end -- ask server to receive file - code, answer = %Private.store(control, server, name, content_cb) + code, answer = Private.store(control, server, name, content_cb) if not code then return answer end end @@ -485,10 +478,10 @@ function Private.download(control, request, segment) return "Invalid file path" end -- setup passive connection - local server, answer = %Private.port(control) + local server, answer = Private.port(control) if not server then return answer end -- ask server to send file or directory listing - code, answer = %Private.retrieve(control, server, name, + code, answer = Private.retrieve(control, server, name, is_directory, content_cb) if not code then return answer end end @@ -510,7 +503,7 @@ function Private.parse_url(request) user = "anonymous", port = 21, path = "/", - password = %Public.EMAIL, + password = Public.EMAIL, scheme = "ftp" }) -- explicit login information overrides that given by URL @@ -565,17 +558,17 @@ end -- err: error message if any ----------------------------------------------------------------------------- function Public.get_cb(request) - local parsed = %Private.parse_url(request) + local parsed = Private.parse_url(request) if parsed.scheme ~= "ftp" then return format("unknown scheme '%s'", parsed.scheme) end - local control, err = %Private.open(parsed) + local control, err = Private.open(parsed) if not control then return err end - local segment = %Private.parse_path(parsed) - return %Private.change_dir(control, segment) or - %Private.change_type(control, parsed.params) or - %Private.download(control, request, segment) or - %Private.close(control) + local segment = Private.parse_path(parsed) + return Private.change_dir(control, segment) or + Private.change_type(control, parsed.params) or + Private.download(control, request, segment) or + Private.close(control) end ----------------------------------------------------------------------------- @@ -591,17 +584,17 @@ end -- err: error message if any ----------------------------------------------------------------------------- function Public.put_cb(request) - local parsed = %Private.parse_url(request) + local parsed = Private.parse_url(request) if parsed.scheme ~= "ftp" then return format("unknown scheme '%s'", parsed.scheme) end - local control, err = %Private.open(parsed) + local control, err = Private.open(parsed) if not control then return err end - local segment = %Private.parse_path(parsed) - return %Private.change_dir(control, segment) or - %Private.change_type(control, parsed.params) or - %Private.upload(control, request, segment) or - %Private.close(control) + local segment = Private.parse_path(parsed) + return Private.change_dir(control, segment) or + Private.change_type(control, parsed.params) or + Private.upload(control, request, segment) or + Private.close(control) end ----------------------------------------------------------------------------- @@ -617,11 +610,11 @@ end -- err: error message if any ----------------------------------------------------------------------------- function Public.put(url_or_request, content) - local request = %Private.build_request(url_or_request) + local request = Private.build_request(url_or_request) request.content_cb = function() - return %content, strlen(%content) + return content, strlen(content) end - return %Public.put_cb(request) + return Public.put_cb(request) end ----------------------------------------------------------------------------- @@ -638,11 +631,11 @@ end ----------------------------------------------------------------------------- function Public.get(url_or_request) local cat = Concat.create() - local request = %Private.build_request(url_or_request) + local request = Private.build_request(url_or_request) request.content_cb = function(chunk, err) - if chunk then %cat:addstring(chunk) end + if chunk then cat:addstring(chunk) end return 1 end - local err = %Public.get_cb(request) + local err = Public.get_cb(request) return cat:getresult(), err end diff --git a/src/smtp.lua b/src/smtp.lua index 7450792..72a0e5a 100644 --- a/src/smtp.lua +++ b/src/smtp.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- SMTP support for the Lua language. --- LuaSocket 1.4 toolkit +-- LuaSocket 1.5 toolkit -- Author: Diego Nehab -- Date: 26/12/2000 -- Conforming to: RFC 821, LTN7 @@ -65,7 +65,7 @@ function Private.send_command(sock, command, param) local line if param then line = command .. " " .. param .. "\r\n" else line = command .. "\r\n" end - return %Private.try_send(sock, line) + return Private.try_send(sock, line) end ----------------------------------------------------------------------------- @@ -78,14 +78,14 @@ end ----------------------------------------------------------------------------- function Private.get_answer(control) local code, lastcode, sep, _ - local line, err = %Private.try_receive(control) + local line, err = Private.try_receive(control) local answer = line if err then return nil, err end _,_, code, sep = strfind(line, "^(%d%d%d)(.)") if not code or not sep then return nil, answer end if sep == "-" then -- answer is multiline repeat - line, err = %Private.try_receive(control) + line, err = Private.try_receive(control) if err then return nil, err end _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)") answer = answer .. "\n" .. line @@ -105,7 +105,7 @@ end -- answer: complete server answer or system error message ----------------------------------------------------------------------------- function Private.check_answer(control, success) - local answer, code = %Private.get_answer(control) + local answer, code = Private.get_answer(control) if not answer then return nil, code end if type(success) ~= "table" then success = {success} end for i = 1, getn(success) do @@ -126,9 +126,9 @@ end -- answer: complete server reply ----------------------------------------------------------------------------- function Private.send_helo(sock) - local err = %Private.send_command(sock, "HELO", %Public.DOMAIN) + local err = Private.send_command(sock, "HELO", Public.DOMAIN) if err then return nil, err end - return %Private.check_answer(sock, 250) + return Private.check_answer(sock, 250) end ----------------------------------------------------------------------------- @@ -140,9 +140,9 @@ end -- answer: complete server reply or error message ----------------------------------------------------------------------------- function Private.send_quit(sock) - local err = %Private.send_command(sock, "QUIT") + local err = Private.send_command(sock, "QUIT") if err then return nil, err end - local code, answer = %Private.check_answer(sock, 221) + local code, answer = Private.check_answer(sock, 221) sock:close() return code, answer end @@ -158,9 +158,9 @@ end ----------------------------------------------------------------------------- function Private.send_mail(sock, sender) local param = format("FROM:<%s>", sender or "") - local err = %Private.send_command(sock, "MAIL", param) + local err = Private.send_command(sock, "MAIL", param) if err then return nil, err end - return %Private.check_answer(sock, 250) + return Private.check_answer(sock, 250) end ----------------------------------------------------------------------------- @@ -175,11 +175,11 @@ function Private.send_headers(sock, headers) local err -- send request headers for i, v in headers or {} do - err = %Private.try_send(sock, i .. ": " .. v .. "\r\n") + err = Private.try_send(sock, i .. ": " .. v .. "\r\n") if err then return err end end -- mark end of request headers - return %Private.try_send(sock, "\r\n") + return Private.try_send(sock, "\r\n") end ----------------------------------------------------------------------------- @@ -193,18 +193,18 @@ end -- answer: complete server reply or error message ----------------------------------------------------------------------------- function Private.send_data(sock, headers, body) - local err = %Private.send_command(sock, "DATA") + local err = Private.send_command(sock, "DATA") if err then return nil, err end - local code, answer = %Private.check_answer(sock, 354) + local code, answer = Private.check_answer(sock, 354) if not code then return nil, answer end -- avoid premature end in message body body = gsub(body or "", "\n%.", "\n%.%.") -- mark end of message body body = body .. "\r\n.\r\n" - err = %Private.send_headers(sock, headers) + err = Private.send_headers(sock, headers) if err then return nil, err end - err = %Private.try_send(sock, body) - return %Private.check_answer(sock, 250) + err = Private.try_send(sock, body) + return Private.check_answer(sock, 250) end ----------------------------------------------------------------------------- @@ -221,9 +221,9 @@ function Private.send_rcpt(sock, rcpt) local code, answer = nil, "No recipient specified" if type(rcpt) ~= "table" then rcpt = {rcpt} end for i = 1, getn(rcpt) do - err = %Private.send_command(sock, "RCPT", format("TO:<%s>", rcpt[i])) + err = Private.send_command(sock, "RCPT", format("TO:<%s>", rcpt[i])) if err then return nil, err end - code, answer = %Private.check_answer(sock, {250, 251}) + code, answer = Private.check_answer(sock, {250, 251}) if not code then return code, answer end end return code, answer @@ -240,16 +240,16 @@ end function Private.open(server) local code, answer -- default server - server = server or %Public.SERVER + server = server or Public.SERVER -- connect to server and make sure we won't hang - local sock, err = connect(server, %Public.PORT) + local sock, err = connect(server, Public.PORT) if not sock then return nil, err end - sock:timeout(%Public.TIMEOUT) + sock:timeout(Public.TIMEOUT) -- initial server greeting - code, answer = %Private.check_answer(sock, 220) + code, answer = Private.check_answer(sock, 220) if not code then return nil, answer end -- HELO - code, answer = %Private.send_helo(sock) + code, answer = Private.send_helo(sock) if not code then return nil, answer end return sock end @@ -270,13 +270,13 @@ end function Private.send(sock, message) local code, answer -- MAIL - code, answer = %Private.send_mail(sock, message.from) + code, answer = Private.send_mail(sock, message.from) if not code then return nil, answer end -- RCPT - code, answer = %Private.send_rcpt(sock, message.rcpt) + code, answer = Private.send_rcpt(sock, message.rcpt) if not code then return nil, answer end -- DATA - return %Private.send_data(sock, message.headers, message.body) + return Private.send_data(sock, message.headers, message.body) end ----------------------------------------------------------------------------- @@ -289,7 +289,7 @@ end ----------------------------------------------------------------------------- function Private.close(sock) -- QUIT - return %Private.send_quit(sock) + return Private.send_quit(sock) end ----------------------------------------------------------------------------- @@ -305,11 +305,11 @@ end -- nil if successfull, error message in case of error ----------------------------------------------------------------------------- function Public.mail(message) - local sock, err = %Private.open(message.server) + local sock, err = Private.open(message.server) if not sock then return err end - local code, answer = %Private.send(sock, message) + local code, answer = Private.send(sock, message) if not code then return answer end - code, answer = %Private.close(sock) + code, answer = Private.close(sock) if code then return nil end return answer end diff --git a/src/url.lua b/src/url.lua index 673c9ac..e17bcf5 100644 --- a/src/url.lua +++ b/src/url.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- URI parsing, composition and relative URL resolution --- LuaSocket 1.4 toolkit. +-- LuaSocket 1.5 toolkit. -- Author: Diego Nehab -- Date: 20/7/2001 -- Conforming to: RFC 2396, LTN7 @@ -36,24 +36,24 @@ function Public.parse_url(url, default) -- remove whitespace url = gsub(url, "%s", "") -- get fragment - url = gsub(url, "#(.*)$", function(f) %parsed.fragment = f end) + url = gsub(url, "#(.*)$", function(f) parsed.fragment = f end) -- get scheme - url = gsub(url, "^([%w][%w%+%-%.]*)%:", function(s) %parsed.scheme = s end) + url = gsub(url, "^([%w][%w%+%-%.]*)%:", function(s) parsed.scheme = s end) -- get authority - url = gsub(url, "^//([^/]*)", function(n) %parsed.authority = n end) + url = gsub(url, "^//([^/]*)", function(n) parsed.authority = n end) -- get query string - url = gsub(url, "%?(.*)", function(q) %parsed.query = q end) + url = gsub(url, "%?(.*)", function(q) parsed.query = q end) -- get params - url = gsub(url, "%;(.*)", function(p) %parsed.params = p end) + url = gsub(url, "%;(.*)", function(p) parsed.params = p end) if url ~= "" then parsed.path = url end local authority = parsed.authority if not authority then return parsed end - authority = gsub(authority,"^([^@]*)@",function(u) %parsed.userinfo = u end) - authority = gsub(authority, ":([^:]*)$", function(p) %parsed.port = p end) + authority = gsub(authority,"^([^@]*)@",function(u) parsed.userinfo = u end) + authority = gsub(authority, ":([^:]*)$", function(p) parsed.port = p end) if authority ~= "" then parsed.host = authority end local userinfo = parsed.userinfo if not userinfo then return parsed end - userinfo = gsub(userinfo, ":([^:]*)$", function(p) %parsed.password = p end) + userinfo = gsub(userinfo, ":([^:]*)$", function(p) parsed.password = p end) parsed.user = userinfo return parsed end @@ -99,8 +99,8 @@ end -- corresponding absolute url ----------------------------------------------------------------------------- function Public.absolute_url(base_url, relative_url) - local base = %Public.parse_url(base_url) - local relative = %Public.parse_url(relative_url) + local base = Public.parse_url(base_url) + local relative = Public.parse_url(relative_url) if not base then return relative_url elseif not relative then return base_url elseif relative.scheme then return relative_url @@ -117,10 +117,10 @@ function Public.absolute_url(base_url, relative_url) end end else - relative.path = %Private.absolute_path(base.path,relative.path) + relative.path = Private.absolute_path(base.path,relative.path) end end - return %Public.build_url(relative) + return Public.build_url(relative) end end @@ -135,7 +135,7 @@ function Public.parse_path(path) local parsed = {} path = path or "" path = gsub(path, "%s", "") - gsub(path, "([^/]+)", function (s) tinsert(%parsed, s) end) + gsub(path, "([^/]+)", function (s) tinsert(parsed, s) end) for i = 1, getn(parsed) do parsed[i] = Code.unescape(parsed[i]) end @@ -166,11 +166,11 @@ function Public.build_path(parsed, unsafe) end else for i = 1, n-1 do - path = path .. %Private.protect_segment(parsed[i]) + path = path .. Private.protect_segment(parsed[i]) path = path .. "/" end if n > 0 then - path = path .. %Private.protect_segment(parsed[n]) + path = path .. Private.protect_segment(parsed[n]) if parsed.is_directory then path = path .. "/" end end end @@ -194,9 +194,9 @@ Private.segment_set = Private.make_set { } function Private.protect_segment(s) - local segment_set = %Private.segment_set + local segment_set = Private.segment_set return gsub(s, "(%W)", function (c) - if %segment_set[c] then return c + if segment_set[c] then return c else return Code.escape(c) end end) end From 65dd6185533031421fef4a9fb5f1c919124c86a6 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 8 Jul 2002 21:53:33 +0000 Subject: [PATCH 103/483] =?UTF-8?q?Using=20noglobals.lua=20Ajeitados=20alg?= =?UTF-8?q?uns=20paths.=20N=C3=A3o=20carrega=20mais=20os=20modulos.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/ftptest.lua | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/test/ftptest.lua b/test/ftptest.lua index 7fc1917..34cccf1 100644 --- a/test/ftptest.lua +++ b/test/ftptest.lua @@ -1,18 +1,4 @@ -function mysetglobal (varname, oldvalue, newvalue) - print("changing " .. varname) - %rawset(%globals(), varname, newvalue) -end -function mygetglobal (varname, newvalue) - print("checking " .. varname) - return %rawget(%globals(), varname) -end -settagmethod(tag(nil), "setglobal", mysetglobal) -settagmethod(tag(nil), "getglobal", mygetglobal) - -assert(dofile("../lua/ftp.lua")) -assert(dofile("../lua/url.lua")) -assert(dofile("../lua/concat.lua")) -assert(dofile("../lua/code.lua")) +dofile("noglobals.lua") local similar = function(s1, s2) return strlower(gsub(s1, "%s", "")) == strlower(gsub(s2, "%s", "")) @@ -50,9 +36,9 @@ local t = _time() index = readfile("index.html") write("testing file upload: ") -remove("/home/ftp/dir1/index.up.html") +remove("/var/ftp/dir1/index.up.html") err = FTP.put("ftp://localhost/dir1/index.up.html;type=i", index) -saved = readfile("/home/ftp/dir1/index.up.html") +saved = readfile("/var/ftp/dir1/index.up.html") check(not err and saved == index, err) write("testing file download: ") @@ -78,7 +64,7 @@ back, err = FTP.get("ftp://luasocket:password@localhost/index.up.html;type=i") check(not err and back == index, err) write("testing weird-character translation: ") -back, err = FTP.get("ftp://luasocket:password@localhost/%2fhome/ftp/dir1/index.html;type=i") +back, err = FTP.get("ftp://luasocket:password@localhost/%2fvar/ftp/dir1/index.html;type=i") check(not err and back == index, err) write("testing parameter overriding: ") @@ -100,12 +86,12 @@ local c, e = connect("", 21) check(not back and err == e, err) write("testing directory listing: ") -expected = capture("ls -F /home/ftp/dir1 | grep -v /") +expected = capture("ls -F /var/ftp/dir1 | grep -v /") back, err = FTP.get("ftp://localhost/dir1;type=d") check(similar(back, expected)) write("testing home directory listing: ") -expected = capture("ls -F /home/ftp | grep -v /") +expected = capture("ls -F /var/ftp | grep -v /") back, err = FTP.get("ftp://localhost/") check(back and similar(back, expected), nil, err) From daff3db32ed52aa18dbe1e8c613535fbe0c622c7 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 8 Jul 2002 21:54:28 +0000 Subject: [PATCH 104/483] =?UTF-8?q?=20Using=20noglobals.lua=20N=C3=A3o=20c?= =?UTF-8?q?arrega=20mais=20os=20modulos.=20Ajeitados=20alguns=20nomes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/httptest.lua | 71 ++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/test/httptest.lua b/test/httptest.lua index 86cb8af..85c8bd8 100644 --- a/test/httptest.lua +++ b/test/httptest.lua @@ -1,21 +1,14 @@ --- needs Alias from /home/i/diego/public/html/luasocket/test to +-- needs Alias from /home/c/diego/tec/luasocket/test to -- /luasocket-test --- needs ScriptAlias from /home/i/diego/public/html/luasocket/test/cgi --- to /luasocket-test/cgi +-- needs ScriptAlias from /home/c/diego/tec/luasocket/test/cgi +-- to /luasocket-test-cgi +-- needs AllowOverride AuthConfig on /home/c/diego/tec/luasocket/test/auth -function mysetglobal (varname, oldvalue, newvalue) - print("changing " .. varname) - %rawset(%globals(), varname, newvalue) -end -function mygetglobal (varname, newvalue) - print("checking " .. varname) - return %rawget(%globals(), varname) -end -settagmethod(tag(nil), "setglobal", mysetglobal) -settagmethod(tag(nil), "getglobal", mygetglobal) +dofile("noglobals.lua") local similar = function(s1, s2) - return strlower(gsub(s1, "%s", "")) == strlower(gsub(s2, "%s", "")) + return strlower(gsub(s1 or "", "%s", "")) == + strlower(gsub(s2 or "", "%s", "")) end local fail = function(s) @@ -52,36 +45,32 @@ local check_request = function(request, expect, ignore) print("ok") end -local host, request, response, ignore, expect, index, prefix, cgiprefix +dofile("../src/modules/http.lua") --- load http -assert(dofile("../lua/http.lua")) -assert(dofile("../lua/code.lua")) -assert(dofile("../lua/concat.lua")) -assert(dofile("../lua/url.lua")) +local request, response, ignore, expect, index, prefix, cgiprefix local t = _time() -host = host or "localhost" +HOST = HOST or "localhost" prefix = prefix or "/luasocket-test" cgiprefix = cgiprefix or "/luasocket-test-cgi" index = readfile("index.html") write("testing request uri correctness: ") local forth = cgiprefix .. "/request-uri?" .. "this+is+the+query+string" -local back = HTTP.get("http://" .. host .. forth) +local back = HTTP.get("http://" .. HOST .. forth) if similar(back, forth) then print("ok") else fail("failed!") end write("testing query string correctness: ") forth = "this+is+the+query+string" -back = HTTP.get("http://" .. host .. cgiprefix .. "/query-string?" .. forth) +back = HTTP.get("http://" .. HOST .. cgiprefix .. "/query-string?" .. forth) if similar(back, forth) then print("ok") else fail("failed!") end write("testing document retrieval: ") request = { - url = "http://" .. host .. prefix .. "/index.html" + url = "http://" .. HOST .. prefix .. "/index.html" } expect = { body = index, @@ -95,7 +84,7 @@ check_request(request, expect, ignore) write("testing HTTP redirection: ") request = { - url = "http://" .. host .. prefix + url = "http://" .. HOST .. prefix } expect = { body = index, @@ -110,7 +99,7 @@ check_request(request, expect, ignore) write("testing automatic auth failure: ") request = { - url = "http://really:wrong@" .. host .. prefix .. "/auth/index.html" + url = "http://really:wrong@" .. HOST .. prefix .. "/auth/index.html" } expect = { code = 401 @@ -124,7 +113,7 @@ check_request(request, expect, ignore) write("testing HTTP redirection failure: ") request = { - url = "http://" .. host .. prefix, + url = "http://" .. HOST .. prefix, stay = 1 } expect = { @@ -150,7 +139,7 @@ check_request(request, expect, ignore) write("testing invalid url: ") request = { - url = host .. prefix + url = HOST .. prefix } local c, e = connect("", 80) expect = { @@ -161,7 +150,7 @@ check_request(request, expect, ignore) write("testing document not found: ") request = { - url = "http://" .. host .. "/wrongdocument.html" + url = "http://" .. HOST .. "/wrongdocument.html" } expect = { code = 404 @@ -175,7 +164,7 @@ check_request(request, expect, ignore) write("testing auth failure: ") request = { - url = "http://" .. host .. prefix .. "/auth/index.html" + url = "http://" .. HOST .. prefix .. "/auth/index.html" } expect = { code = 401 @@ -189,7 +178,7 @@ check_request(request, expect, ignore) write("testing manual basic auth: ") request = { - url = "http://" .. host .. prefix .. "/auth/index.html", + url = "http://" .. HOST .. prefix .. "/auth/index.html", headers = { authorization = "Basic " .. Code.base64("luasocket:password") } @@ -206,7 +195,7 @@ check_request(request, expect, ignore) write("testing automatic basic auth: ") request = { - url = "http://luasocket:password@" .. host .. prefix .. "/auth/index.html" + url = "http://luasocket:password@" .. HOST .. prefix .. "/auth/index.html" } expect = { code = 200, @@ -220,7 +209,7 @@ check_request(request, expect, ignore) write("testing auth info overriding: ") request = { - url = "http://really:wrong@" .. host .. prefix .. "/auth/index.html", + url = "http://really:wrong@" .. HOST .. prefix .. "/auth/index.html", user = "luasocket", password = "password" } @@ -236,7 +225,7 @@ check_request(request, expect, ignore) write("testing cgi output retrieval (probably chunked...): ") request = { - url = "http://" .. host .. cgiprefix .. "/cat-index-html" + url = "http://" .. HOST .. cgiprefix .. "/cat-index-html" } expect = { body = index, @@ -250,7 +239,7 @@ check_request(request, expect, ignore) write("testing redirect loop: ") request = { - url = "http://" .. host .. cgiprefix .. "/redirect-loop" + url = "http://" .. HOST .. cgiprefix .. "/redirect-loop" } expect = { code = 302 @@ -264,7 +253,7 @@ check_request(request, expect, ignore) write("testing post method: ") request = { - url = "http://" .. host .. cgiprefix .. "/cat", + url = "http://" .. HOST .. cgiprefix .. "/cat", method = "POST", body = index } @@ -280,7 +269,7 @@ check_request(request, expect, ignore) write("testing wrong scheme: ") request = { - url = "wrong://" .. host .. cgiprefix .. "/cat", + url = "wrong://" .. HOST .. cgiprefix .. "/cat", method = "GET" } expect = { @@ -292,24 +281,24 @@ check_request(request, expect, ignore) local body write("testing simple get function: ") -body = HTTP.get("http://" .. host .. prefix .. "/index.html") +body = HTTP.get("http://" .. HOST .. prefix .. "/index.html") check(body == index) write("testing simple get function with table args: ") body = HTTP.get { - url = "http://really:wrong@" .. host .. prefix .. "/auth/index.html", + url = "http://really:wrong@" .. HOST .. prefix .. "/auth/index.html", user = "luasocket", password = "password" } check(body == index) write("testing simple post function: ") -body = HTTP.post("http://" .. host .. cgiprefix .. "/cat", index) +body = HTTP.post("http://" .. HOST .. cgiprefix .. "/cat", index) check(body == index) write("testing simple post function with table args: ") body = HTTP.post { - url = "http://" .. host .. cgiprefix .. "/cat", + url = "http://" .. HOST .. cgiprefix .. "/cat", body = index } check(body == index) From 0fc2302221e80a8f2d55808320073024a02517e8 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 8 Jul 2002 21:55:01 +0000 Subject: [PATCH 105/483] Initial revision --- src/mbox.lua | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/mbox.lua diff --git a/src/mbox.lua b/src/mbox.lua new file mode 100644 index 0000000..9cce9ff --- /dev/null +++ b/src/mbox.lua @@ -0,0 +1,45 @@ +local Public = {} + +parse = Public + +function Public.headers(headers_s) + local headers = {} + headers_s = "\n" .. headers_s .. "$$$:\n" + local i, j = 1, 1 + local name, value, _ + while 1 do + j = strfind(headers_s, "\n%S-:", i+1) + if not j then break end + _,_, name, value = strfind(strsub(headers_s, i+1, j-1), "(%S-):%s?(.*)") + value = gsub(value or "", "\r\n", "\n") + value = gsub(value, "\n%s*", " ") + name = strlower(name) + if headers[name] then headers[name] = headers[name] .. ", " .. value + else headers[name] = value end + i, j = j, i + end + headers["$$$"] = nil + return headers +end + +function Public.message(message_s) + message_s = gsub(message_s, "^.-\n", "") + local _, headers_s, body + _, _, headers_s, body = strfind(message_s, "^(.-\n)\n(.*)") + headers_s = headers_s or "" + body = body or "" + return { headers = %Public.headers(headers_s), body = body } +end + +function Public.mbox(mbox_s) + local mbox = {} + mbox_s = "\n" .. mbox_s .. "\nFrom " + local i, j = 1, 1 + while 1 do + j = strfind(mbox_s, "\nFrom ", i + 1) + if not j then break end + tinsert(mbox, %Public.message(strsub(mbox_s, i + 1, j - 1))) + i, j = j, i + end + return mbox +end From af181244d9670ec8f6ef83c3548060c35fa9bf0e Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 8 Jul 2002 21:55:35 +0000 Subject: [PATCH 106/483] Usando noglobals.lua e parsembox.lua --- test/smtptest.lua | 60 +++-------------------------------------------- 1 file changed, 3 insertions(+), 57 deletions(-) diff --git a/test/smtptest.lua b/test/smtptest.lua index fcd238b..6b01134 100644 --- a/test/smtptest.lua +++ b/test/smtptest.lua @@ -4,26 +4,14 @@ local from = "luasock@tecgraf.puc-rio.br" local server = "mail.tecgraf.puc-rio.br" local rcpt = "luasock@tecgraf.puc-rio.br" -local parse = {} - local name = "/var/spool/mail/luasock" local t = _time() local err -function mysetglobal (varname, oldvalue, newvalue) - print("changing " .. varname) - %rawset(%globals(), varname, newvalue) -end -function mygetglobal (varname, newvalue) - print("checking " .. varname) - return %rawget(%globals(), varname) -end -settagmethod(tag(nil), "setglobal", mysetglobal) -settagmethod(tag(nil), "getglobal", mygetglobal) - -assert(dofile("../lua/smtp.lua")) -assert(dofile("../lua/cl-compat.lua")) +dofile("parsembox.lua") +local parse = parse +dofile("noglobals.lua") local total = function() local t = 0 @@ -37,48 +25,6 @@ local similar = function(s1, s2) return strlower(gsub(s1, "%s", "")) == strlower(gsub(s2, "%s", "")) end -function parse.headers(headers_s) - local headers = {} - headers_s = "\n" .. headers_s .. "$$$:\n" - local i, j = 1, 1 - local name, value, _ - while 1 do - j = strfind(headers_s, "\n%S-:", i+1) - if not j then break end - _, _, name, value = strfind(strsub(headers_s, i+1, j-1), "(%S-): (.*)") - value = gsub(value, "\r\n", "\n") - value = gsub(value, "\n%s*", " ") - name = strlower(name) - if headers[name] then headers[name] = headers[name] .. ", " .. value - else headers[name] = value end - i, j = j, i - end - headers["$$$"] = nil - return headers -end - -function parse.message(message_s) - message_s = gsub(message_s, "^.-\n", "") - local _, headers_s, body - _, _, headers_s, body = strfind(message_s, "^(.-\n)\n(.*)") - headers_s = headers_s or "" - body = body or "" - return { headers = %parse.headers(headers_s), body = body } -end - -function parse.mbox(mbox_s) - local mbox = {} - mbox_s = "\n" .. mbox_s .. "\nFrom " - local i, j = 1, 1 - while 1 do - j = strfind(mbox_s, "\nFrom ", i + 1) - if not j then break end - tinsert(mbox, %parse.message(strsub(mbox_s, i + 1, j - 1))) - i, j = j, i - end - return mbox -end - local readfile = function(name) local f = readfrom(name) if not f then return nil end From 7b991956498fece58bc9beacfd64fe9eb73520a5 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 8 Jul 2002 21:56:01 +0000 Subject: [PATCH 107/483] Testes reformulados. --- test/testclnt.lua | 871 +++++++++++++++++++++++----------------------- test/testsrvr.lua | 114 ++---- 2 files changed, 465 insertions(+), 520 deletions(-) diff --git a/test/testclnt.lua b/test/testclnt.lua index 4ee2c48..73c10de 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -1,467 +1,484 @@ ------------------------------------------------------------------------------ --- LuaSocket automated test module --- testclnt.lua --- This is the client module. It connects with the server module and executes --- all tests. ------------------------------------------------------------------------------ - ------------------------------------------------------------------------------ --- Read command definitions ------------------------------------------------------------------------------ HOST = HOST or "localhost" -assert(dofile("testcmd.lua")) -test_debug_mode() +PORT = PORT or "8080" ------------------------------------------------------------------------------ --- Start control connection ------------------------------------------------------------------------------ -new_test("initializing...") -while control == nil do - print("client: trying control connection...") - control, err = connect(HOST, PORT) - if control then - print("client: control connection stablished!") - else - _sleep(2) - end +function pass(...) + local s = call(format, arg) + write(s, "\n") end ------------------------------------------------------------------------------ --- Make sure server is ready for data transmission ------------------------------------------------------------------------------ -function sync() - send_command(SYNC) - get_command() +function fail(...) + local s = call(format, arg) + write("ERROR: ", s, "!\n") + exit() end ------------------------------------------------------------------------------ --- Close and reopen data connection, to get rid of any unread blocks ------------------------------------------------------------------------------ -function reconnect() - if data then - data:close() - send_command(CLOSE) - data = nil - end - while data == nil do - send_command(CONNECT) - data = connect(HOST, PORT) - if not data then - print("client: waiting for data connection.") - _sleep(1) - end - end - sync() +function warn(...) + local s = call(format, arg) + write("WARNING: ", s, "\n") end ------------------------------------------------------------------------------ --- Tests the command connection ------------------------------------------------------------------------------ -function test_command(cmd, par) - local cmd_back, par_back - reconnect() - send_command(COMMAND) - write("testing command ") - print_command(cmd, par) - send_command(cmd, par) - cmd_back, par_back = get_command() - if cmd_back ~= cmd or par_back ~= par then - fail(cmd) - else - pass() - end +function remote(...) + local s = call(format, arg) + s = gsub(s, "\n", ";") + s = gsub(s, "%s+", " ") + s = gsub(s, "^%s*", "") + control:send(s, "\n") + control:receive() end ------------------------------------------------------------------------------ --- Tests ASCII line transmission --- Input --- len: length of line to be tested ------------------------------------------------------------------------------ -function test_asciiline(len) - local str, str10, back, err - reconnect() - send_command(ECHO_LINE) - str = strrep("x", mod(len, 10)) - str10 = strrep("aZb.c#dAe?", floor(len/10)) - str = str .. str10 - write("testing ", len, " byte(s) line\n") - err = data:send(str, "\n") - if err then fail(err) end - back, err = data:receive() - if err then fail(err) end - if back == str then pass("lines match") - else fail("lines don't match") end +function test(test) + write("----------------------------------------------\n", + "testing: ", test, "\n", + "----------------------------------------------\n") end ------------------------------------------------------------------------------ --- Tests multiple pattern transmission --- Input --- len: length of line to be tested ------------------------------------------------------------------------------ -function test_multiple() - local p1 = "unix line\n" - local p2 = "dos line\r\n" - local p3 = "raw bytes" - local bp1, bp2, bp3 - reconnect() - send_command(ECHO_BLOCK, strlen(p1)+strlen(p2)+strlen(p3)) - err = data:send(p1, p2, p3) - if err then fail(err) end - bp1, bp2, bp3, err = data:receive("*lu", "*l", strlen(p3)) - if err then fail(err) end - if bp1.."\n" == p1 and bp2.."\r\n" == p2 and bp3 == p3 then - pass("patterns match") - else fail("patterns don't match") end +function check_timeout(tm, sl, elapsed, err, opp, mode, alldone) + if tm < sl then + if opp == "send" then + if not err then warn("must be buffered") + elseif err == "timeout" then pass("proper timeout") + else fail("unexpected error '%s'", err) end + else + if err ~= "timeout" then fail("should have timed out") + else pass("proper timeout") end + end + else + if mode == "return" then + if elapsed > tm then + if err ~= "timeout" then fail("should have timed out") + else pass("proper timeout") end + elseif elapsed < tm then + if err then fail(err) + else pass("ok") end + else + if alldone then + if err then fail("unexpected error '%s'", err) + else pass("ok") end + else + if err ~= "timeout" then fail(err) + else pass("proper timeoutk") end + end + end + else + if err then fail(err) + else pass("ok") end + end + end end ------------------------------------------------------------------------------ --- Tests closed connection detection ------------------------------------------------------------------------------ -function test_closed() - local str = "This is our little test line" - local len = strlen(str) - local back, err, total - reconnect() - print("testing close while reading line") - send_command(ECHO_BLOCK, len) - data:send(str) - send_command(CLOSE) - -- try to get a line - back, err = data:receive() - if not err then fail("shold have gotten 'closed'.") - elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") - elseif str ~= back then fail("didn't receive what i should 'closed'.") - else pass("rightfull 'closed' received") end - reconnect() - print("testing close while reading block") - send_command(ECHO_BLOCK, len) - data:send(str) - send_command(CLOSE) - -- try to get a line - back, err = data:receive(2*len) - if not err then fail("shold have gotten 'closed'.") - elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") - elseif str ~= back then fail("didn't receive what I should.") - else pass("rightfull 'closed' received") end -end +write("----------------------------------------------\n", +"LuaSocket Test Procedures\n", +"----------------------------------------------\n") ------------------------------------------------------------------------------ --- Tests binary line transmission --- Input --- len: length of line to be tested ------------------------------------------------------------------------------ -function test_rawline(len) - local str, str10, back, err - reconnect() - send_command(ECHO_LINE) - str = strrep(strchar(47), mod(len, 10)) - str10 = strrep(strchar(120,21,77,4,5,0,7,36,44,100), floor(len/10)) - str = str .. str10 - write("testing ", len, " byte(s) line\n") - err = data:send(str, "\n") - if err then fail(err) end - back, err = data:receive() - if err then fail(err) end - if back == str then pass("lines match") - else fail("lines don't match") end -end +if not _time or not _sleep then fail("not compiled with _DEBUG") end ------------------------------------------------------------------------------ --- Tests block transmission --- Input --- len: length of block to be tested ------------------------------------------------------------------------------ -function test_block(len) - local half = floor(len/2) - local s1, s2, back, err - reconnect() - send_command(ECHO_BLOCK, len) - write("testing ", len, " byte(s) block\n") - s1 = strrep("x", half) - err = data:send(s1) - if err then fail(err) end - s2 = strrep("y", len-half) - err = data:send(s2) - if err then fail(err) end - back, err = data:receive(len) - if err then fail(err) end - if back == s1..s2 then pass("blocks match") - else fail("blocks don't match") end -end - ------------------------------------------------------------------------------ --- Tests if return-timeout was respected --- delta: time elapsed during transfer --- t: timeout value --- s: time server slept --- err: error code returned by I/O operation --- o: operation being executed ------------------------------------------------------------------------------ -function blockedtimed_out(t, s, err, o) - if err == "timeout" then - if s >= t then - pass("got rightfull forced timeout") - return 1 - else - pass("got natural cause timeout") - return 1 - end - elseif s > t then - if o == "send" then - pass("must have been buffered (may be wrong)") - else - fail("should have gotten timeout") - end - end -end - ------------------------------------------------------------------------------ --- Tests blocked-timeout conformance --- Input --- len: length of block to be tested --- t: timeout value --- s: server sleep between transfers ------------------------------------------------------------------------------ -function test_blockedtimeout(len, t, s) - local str, err, back, total - reconnect() - send_command(RECEIVE_BLOCK, len) - send_command(SLEEP, s) - send_command(RECEIVE_BLOCK, len) - write("testing ", len, " bytes, ", t, - "s block timeout, ", s, "s sleep\n") - data:timeout(t) - str = strrep("a", 2*len) - err, total = data:send(str) - if blockedtimed_out(t, s, err, "send") then return end - if err then fail(err) end - send_command(SEND_BLOCK) - send_command(SLEEP, s) - send_command(SEND_BLOCK) - back, err = data:receive(2*len) - if blockedtimed_out(t, s, err, "receive") then return end - if err then fail(err) end - if back == str then pass("blocks match") - else fail("blocks don't match") end -end - ------------------------------------------------------------------------------ --- Tests if return-timeout was respected --- delta: time elapsed during transfer --- t: timeout value --- err: error code returned by I/O operation ------------------------------------------------------------------------------ -function returntimed_out(delta, t, err) - if err == "timeout" then - if delta >= t then - pass("got rightfull timeout") - return 1 - else - fail("shouldn't have gotten timeout") - end - elseif delta > t then - pass(format("but took %fs longer than should have", delta - t)) - end -end - ------------------------------------------------------------------------------ --- Tests return-timeout conformance --- Input --- len: length of block to be tested --- t: timeout value --- s: server sleep between transfers ------------------------------------------------------------------------------ -function test_returntimeout(len, t, s) - local str, err, back, delta, total - reconnect() - send_command(RECEIVE_BLOCK, len) - send_command(SLEEP, s) - send_command(RECEIVE_BLOCK, len) - write("testing ", len, " bytes, ", t, - "s return timeout, ", s, "s sleep\n") - data:timeout(t, "return") - str = strrep("a", 2*len) - err, total, delta = data:send(str) - print("sent in " .. delta .. "s") - if returntimed_out(delta, t, err) then return end - if err then fail("unexpected error: " .. err) end - send_command(SEND_BLOCK) - send_command(SLEEP, s) - send_command(SEND_BLOCK) - back, err, delta = data:receive(2*len) - print("received in " .. delta .. "s") - if returntimed_out(delta, t, err) then return end - if err then fail("unexpected error: " .. err) end - if back == str then pass("blocks match") - else fail("blocks don't match") end -end - ------------------------------------------------------------------------------ --- Tests read patterns ------------------------------------------------------------------------------ -function test_word() - local b1 = " \t one two three \n this_is_a_very" - local b2 = "_big_word " - send_command(ECHO_BLOCK, strlen(b1)+strlen(b2)) - err = data:send(b1, b2) - local a1, a2, a3, a4 - a1, a2, a3, a4, err = data:receive("*w", "*w", "*w", "*w") - if err then fail(err) end - _, err = data:receive(1) -- get last space - if err then fail(err) end - if a1 ~= "one" or a2 ~= "two" or a3 ~= "three" or - a4 ~= "this_is_a_very_big_word" then fail("'*w' failed") end - pass("'*w' is ok") -end - -function test_patterns() - local dos_line1 = "this the first dos line" - local dos_line2 = "this is another dos line" - local unix_line1 = "this the first unix line" - local unix_line2 = "this is another unix line" - local block = dos_line1 .. "\r\n" .. dos_line2 .. "\r\n" - reconnect() - block = block .. unix_line1 .. "\n" .. unix_line2 .. "\n" - block = block .. block - send_command(ECHO_BLOCK, strlen(block)) - err = data:send(block) - if err then fail(err) end - local back = data:receive("*l") - if back ~= dos_line1 then fail("'*l' failed") end - back = data:receive("*l") - if back ~= dos_line2 then fail("'*l' failed") end - back = data:receive("*lu") - if back ~= unix_line1 then fail("'*lu' failed") end - back = data:receive("*lu") - if back ~= unix_line2 then fail("'*lu' failed") end - back = data:receive() - if back ~= dos_line1 then fail("default failed") end - back = data:receive() - if back ~= dos_line2 then fail("default failed") end - back = data:receive("*lu") - if back ~= unix_line1 then fail("'*lu' failed") end - back = data:receive("*lu") - if back ~= unix_line2 then fail("'*lu' failed") end - pass("line patterns are ok") - send_command(ECHO_BLOCK, strlen(block)) - err = data:send(block) - if err then fail(err) end - back = data:receive(strlen(block)) - if back ~= block then fail("number failed") end - pass("number is ok") - test_word() - send_command(ECHO_BLOCK, strlen(block)) - send_command(SLEEP, 1) - send_command(CLOSE) - err = data:send(block) - if err then fail(err) end - back = data:receive("*a") - if back ~= block then fail("'*a' failed") end - pass("'*a' is ok") -end - ------------------------------------------------------------------------------ --- Test for select bugs ------------------------------------------------------------------------------ -function test_select() - local r, s, e = select(nil, nil, 0.1) - assert(type(r) == "table" and type(s) == "table" and e == "timeout") - pass("both nil") - data:close() - r, s, e = select({ data }, { data }, 0.1) - assert(type(r) == "table" and type(s) == "table" and e == "timeout") - pass("closed sockets") - e = call(select, {"wrong", 1, 0.1}, "x", nil) - assert(e == nil) - pass("invalid input") -end - ------------------------------------------------------------------------------ --- Execute all tests ------------------------------------------------------------------------------ start = _time() -new_test("control connection test") -test_command(EXIT) -test_command(CONNECT) -test_command(CLOSE) -test_command(ECHO_BLOCK, 12234) -test_command(SLEEP, 1111) -test_command(ECHO_LINE) +function tcpreconnect() + write("attempting data connection... ") + if data then data:close() end + remote [[ + if data then data:close() data = nil end + data = server:accept() + ]] + data, error = connect(HOST, PORT) + if not data then fail(error) + else pass("connected!") end +end +reconnect = tcpreconnect -new_test("testing for select bugs") -test_select() +pass("attempting control connection...") +control, error = connect(HOST, PORT) +if error then fail(error) +else pass("connected!") end -new_test("connection close test") -test_closed() +------------------------------------------------------------------------ +test("bugs") -new_test("read pattern test") -test_patterns() +write("empty host connect: ") +function empty_connect() + if data then data:close() data = nil end + remote [[ + if data then data:close() data = nil end + data = server:accept() + ]] + data, err = connect("", PORT) + if not data then + pass("ok") + data = connect(HOST, PORT) + else fail("should not have connected!") end +end -new_test("multiple pattern test") -test_multiple() +empty_connect() + +------------------------------------------------------------------------ +test("method registration") + +function test_methods(sock, methods) + for _, v in methods do + if type(sock[v]) ~= "function" then + fail(type(sock) .. " method " .. v .. "not registered") + end + end + pass(type(sock) .. " methods are ok") +end + +test_methods(control, { + "close", + "timeout", + "send", + "receive", + "getpeername", + "getsockname" +}) + +if udpsocket then + test_methods(udpsocket(), { + "close", + "timeout", + "send", + "sendto", + "receive", + "receivefrom", + "getpeername", + "getsockname", + "setsockname", + "setpeername" + }) +end + +test_methods(bind("*", 0), { + "close", + "timeout", + "accept" +}) + +if pipe then + local p1, p2 = pipe() + test_methods(p1, { + "close", + "timeout", + "send", + "receive" + }) + test_methods(p2, { + "close", + "timeout", + "send", + "receive" + }) +end + +if filesocket then + test_methods(filesocket(0), { + "close", + "timeout", + "send", + "receive" + }) +end + +------------------------------------------------------------------------ +test("select function") +function test_selectbugs() + local r, s, e = select(nil, nil, 0.1) + assert(type(r) == "table" and type(s) == "table" and e == "timeout") + pass("both nil: ok") + local udp = udpsocket() + udp:close() + r, s, e = select({ data }, { data }, 0.1) + assert(type(r) == "table" and type(s) == "table" and e == "timeout") + pass("closed sockets: ok") + e = call(select, {"wrong", 1, 0.1}, "x", nil) + assert(e == nil) + pass("invalid input: ok") +end + +test_selectbugs() + +------------------------------------------------------------------------ +test("character line") +reconnect() + +function test_asciiline(len) + local str, str10, back, err + str = strrep("x", mod(len, 10)) + str10 = strrep("aZb.c#dAe?", floor(len/10)) + str = str .. str10 + pass(len .. " byte(s) line") +remote "str = data:receive()" + err = data:send(str, "\n") + if err then fail(err) end +remote "data:send(str, '\\n')" + back, err = data:receive() + if err then fail(err) end + if back == str then pass("lines match") + else fail("lines don't match") end +end -new_test("character string test") test_asciiline(1) test_asciiline(17) test_asciiline(200) -test_asciiline(3000) -test_asciiline(80000) +test_asciiline(4091) +test_asciiline(80199) test_asciiline(800000) -new_test("binary string test") +------------------------------------------------------------------------ +test("binary line") +reconnect() + +function test_rawline(len) + local str, str10, back, err + str = strrep(strchar(47), mod(len, 10)) + str10 = strrep(strchar(120,21,77,4,5,0,7,36,44,100), floor(len/10)) + str = str .. str10 + pass(len .. " byte(s) line") +remote "str = data:receive()" + err = data:send(str, "\n") + if err then fail(err) end +remote "data:send(str, '\\n')" + back, err = data:receive() + if err then fail(err) end + if back == str then pass("lines match") + else fail("lines don't match") end +end + test_rawline(1) test_rawline(17) test_rawline(200) -test_rawline(3000) -test_rawline(8000) -test_rawline(80000) +test_rawline(4091) +test_rawline(80199) test_rawline(800000) +test_rawline(80199) +test_rawline(4091) +test_rawline(200) +test_rawline(17) +test_rawline(1) -new_test("blocking transfer test") -test_block(1) -test_block(17) -test_block(200) -test_block(3000) -test_block(80000) -test_block(800000) +------------------------------------------------------------------------ +test("raw transfer") +reconnect() + +function test_raw(len) + local half = floor(len/2) + local s1, s2, back, err + s1 = strrep("x", half) + s2 = strrep("y", len-half) + pass(len .. " byte(s) block") +remote (format("str = data:receive(%d)", len)) + err = data:send(s1) + if err then fail(err) end + err = data:send(s2) + if err then fail(err) end +remote "data:send(str)" + back, err = data:receive(len) + if err then fail(err) end + if back == s1..s2 then pass("blocks match") + else fail("blocks don't match") end +end + +test_raw(1) +test_raw(17) +test_raw(200) +test_raw(4091) +test_raw(80199) +test_raw(800000) +test_raw(80199) +test_raw(4091) +test_raw(200) +test_raw(17) +test_raw(1) +------------------------------------------------------------------------ +test("non-blocking transfer") +reconnect() -new_test("non-blocking transfer test") -- the value is not important, we only want -- to test non-blockin I/O anyways data:timeout(200) -test_block(1) -test_block(17) -test_block(200) -test_block(3000) -test_block(80000) -test_block(800000) +test_raw(1) +test_raw(17) +test_raw(200) +test_raw(4091) +test_raw(80199) +test_raw(800000) +test_raw(80199) +test_raw(4091) +test_raw(200) +test_raw(17) +test_raw(1) -new_test("blocked timeout test") -test_blockedtimeout(80, 1, 2) -test_blockedtimeout(80, 2, 2) -test_blockedtimeout(80, 3, 2) -test_blockedtimeout(800, 1, 0) -test_blockedtimeout(8000, 2, 3) -test_blockedtimeout(80000, 2, 1) -test_blockedtimeout(800000, 0.01, 0) +------------------------------------------------------------------------ +test("mixed patterns") +reconnect() -new_test("return timeout test") -test_returntimeout(80, 2, 1) -test_returntimeout(80, 1, 2) -test_returntimeout(8000, 1, 2) -test_returntimeout(80000, 2, 1) -test_returntimeout(800000, 0.1, 0) -test_returntimeout(800000, 2, 1) +function test_mixed(len) + local inter = floor(len/3) + local p1 = "unix " .. strrep("x", inter) .. "line\n" + local p2 = "dos " .. strrep("y", inter) .. "line\r\n" + local p3 = "raw " .. strrep("z", inter) .. "bytes" + local bp1, bp2, bp3 + pass(len .. " byte(s) patterns") +remote (format("str = data:receive(%d)", strlen(p1)+strlen(p2)+strlen(p3))) + err = data:send(p1, p2, p3) + if err then fail(err) end +remote "data:send(str)" + bp1, bp2, bp3, err = data:receive("*lu", "*l", strlen(p3)) + if err then fail(err) end + if bp1.."\n" == p1 and bp2.."\r\n" == p2 and bp3 == p3 then + pass("patterns match") + else fail("patterns don't match") end +end ------------------------------------------------------------------------------ --- Close connection and exit server. We are done. ------------------------------------------------------------------------------ -new_test("the library has passed all tests") -print("client: closing connection with server") -send_command(CLOSE) -send_command(EXIT) -control:close() -print(format("time elapsed: %6.2fs", _time() - start)) -print("client: exiting...") -exit() +test_mixed(1) +test_mixed(17) +test_mixed(200) +test_mixed(4091) +test_mixed(80199) +test_mixed(800000) +test_mixed(80199) +test_mixed(4091) +test_mixed(200) +test_mixed(17) +test_mixed(1) + +------------------------------------------------------------------------ +test("closed connection detection") + +function test_closed() + local back, err + local str = 'little string' + reconnect() + pass("trying read detection") + remote (format ([[ + data:send('%s') + data:close() + data = nil + ]], str)) + -- try to get a line + back, err = data:receive() + if not err then fail("shold have gotten 'closed'.") + elseif err ~= "closed" then fail("got '"..err.."' instead of 'closed'.") + elseif str ~= back then fail("didn't receive partial result.") + else pass("graceful 'closed' received") end + reconnect() + pass("trying write detection") + remote [[ + data:close() + data = nil + ]] + err, total = data:send(strrep("ugauga", 100000)) + if not err then +pass("failed: output buffer is at least %d bytes long!", total) + elseif err ~= "closed" then +fail("got '"..err.."' instead of 'closed'.") + else +pass("graceful 'closed' received after %d bytes were sent", total) + end +end + +test_closed() + +------------------------------------------------------------------------ +test("return timeout on receive") +function test_blockingtimeoutreceive(len, tm, sl) + local str, err, total + reconnect() + pass("%d bytes, %ds return timeout, %ds pause", len, tm, sl) + remote (format ([[ + data:timeout(%d) + str = strrep('a', %d) + data:send(str) + print('server: sleeping for %ds') + _sleep(%d) + print('server: woke up') + data:send(str) + ]], 2*tm, len, sl, sl)) + data:timeout(tm, "return") + str, err, elapsed = data:receive(2*len) + check_timeout(tm, sl, elapsed, err, "receive", "return", + strlen(str) == 2*len) +end +test_blockingtimeoutreceive(800091, 1, 3) +test_blockingtimeoutreceive(800091, 2, 3) +test_blockingtimeoutreceive(800091, 3, 2) +test_blockingtimeoutreceive(800091, 3, 1) + +------------------------------------------------------------------------ +test("return timeout on send") +function test_returntimeoutsend(len, tm, sl) + local str, err, total + reconnect() + pass("%d bytes, %ds return timeout, %ds pause", len, tm, sl) + remote (format ([[ + data:timeout(%d) + str = data:receive(%d) + print('server: sleeping for %ds') + _sleep(%d) + print('server: woke up') + str = data:receive(%d) + ]], 2*tm, len, sl, sl, len)) + data:timeout(tm, "return") + str = strrep("a", 2*len) + err, total, elapsed = data:send(str) + check_timeout(tm, sl, elapsed, err, "send", "return", + total == 2*len) +end +test_returntimeoutsend(800091, 1, 3) +test_returntimeoutsend(800091, 2, 3) +test_returntimeoutsend(800091, 3, 2) +test_returntimeoutsend(800091, 3, 1) + + +------------------------------------------------------------------------ +test("blocking timeout on receive") +function test_blockingtimeoutreceive(len, tm, sl) + local str, err, total + reconnect() + pass("%d bytes, %ds blocking timeout, %ds pause", len, tm, sl) + remote (format ([[ + data:timeout(%d) + str = strrep('a', %d) + data:send(str) + print('server: sleeping for %ds') + _sleep(%d) + print('server: woke up') + data:send(str) + ]], 2*tm, len, sl, sl)) + data:timeout(tm) + str, err, elapsed = data:receive(2*len) + check_timeout(tm, sl, elapsed, err, "receive", "blocking", + strlen(str) == 2*len) +end +test_blockingtimeoutreceive(800091, 1, 3) +test_blockingtimeoutreceive(800091, 2, 3) +test_blockingtimeoutreceive(800091, 3, 2) +test_blockingtimeoutreceive(800091, 3, 1) + + +------------------------------------------------------------------------ +test("blocking timeout on send") +function test_blockingtimeoutsend(len, tm, sl) + local str, err, total + reconnect() + pass("%d bytes, %ds blocking timeout, %ds pause", len, tm, sl) + remote (format ([[ + data:timeout(%d) + str = data:receive(%d) + print('server: sleeping for %ds') + _sleep(%d) + print('server: woke up') + str = data:receive(%d) + ]], 2*tm, len, sl, sl, len)) + data:timeout(tm) + str = strrep("a", 2*len) + err, total, elapsed = data:send(str) + check_timeout(tm, sl, elapsed, err, "send", "blocking", + total == 2*len) +end +test_blockingtimeoutsend(800091, 1, 3) +test_blockingtimeoutsend(800091, 2, 3) +test_blockingtimeoutsend(800091, 3, 2) +test_blockingtimeoutsend(800091, 3, 1) + +------------------------------------------------------------------------ +test(format("done in %.2fs", _time() - start)) diff --git a/test/testsrvr.lua b/test/testsrvr.lua index 7b89987..227e341 100644 --- a/test/testsrvr.lua +++ b/test/testsrvr.lua @@ -1,96 +1,24 @@ ------------------------------------------------------------------------------ --- LuaSocket automated test module --- testsrvr.lua --- This is the server module. It's completely controled by the client module --- by the use of a control connection. ------------------------------------------------------------------------------ +HOST = HOST or "localhost" +PORT = PORT or "8080" ------------------------------------------------------------------------------ --- Read command definitions ------------------------------------------------------------------------------ -HOST = HOST or "*" -assert(dofile("testcmd.lua")) -test_debug_mode() - ------------------------------------------------------------------------------ --- Start control connection ------------------------------------------------------------------------------ -server, err = bind(HOST, PORT) -if not server then - fail(err) - exit(1) -end -print("server: waiting for control connection...") -control = server:accept() -print("server: control connection stablished!") - ------------------------------------------------------------------------------ --- Executes a command, detecting any possible failures --- Input --- cmd: command to be executed --- par: command parameters, if needed ------------------------------------------------------------------------------ -function execute_command(cmd, par) - if cmd == CONNECT then - print("server: waiting for data connection...") - data = server:accept() - data:timeout(10) - if not data then - fail("server: unable to start data connection!") - else - print("server: data connection stablished!") - end - elseif cmd == CLOSE then - print("server: closing connection with client...") - if data then - data:close() - data = nil - end - elseif cmd == ECHO_LINE then - str, err = data:receive() - if err then fail("server: " .. err) end - err = data:send(str, "\n") - if err then fail("server: " .. err) end - elseif cmd == ECHO_BLOCK then - str, err = data:receive(par) - print(format("server: received %d bytes", strlen(str))) - if err then fail("server: " .. err) end - print(format("server: sending %d bytes", strlen(str))) - err = data:send(str) - if err then fail("server: " .. err) end - elseif cmd == RECEIVE_BLOCK then - str, err = data:receive(par) - print(format("server: received %d bytes", strlen(str))) - elseif cmd == SEND_BLOCK then - print(format("server: sending %d bytes", strlen(str))) - err = data:send(str) - elseif cmd == ECHO_TIMEOUT then - str, err = data:receive(par) - if err then fail("server: " .. err) end - err = data:send(str) - if err then fail("server: " .. err) end - elseif cmd == COMMAND then - cmd, par = get_command() - send_command(cmd, par) - elseif cmd == EXIT then - print("server: exiting...") - exit(0) - elseif cmd == SYNC then - print("server: synchronizing...") - send_command(SYNC) - elseif cmd == SLEEP then - print("server: sleeping for " .. par .. " seconds...") - _sleep(par) - print("server: woke up!") - end -end - ------------------------------------------------------------------------------ --- Loop forever, accepting and executing commands ------------------------------------------------------------------------------ +server, error = bind(HOST, PORT) +if not server then print("server: " .. tostring(error)) exit() end while 1 do - cmd, par = get_command() - if not cmd then fail("server: " .. par) end - print_command(cmd, par) - execute_command(cmd, par) + print("server: waiting for client connection..."); + control = server:accept() + while 1 do + command, error = control:receive() + if error then + control:close() + print("server: closing connection...") + break + end + error = control:send("\n") + if error then + control:close() + print("server: closing connection...") + break + end + dostring(command) + end end From 5b911918e33967be36728ebfab76ad635d2ab09e Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 8 Jul 2002 21:56:57 +0000 Subject: [PATCH 108/483] Usando localhost por default. --- test/tftptest.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tftptest.lua b/test/tftptest.lua index 1e9e1d5..b29657a 100644 --- a/test/tftptest.lua +++ b/test/tftptest.lua @@ -14,7 +14,7 @@ function readfile(file) return a end -host = host or "goya" +host = host or "localhost" print("downloading") err = tftp_get(host, 69, "index.html", "index.got") assert(not err, err) From b796207ce06a66b04cce6686b3fa664c06703995 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 8 Jul 2002 21:57:18 +0000 Subject: [PATCH 109/483] Usando noglobals.lua --- test/urltest.lua | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/urltest.lua b/test/urltest.lua index b078d03..da7475a 100644 --- a/test/urltest.lua +++ b/test/urltest.lua @@ -1,17 +1,3 @@ -function mysetglobal (varname, oldvalue, newvalue) - print("changing " .. varname) - %rawset(%globals(), varname, newvalue) -end -function mygetglobal (varname, newvalue) - print("checking " .. varname) - return %rawget(%globals(), varname) -end -settagmethod(tag(nil), "setglobal", mysetglobal) -settagmethod(tag(nil), "getglobal", mygetglobal) - -assert(dofile "../lua/code.lua") -assert(dofile "../lua/url.lua") - local check_build_url = function(parsed) local built = URL.build_url(parsed) if built ~= parsed.url then From d7e80592a69c076991ed4f4cc15d5390e14d1f0b Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 2 Dec 2002 23:34:41 +0000 Subject: [PATCH 110/483] Already compiling and running for Lua 5.0 (alpha) --- src/http.lua | 20 ++++----- src/inet.c | 8 ++-- src/luasocket.c | 10 ++++- src/luasocket.h | 7 ++- src/mbox.lua | 111 +++++++++++++++++++++++++++++++++--------------- src/select.c | 15 ++++--- src/smtp.lua | 2 +- src/timeout.c | 4 +- src/udp.c | 12 +++--- src/unix.c | 8 ++-- src/unix.h | 6 +-- src/url.lua | 62 ++++++++++++++------------- 12 files changed, 164 insertions(+), 101 deletions(-) diff --git a/src/http.lua b/src/http.lua index 9832a6b..dce3ac8 100644 --- a/src/http.lua +++ b/src/http.lua @@ -32,7 +32,7 @@ Public.BLOCKSIZE = 8192 ----------------------------------------------------------------------------- function Private.try_receive(...) local sock = arg[1] - local data, err = call(sock.receive, arg) + local data, err = sock.receive(unpack(arg)) if err then sock:close() return nil, err @@ -62,7 +62,7 @@ end ----------------------------------------------------------------------------- function Private.get_statuscode(line) local code, _ - _, _, code = strfind(line, "HTTP/%d*%.%d* (%d%d%d)") + _, _, code = string.find(line, "HTTP/%d*%.%d* (%d%d%d)") return tonumber(code) end @@ -102,17 +102,17 @@ function Private.receive_headers(sock, headers) -- headers go until a blank line is found while line ~= "" do -- get field-name and value - _,_, name, value = strfind(line, "^(.-):%s*(.*)") + _,_, name, value = string.find(line, "^(.-):%s*(.*)") if not name or not value then sock:close() return nil, "malformed reponse headers" end - name = strlower(name) + name = string.lower(name) -- get next line (value might be folded) line, err = Private.try_receive(sock) if err then return nil, err end -- unfold any folded values - while not err and strfind(line, "^%s") do + while not err and string.find(line, "^%s") do value = value .. line line, err = Private.try_receive(sock) if err then return nil, err end @@ -142,7 +142,7 @@ function Private.receivebody_bychunks(sock, headers, receive_cb) local go, uerr = receive_cb(nil, err) return uerr or err end - size = tonumber(gsub(line, ";.*", ""), 16) + size = tonumber(string.gsub(line, ";.*", ""), 16) if not size then err = "invalid chunk size" sock:close() @@ -299,7 +299,7 @@ function Private.send_indirect(data, send_cb, chunk, size) data:close() return err end - sent = sent + strlen(chunk) + sent = sent + string.len(chunk) if sent >= size then break end chunk, size = send_cb() end @@ -391,7 +391,7 @@ function Private.fill_headers(headers, parsed) lower["user-agent"] = Public.USERAGENT -- override with user values for i,v in headers do - lower[strlower(i)] = v + lower[string.lower(i)] = v end lower["host"] = parsed.host -- this cannot be overriden @@ -554,7 +554,7 @@ function Public.request_cb(request, response) request.headers = Private.fill_headers(request.headers, parsed) -- try to connect to server local sock - sock, response.error = connect(parsed.host, parsed.port) + sock, response.error = socket.connect(parsed.host, parsed.port) if not sock then return response end -- set connection timeout so that we do not hang forever sock:timeout(Public.TIMEOUT) @@ -619,7 +619,7 @@ function Public.request(request) local response = {} if request.body then request.body_cb = function() - return request.body, strlen(request.body) + return request.body, string.len(request.body) end end local cat = Concat.create() diff --git a/src/inet.c b/src/inet.c index 5003ed9..3e89e88 100644 --- a/src/inet.c +++ b/src/inet.c @@ -38,9 +38,9 @@ static int inet_aton(cchar *cp, struct in_addr *inp); void inet_open(lua_State *L) { lua_pushcfunction(L, inet_lua_toip); - lua_setglobal(L, "toip"); + priv_newglobal(L, "toip"); lua_pushcfunction(L, inet_lua_tohostname); - lua_setglobal(L, "tohostname"); + priv_newglobal(L, "tohostname"); priv_newglobalmethod(L, "getsockname"); priv_newglobalmethod(L, "getpeername"); } @@ -145,7 +145,7 @@ static int inet_lua_getpeername(lua_State *L) { p_sock sock = (p_sock) lua_touserdata(L, 1); struct sockaddr_in peer; - size_t peer_len = sizeof(peer); + int peer_len = sizeof(peer); if (getpeername(sock->fd, (SA *) &peer, &peer_len) < 0) { lua_pushnil(L); return 1; @@ -167,7 +167,7 @@ static int inet_lua_getsockname(lua_State *L) { p_sock sock = (p_sock) lua_touserdata(L, 1); struct sockaddr_in local; - size_t local_len = sizeof(local); + int local_len = sizeof(local); if (getsockname(sock->fd, (SA *) &local, &local_len) < 0) { lua_pushnil(L); return 1; diff --git a/src/luasocket.c b/src/luasocket.c index d329d4e..26bc014 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -56,5 +56,13 @@ LUASOCKET_API int lua_socketlibopen(lua_State *L) buf_open(L); tcps_open(L); udp_open(L); - return 1; +#if LUASOCKET_DEBUG + lua_dofile(L, "concat.lua"); + lua_dofile(L, "code.lua"); + lua_dofile(L, "url.lua"); + lua_dofile(L, "http.lua"); + lua_dofile(L, "smtp.lua"); + lua_dofile(L, "ftp.lua"); +#endif + return 0; } diff --git a/src/luasocket.h b/src/luasocket.h index 95720e2..fd22606 100644 --- a/src/luasocket.h +++ b/src/luasocket.h @@ -11,7 +11,12 @@ /*-------------------------------------------------------------------------*\ * Current luasocket version \*-------------------------------------------------------------------------*/ -#define LUASOCKET_VERSION "LuaSocket 1.5" +#define LUASOCKET_VERSION "LuaSocket 1.5 (alpha)" + +/*-------------------------------------------------------------------------*\ +* Library's namespace +\*-------------------------------------------------------------------------*/ +#define LUASOCKET_LIBNAME "socket" /*-------------------------------------------------------------------------*\ * This macro prefixes all exported API functions diff --git a/src/mbox.lua b/src/mbox.lua index 9cce9ff..2969111 100644 --- a/src/mbox.lua +++ b/src/mbox.lua @@ -1,45 +1,88 @@ local Public = {} -parse = Public +mbox = Public -function Public.headers(headers_s) - local headers = {} - headers_s = "\n" .. headers_s .. "$$$:\n" - local i, j = 1, 1 - local name, value, _ - while 1 do - j = strfind(headers_s, "\n%S-:", i+1) - if not j then break end - _,_, name, value = strfind(strsub(headers_s, i+1, j-1), "(%S-):%s?(.*)") - value = gsub(value or "", "\r\n", "\n") - value = gsub(value, "\n%s*", " ") - name = strlower(name) - if headers[name] then headers[name] = headers[name] .. ", " .. value - else headers[name] = value end - i, j = j, i +function Public.split_message(message_s) + local message = {} + message_s = gsub(message_s, "\r\n", "\n") + gsub(message_s, "^(.-\n)\n", function (h) %message.headers = h end) + gsub(message_s, "^.-\n\n(.*)", function (b) %message.body = b end) + if not message.body then + gsub(message_s, "^\n(.*)", function (b) %message.body = b end) end - headers["$$$"] = nil + if not message.headers and not message.body then + message.headers = message_s + end + return message.headers or "", message.body or "" +end + +function Public.split_headers(headers_s) + local headers = {} + headers_s = gsub(headers_s, "\r\n", "\n") + headers_s = gsub(headers_s, "\n[ ]+", " ") + gsub("\n" .. headers_s, "\n([^\n]+)", function (h) tinsert(%headers, h) end) return headers end -function Public.message(message_s) - message_s = gsub(message_s, "^.-\n", "") - local _, headers_s, body - _, _, headers_s, body = strfind(message_s, "^(.-\n)\n(.*)") - headers_s = headers_s or "" - body = body or "" - return { headers = %Public.headers(headers_s), body = body } +function Public.parse_header(header_s) + header_s = gsub(header_s, "\n[ ]+", " ") + header_s = gsub(header_s, "\n+", "") + local _, __, name, value = strfind(header_s, "([^%s:]-):%s*(.*)") + return name, value end -function Public.mbox(mbox_s) - local mbox = {} - mbox_s = "\n" .. mbox_s .. "\nFrom " - local i, j = 1, 1 - while 1 do - j = strfind(mbox_s, "\nFrom ", i + 1) - if not j then break end - tinsert(mbox, %Public.message(strsub(mbox_s, i + 1, j - 1))) - i, j = j, i +function Public.parse_headers(headers_s) + local headers_t = %Public.split_headers(headers_s) + local headers = {} + for i = 1, getn(headers_t) do + local name, value = %Public.parse_header(headers_t[i]) + if name then + name = strlower(name) + if headers[name] then + headers[name] = headers[name] .. ", " .. value + else headers[name] = value end + end end - return mbox + return headers +end + +function Public.parse_from(from) + local _, __, name, address = strfind(from, "^%s*(.-)%s*%<(.-)%>") + if not address then + _, __, address = strfind(from, "%s*(.+)%s*") + end + name = name or "" + address = address or "" + if name == "" then name = address end + name = gsub(name, '"', "") + return name, address +end + +function Public.split_mbox(mbox_s) + mbox = {} + mbox_s = gsub(mbox_s, "\r\n", "\n") .."\n\nFrom \n" + local nj, i, j = 1, 1, 1 + while 1 do + i, nj = strfind(mbox_s, "\n\nFrom .-\n", j) + if not i then break end + local message = strsub(mbox_s, j, i-1) + tinsert(mbox, message) + j = nj+1 + end + return mbox +end + +function Public.parse_mbox(mbox_s) + local mbox = %Public.split_mbox(mbox_s) + for i = 1, getn(mbox) do + mbox[i] = %Public.parse_message(mbox[i]) + end + return mbox +end + +function Public.parse_message(message_s) + local message = {} + message.headers, message.body = %Public.split_message(message_s) + message.headers = %Public.parse_headers(message.headers) + return message end diff --git a/src/select.c b/src/select.c index 9a24dbb..1aaa7fe 100644 --- a/src/select.c +++ b/src/select.c @@ -31,9 +31,12 @@ void select_open(lua_State *L) { /* push select auxiliar lua function and register * select_lua_select with it as an upvalue */ -#include "lsselect.loh" +#ifdef LUASOCKET_DEBUG +#endif + luaL_loadfile(L, "lsselect.lua"); + lua_call(L, 0, 1); lua_pushcclosure(L, select_lua_select, 1); - lua_setglobal(L, "select"); + priv_newglobal(L, "select"); /* create luasocket(select) table */ lua_pushstring(L, "luasocket(select)"); lua_newtable(L); @@ -61,8 +64,8 @@ static int select_lua_select(lua_State *L) /* make sure we have enough arguments (nil is the default) */ lua_settop(L, 4); /* pass FD_SET and manipulation functions */ - lua_newuserdatabox(L, &read); - lua_newuserdatabox(L, &write); + lua_boxpointer(L, &read); + lua_boxpointer(L, &write); lua_pushcfunction(L, local_FD_SET); lua_pushcfunction(L, local_FD_ISSET); /* pass getfd function with selectable table as upvalue */ @@ -121,7 +124,7 @@ static int local_select(lua_State *L) static int local_FD_SET(lua_State *L) { COMPAT_FD fd = (COMPAT_FD) lua_tonumber(L, 1); - fd_set *set = (fd_set *) lua_touserdata(L, 2); + fd_set *set = (fd_set *) lua_topointer(L, 2); if (fd >= 0) FD_SET(fd, set); return 0; } @@ -129,7 +132,7 @@ static int local_FD_SET(lua_State *L) static int local_FD_ISSET(lua_State *L) { COMPAT_FD fd = (COMPAT_FD) lua_tonumber(L, 1); - fd_set *set = (fd_set *) lua_touserdata(L, 2); + fd_set *set = (fd_set *) lua_topointer(L, 2); if (fd >= 0 && FD_ISSET(fd, set)) lua_pushnumber(L, 1); else lua_pushnil(L); return 1; diff --git a/src/smtp.lua b/src/smtp.lua index 72a0e5a..774dddb 100644 --- a/src/smtp.lua +++ b/src/smtp.lua @@ -19,7 +19,7 @@ Public.TIMEOUT = 180 Public.PORT = 25 -- domain used in HELO command and default sendmail -- If we are under a CGI, try to get from environment -Public.DOMAIN = getenv("SERVER_NAME") or "localhost" +Public.DOMAIN = os.getenv("SERVER_NAME") or "localhost" -- default server used to send e-mails Public.SERVER = "localhost" diff --git a/src/timeout.c b/src/timeout.c index 266a86e..fdbc47a 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -125,9 +125,9 @@ void tm_open(lua_State *L) (void) L; #ifdef _DEBUG lua_pushcfunction(L, tm_lua_time); - lua_setglobal(L, "_time"); + priv_newglobal(L, "_time"); lua_pushcfunction(L, tm_lua_sleep); - lua_setglobal(L, "_sleep"); + priv_newglobal(L, "_sleep"); #endif } diff --git a/src/udp.c b/src/udp.c index 0dc0df8..29004fd 100644 --- a/src/udp.c +++ b/src/udp.c @@ -45,7 +45,7 @@ void udp_open(lua_State *L) udp_inherit(L, UDP_CLASS); /* declare global functions */ lua_pushcfunction(L, udp_global_udpsocket); - lua_setglobal(L, "udpsocket"); + priv_newglobal(L, "udp"); for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) priv_newglobalmethod(L, funcs[i].name); /* make class selectable */ @@ -162,12 +162,12 @@ static int udp_lua_receivefrom(lua_State *L) p_udp udp = (p_udp) lua_touserdata(L, 1); p_tm tm = &udp->base_tm; struct sockaddr_in peer; - size_t peer_len = sizeof(peer); + int peer_len = sizeof(peer); unsigned char buffer[UDP_DATAGRAMSIZE]; size_t wanted = (size_t) luaL_opt_number(L, 2, sizeof(buffer)); size_t got; int err; - if (udp->udp_connected) lua_error(L, "receivefrom on connected socket"); + if (udp->udp_connected) luaL_error(L, "receivefrom on connected socket"); tm_markstart(tm); wanted = MIN(wanted, sizeof(buffer)); err = compat_recvfrom(udp->fd, buffer, wanted, &got, tm_getremaining(tm), @@ -201,7 +201,7 @@ static int udp_lua_send(lua_State *L) size_t wanted, sent = 0; int err; cchar *data = luaL_check_lstr(L, 2, &wanted); - if (!udp->udp_connected) lua_error(L, "send on unconnected socket"); + if (!udp->udp_connected) luaL_error(L, "send on unconnected socket"); tm_markstart(tm); err = compat_send(udp->fd, data, wanted, &sent, tm_getremaining(tm)); priv_pusherror(L, err == PRIV_CLOSED ? PRIV_REFUSED : err); @@ -230,9 +230,9 @@ static int udp_lua_sendto(lua_State *L) p_tm tm = &udp->base_tm; struct sockaddr_in peer; int err; - if (udp->udp_connected) lua_error(L, "sendto on connected socket"); + if (udp->udp_connected) luaL_error(L, "sendto on connected socket"); memset(&peer, 0, sizeof(peer)); - if (!inet_aton(ip, &peer.sin_addr)) lua_error(L, "invalid ip address"); + if (!inet_aton(ip, &peer.sin_addr)) luaL_error(L, "invalid ip address"); peer.sin_family = AF_INET; peer.sin_port = htons(port); tm_markstart(tm); diff --git a/src/unix.c b/src/unix.c index d50d98c..0fc08bd 100644 --- a/src/unix.c +++ b/src/unix.c @@ -28,7 +28,7 @@ void compat_open(lua_State *L) } COMPAT_FD compat_accept(COMPAT_FD s, struct sockaddr *addr, - socklen_t *len, int deadline) + int *len, int deadline) { struct timeval tv; fd_set fds; @@ -74,7 +74,7 @@ int compat_send(COMPAT_FD c, cchar *data, size_t count, size_t *sent, } int compat_sendto(COMPAT_FD c, cchar *data, size_t count, size_t *sent, - int deadline, SA *addr, socklen_t len) + int deadline, SA *addr, int len) { struct timeval tv; fd_set fds; @@ -134,7 +134,7 @@ int compat_recv(COMPAT_FD c, uchar *data, size_t count, size_t *got, } int compat_recvfrom(COMPAT_FD c, uchar *data, size_t count, size_t *got, - int deadline, SA *addr, socklen_t *len) + int deadline, SA *addr, int *len) { struct timeval tv; fd_set fds; @@ -290,7 +290,7 @@ cchar *compat_trysetoptions(lua_State *L, COMPAT_FD sock) static cchar *try_setbooloption(lua_State *L, COMPAT_FD sock, int name) { int bool, res; - if (!lua_isnumber(L, -1)) lua_error(L, "invalid option value"); + if (!lua_isnumber(L, -1)) luaL_error(L, "invalid option value"); bool = (int) lua_tonumber(L, -1); res = setsockopt(sock, SOL_SOCKET, name, (char *) &bool, sizeof(bool)); if (res < 0) return "error setting option"; diff --git a/src/unix.h b/src/unix.h index e317b06..944b471 100644 --- a/src/unix.h +++ b/src/unix.h @@ -46,15 +46,15 @@ void compat_open(lua_State *L); #define compat_select select COMPAT_FD compat_socket(int domain, int type, int protocol); -COMPAT_FD compat_accept(COMPAT_FD s, SA *addr, socklen_t *len, int deadline); +COMPAT_FD compat_accept(COMPAT_FD s, SA *addr, int *len, int deadline); int compat_send(COMPAT_FD c, cchar *data, size_t count, size_t *done, int deadline); int compat_recv(COMPAT_FD c, uchar *data, size_t count, size_t *done, int deadline); int compat_sendto(COMPAT_FD c, cchar *data, size_t count, size_t *done, - int deadline, SA *addr, socklen_t len); + int deadline, SA *addr, int len); int compat_recvfrom(COMPAT_FD c, uchar *data, size_t count, size_t *got, - int deadline, SA *addr, socklen_t *len); + int deadline, SA *addr, int *len); void compat_setnonblocking(COMPAT_FD sock); void compat_setblocking(COMPAT_FD sock); void compat_setreuseaddr(COMPAT_FD sock); diff --git a/src/url.lua b/src/url.lua index e17bcf5..0ecec3c 100644 --- a/src/url.lua +++ b/src/url.lua @@ -34,26 +34,30 @@ function Public.parse_url(url, default) -- empty url is parsed to nil if not url or url == "" then return nil end -- remove whitespace - url = gsub(url, "%s", "") + url = string.gsub(url, "%s", "") -- get fragment - url = gsub(url, "#(.*)$", function(f) parsed.fragment = f end) + url = string.gsub(url, "#(.*)$", function(f) parsed.fragment = f end) -- get scheme - url = gsub(url, "^([%w][%w%+%-%.]*)%:", function(s) parsed.scheme = s end) + url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", + function(s) parsed.scheme = s end) -- get authority - url = gsub(url, "^//([^/]*)", function(n) parsed.authority = n end) - -- get query string - url = gsub(url, "%?(.*)", function(q) parsed.query = q end) + url = string.gsub(url, "^//([^/]*)", function(n) parsed.authority = n end) + -- get query stringing + url = string.gsub(url, "%?(.*)", function(q) parsed.query = q end) -- get params - url = gsub(url, "%;(.*)", function(p) parsed.params = p end) + url = string.gsub(url, "%;(.*)", function(p) parsed.params = p end) if url ~= "" then parsed.path = url end local authority = parsed.authority if not authority then return parsed end - authority = gsub(authority,"^([^@]*)@",function(u) parsed.userinfo = u end) - authority = gsub(authority, ":([^:]*)$", function(p) parsed.port = p end) + authority = string.gsub(authority,"^([^@]*)@", + function(u) parsed.userinfo = u end) + authority = string.gsub(authority, ":([^:]*)$", + function(p) parsed.port = p end) if authority ~= "" then parsed.host = authority end local userinfo = parsed.userinfo if not userinfo then return parsed end - userinfo = gsub(userinfo, ":([^:]*)$", function(p) parsed.password = p end) + userinfo = string.gsub(userinfo, ":([^:]*)$", + function(p) parsed.password = p end) parsed.user = userinfo return parsed end @@ -64,7 +68,7 @@ end -- Input -- parsed: parsed URL, as returned by Public.parse -- Returns --- a string with the corresponding URL +-- a stringing with the corresponding URL ----------------------------------------------------------------------------- function Public.build_url(parsed) local url = parsed.path or "" @@ -86,7 +90,7 @@ function Public.build_url(parsed) if authority then url = "//" .. authority .. url end if parsed.scheme then url = parsed.scheme .. ":" .. url end if parsed.fragment then url = url .. "#" .. parsed.fragment end - url = gsub(url, "%s", "") + url = string.gsub(url, "%s", "") return url end @@ -134,13 +138,13 @@ end function Public.parse_path(path) local parsed = {} path = path or "" - path = gsub(path, "%s", "") - gsub(path, "([^/]+)", function (s) tinsert(parsed, s) end) - for i = 1, getn(parsed) do + path = string.gsub(path, "%s", "") + string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) + for i = 1, table.getn(parsed) do parsed[i] = Code.unescape(parsed[i]) end - if strsub(path, 1, 1) == "/" then parsed.is_absolute = 1 end - if strsub(path, -1, -1) == "/" then parsed.is_directory = 1 end + if stringsub(path, 1, 1) == "/" then parsed.is_absolute = 1 end + if stringsub(path, -1, -1) == "/" then parsed.is_directory = 1 end return parsed end @@ -150,11 +154,11 @@ end -- parsed: path segments -- unsafe: if true, segments are not protected before path is built -- Returns --- path: correspondin path string +-- path: correspondin path stringing ----------------------------------------------------------------------------- function Public.build_path(parsed, unsafe) local path = "" - local n = getn(parsed) + local n = table.getn(parsed) if unsafe then for i = 1, n-1 do path = path .. parsed[i] @@ -178,10 +182,10 @@ function Public.build_path(parsed, unsafe) return path end -function Private.make_set(table) +function Private.make_set(t) local s = {} - for i = 1, getn(table) do - s[table[i]] = 1 + for i = 1, table.getn(t) do + s[t[i]] = 1 end return s end @@ -195,7 +199,7 @@ Private.segment_set = Private.make_set { function Private.protect_segment(s) local segment_set = Private.segment_set - return gsub(s, "(%W)", function (c) + return string.gsub(s, "(%W)", function (c) if segment_set[c] then return c else return Code.escape(c) end end) @@ -210,21 +214,21 @@ end -- corresponding absolute path ----------------------------------------------------------------------------- function Private.absolute_path(base_path, relative_path) - if strsub(relative_path, 1, 1) == "/" then return relative_path end - local path = gsub(base_path, "[^/]*$", "") + if stringsub(relative_path, 1, 1) == "/" then return relative_path end + local path = string.gsub(base_path, "[^/]*$", "") path = path .. relative_path - path = gsub(path, "([^/]*%./)", function (s) + path = string.gsub(path, "([^/]*%./)", function (s) if s ~= "./" then return s else return "" end end) - path = gsub(path, "/%.$", "/") + path = string.gsub(path, "/%.$", "/") local reduced while reduced ~= path do reduced = path - path = gsub(reduced, "([^/]*/%.%./)", function (s) + path = string.gsub(reduced, "([^/]*/%.%./)", function (s) if s ~= "../../" then return "" else return s end end) end - path = gsub(reduced, "([^/]*/%.%.)$", function (s) + path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) if s ~= "../.." then return "" else return s end end) return path From 7da19138e37c4e0123860f1fecbceb80c3d2627d Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Tue, 3 Dec 2002 07:20:34 +0000 Subject: [PATCH 111/483] Faltam testes de ftp e smtp. O resto passa. --- src/buffer.c | 4 +- src/http.lua | 6 +- src/select.c | 2 - src/timeout.c | 6 +- src/url.lua | 6 +- test/httptest.lua | 88 ++++++++++++------------ test/testclnt.lua | 169 +++++++++++++++++++++++----------------------- test/testsrvr.lua | 6 +- test/urltest.lua | 2 +- 9 files changed, 141 insertions(+), 148 deletions(-) diff --git a/src/buffer.c b/src/buffer.c index a9a9782..4260f20 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -74,7 +74,7 @@ int buf_send(lua_State *L, p_buf buf) } priv_pusherror(L, err); lua_pushnumber(L, total); -#ifdef _DEBUG +#ifdef LUASOCKET_DEBUG /* push time elapsed during operation as the last return value */ lua_pushnumber(L, tm_getelapsed(&base->base_tm)/1000.0); #endif @@ -139,7 +139,7 @@ int buf_receive(lua_State *L, p_buf buf) for ( ; arg <= top; arg++) lua_pushnil(L); /* last return is an error code */ priv_pusherror(L, err); -#ifdef _DEBUG +#ifdef LUASOCKET_DEBUG /* push time elapsed during operation as the last return value */ lua_pushnumber(L, tm_getelapsed(&base->base_tm)/1000.0); #endif diff --git a/src/http.lua b/src/http.lua index dce3ac8..9543d59 100644 --- a/src/http.lua +++ b/src/http.lua @@ -8,7 +8,7 @@ ----------------------------------------------------------------------------- local Public, Private = {}, {} -HTTP = Public +http = Public ----------------------------------------------------------------------------- -- Program constants @@ -195,7 +195,7 @@ end function Private.receivebody_bylength(sock, length, receive_cb) local uerr, go while length > 0 do - local size = min(Public.BLOCKSIZE, length) + local size = math.min(Public.BLOCKSIZE, length) local chunk, err = sock:receive(size) if err then go, uerr = receive_cb(nil, err) @@ -542,7 +542,7 @@ function Public.request_cb(request, response) scheme = "http" }) if parsed.scheme ~= "http" then - response.error = format("unknown scheme '%s'", parsed.scheme) + response.error = string.format("unknown scheme '%s'", parsed.scheme) return response end -- explicit authentication info overrides that given by the URL diff --git a/src/select.c b/src/select.c index 1aaa7fe..5c08730 100644 --- a/src/select.c +++ b/src/select.c @@ -31,8 +31,6 @@ void select_open(lua_State *L) { /* push select auxiliar lua function and register * select_lua_select with it as an upvalue */ -#ifdef LUASOCKET_DEBUG -#endif luaL_loadfile(L, "lsselect.lua"); lua_call(L, 0, 1); lua_pushcclosure(L, select_lua_select, 1); diff --git a/src/timeout.c b/src/timeout.c index fdbc47a..940ddca 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -20,7 +20,7 @@ /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ -#ifdef _DEBUG +#ifdef LUASOCKET_DEBUG static int tm_lua_time(lua_State *L); static int tm_lua_sleep(lua_State *L); #endif @@ -123,7 +123,7 @@ int tm_gettime(void) void tm_open(lua_State *L) { (void) L; -#ifdef _DEBUG +#ifdef LUASOCKET_DEBUG lua_pushcfunction(L, tm_lua_time); priv_newglobal(L, "_time"); lua_pushcfunction(L, tm_lua_sleep); @@ -137,7 +137,7 @@ void tm_open(lua_State *L) /*-------------------------------------------------------------------------*\ * Returns the time the system has been up, in secconds. \*-------------------------------------------------------------------------*/ -#ifdef _DEBUG +#ifdef LUASOCKET_DEBUG static int tm_lua_time(lua_State *L) { lua_pushnumber(L, tm_gettime()/1000.0); diff --git a/src/url.lua b/src/url.lua index 0ecec3c..4d2bfa7 100644 --- a/src/url.lua +++ b/src/url.lua @@ -143,8 +143,8 @@ function Public.parse_path(path) for i = 1, table.getn(parsed) do parsed[i] = Code.unescape(parsed[i]) end - if stringsub(path, 1, 1) == "/" then parsed.is_absolute = 1 end - if stringsub(path, -1, -1) == "/" then parsed.is_directory = 1 end + if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end + if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end return parsed end @@ -214,7 +214,7 @@ end -- corresponding absolute path ----------------------------------------------------------------------------- function Private.absolute_path(base_path, relative_path) - if stringsub(relative_path, 1, 1) == "/" then return relative_path end + if string.sub(relative_path, 1, 1) == "/" then return relative_path end local path = string.gsub(base_path, "[^/]*$", "") path = path .. relative_path path = string.gsub(path, "([^/]*%./)", function (s) diff --git a/test/httptest.lua b/test/httptest.lua index 85c8bd8..2941390 100644 --- a/test/httptest.lua +++ b/test/httptest.lua @@ -7,21 +7,21 @@ dofile("noglobals.lua") local similar = function(s1, s2) - return strlower(gsub(s1 or "", "%s", "")) == - strlower(gsub(s2 or "", "%s", "")) + return string.lower(string.gsub(s1 or "", "%s", "")) == + string.lower(string.gsub(s2 or "", "%s", "")) end local fail = function(s) s = s or "failed!" print(s) - exit() + os.exit() end local readfile = function(name) - local f = readfrom(name) + local f = io.open(name, "r") if not f then return nil end - local s = read("*a") - readfrom() + local s = f:read("*a") + f:close() return s end @@ -31,7 +31,7 @@ local check = function (v, e) end local check_request = function(request, expect, ignore) - local response = HTTP.request(request) + local response = http.request(request) for i,v in response do if not ignore[i] then if v ~= expect[i] then %fail(i .. " differs!") end @@ -45,30 +45,28 @@ local check_request = function(request, expect, ignore) print("ok") end -dofile("../src/modules/http.lua") - local request, response, ignore, expect, index, prefix, cgiprefix -local t = _time() +local t = socket._time() HOST = HOST or "localhost" -prefix = prefix or "/luasocket-test" -cgiprefix = cgiprefix or "/luasocket-test-cgi" -index = readfile("index.html") +prefix = prefix or "/luasocket" +cgiprefix = cgiprefix or "/luasocket/cgi" +index = readfile("test/index.html") -write("testing request uri correctness: ") +io.write("testing request uri correctness: ") local forth = cgiprefix .. "/request-uri?" .. "this+is+the+query+string" -local back = HTTP.get("http://" .. HOST .. forth) +local back = http.get("http://" .. HOST .. forth) if similar(back, forth) then print("ok") else fail("failed!") end -write("testing query string correctness: ") +io.write("testing query string correctness: ") forth = "this+is+the+query+string" -back = HTTP.get("http://" .. HOST .. cgiprefix .. "/query-string?" .. forth) +back = http.get("http://" .. HOST .. cgiprefix .. "/query-string?" .. forth) if similar(back, forth) then print("ok") else fail("failed!") end -write("testing document retrieval: ") +io.write("testing document retrieval: ") request = { url = "http://" .. HOST .. prefix .. "/index.html" } @@ -82,7 +80,7 @@ ignore = { } check_request(request, expect, ignore) -write("testing HTTP redirection: ") +io.write("testing http redirection: ") request = { url = "http://" .. HOST .. prefix } @@ -97,7 +95,7 @@ ignore = { check_request(request, expect, ignore) -write("testing automatic auth failure: ") +io.write("testing automatic auth failure: ") request = { url = "http://really:wrong@" .. HOST .. prefix .. "/auth/index.html" } @@ -111,7 +109,7 @@ ignore = { } check_request(request, expect, ignore) -write("testing HTTP redirection failure: ") +io.write("testing http redirection failure: ") request = { url = "http://" .. HOST .. prefix, stay = 1 @@ -126,29 +124,29 @@ ignore = { } check_request(request, expect, ignore) -write("testing host not found: ") +io.write("testing host not found: ") request = { url = "http://wronghost/does/not/exist" } -local c, e = connect("wronghost", 80) +local c, e = socket.connect("wronghost", 80) expect = { error = e } ignore = {} check_request(request, expect, ignore) -write("testing invalid url: ") +io.write("testing invalid url: ") request = { url = HOST .. prefix } -local c, e = connect("", 80) +local c, e = socket.connect("", 80) expect = { error = e } ignore = {} check_request(request, expect, ignore) -write("testing document not found: ") +io.write("testing document not found: ") request = { url = "http://" .. HOST .. "/wrongdocument.html" } @@ -162,7 +160,7 @@ ignore = { } check_request(request, expect, ignore) -write("testing auth failure: ") +io.write("testing auth failure: ") request = { url = "http://" .. HOST .. prefix .. "/auth/index.html" } @@ -176,7 +174,7 @@ ignore = { } check_request(request, expect, ignore) -write("testing manual basic auth: ") +io.write("testing manual basic auth: ") request = { url = "http://" .. HOST .. prefix .. "/auth/index.html", headers = { @@ -193,7 +191,7 @@ ignore = { } check_request(request, expect, ignore) -write("testing automatic basic auth: ") +io.write("testing automatic basic auth: ") request = { url = "http://luasocket:password@" .. HOST .. prefix .. "/auth/index.html" } @@ -207,7 +205,7 @@ ignore = { } check_request(request, expect, ignore) -write("testing auth info overriding: ") +io.write("testing auth info overriding: ") request = { url = "http://really:wrong@" .. HOST .. prefix .. "/auth/index.html", user = "luasocket", @@ -223,7 +221,7 @@ ignore = { } check_request(request, expect, ignore) -write("testing cgi output retrieval (probably chunked...): ") +io.write("testing cgi output retrieval (probably chunked...): ") request = { url = "http://" .. HOST .. cgiprefix .. "/cat-index-html" } @@ -237,7 +235,7 @@ ignore = { } check_request(request, expect, ignore) -write("testing redirect loop: ") +io.write("testing redirect loop: ") request = { url = "http://" .. HOST .. cgiprefix .. "/redirect-loop" } @@ -251,7 +249,7 @@ ignore = { } check_request(request, expect, ignore) -write("testing post method: ") +io.write("testing post method: ") request = { url = "http://" .. HOST .. cgiprefix .. "/cat", method = "POST", @@ -267,7 +265,7 @@ ignore = { } check_request(request, expect, ignore) -write("testing wrong scheme: ") +io.write("testing wrong scheme: ") request = { url = "wrong://" .. HOST .. cgiprefix .. "/cat", method = "GET" @@ -280,31 +278,31 @@ ignore = { check_request(request, expect, ignore) local body -write("testing simple get function: ") -body = HTTP.get("http://" .. HOST .. prefix .. "/index.html") +io.write("testing simple get function: ") +body = http.get("http://" .. HOST .. prefix .. "/index.html") check(body == index) -write("testing simple get function with table args: ") -body = HTTP.get { +io.write("testing simple get function with table args: ") +body = http.get { url = "http://really:wrong@" .. HOST .. prefix .. "/auth/index.html", user = "luasocket", password = "password" } check(body == index) -write("testing simple post function: ") -body = HTTP.post("http://" .. HOST .. cgiprefix .. "/cat", index) +io.write("testing simple post function: ") +body = http.post("http://" .. HOST .. cgiprefix .. "/cat", index) check(body == index) -write("testing simple post function with table args: ") -body = HTTP.post { +io.write("testing simple post function with table args: ") +body = http.post { url = "http://" .. HOST .. cgiprefix .. "/cat", body = index } check(body == index) -write("testing HEAD method: ") -response = HTTP.request { +io.write("testing HEAD method: ") +response = http.request { method = "HEAD", url = "http://www.tecgraf.puc-rio.br/~diego/" } @@ -312,4 +310,4 @@ check(response and response.headers) print("passed all tests") -print(format("done in %.2fs", _time() - t)) +print(string.format("done in %.2fs", socket._time() - t)) diff --git a/test/testclnt.lua b/test/testclnt.lua index 73c10de..15f1dd8 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -2,32 +2,32 @@ HOST = HOST or "localhost" PORT = PORT or "8080" function pass(...) - local s = call(format, arg) - write(s, "\n") + local s = string.format(unpack(arg)) + io.write(s, "\n") end function fail(...) - local s = call(format, arg) - write("ERROR: ", s, "!\n") - exit() + local s = string.format(unpack(arg)) + io.write("ERROR: ", s, "!\n") + os.exit() end function warn(...) - local s = call(format, arg) - write("WARNING: ", s, "\n") + local s = format(unpack(arg)) + io.write("WARNING: ", s, "\n") end function remote(...) - local s = call(format, arg) - s = gsub(s, "\n", ";") - s = gsub(s, "%s+", " ") - s = gsub(s, "^%s*", "") + local s = string.format(unpack(arg)) + s = string.gsub(s, "\n", ";") + s = string.gsub(s, "%s+", " ") + s = string.gsub(s, "^%s*", "") control:send(s, "\n") control:receive() end function test(test) - write("----------------------------------------------\n", + io.write("----------------------------------------------\n", "testing: ", test, "\n", "----------------------------------------------\n") end @@ -66,51 +66,69 @@ function check_timeout(tm, sl, elapsed, err, opp, mode, alldone) end end -write("----------------------------------------------\n", +io.write("----------------------------------------------\n", "LuaSocket Test Procedures\n", "----------------------------------------------\n") -if not _time or not _sleep then fail("not compiled with _DEBUG") end +if not socket._time or not socket._sleep then + fail("not compiled with _DEBUG") +end -start = _time() +start = socket._time() function tcpreconnect() - write("attempting data connection... ") + io.write("attempting data connection... ") if data then data:close() end remote [[ if data then data:close() data = nil end data = server:accept() ]] - data, error = connect(HOST, PORT) - if not data then fail(error) + data, err = socket.connect(HOST, PORT) + if not data then fail(err) else pass("connected!") end end reconnect = tcpreconnect pass("attempting control connection...") -control, error = connect(HOST, PORT) -if error then fail(error) +control, err = socket.connect(HOST, PORT) +if err then fail(err) else pass("connected!") end ------------------------------------------------------------------------ test("bugs") -write("empty host connect: ") +io.write("empty host connect: ") function empty_connect() if data then data:close() data = nil end remote [[ if data then data:close() data = nil end data = server:accept() ]] - data, err = connect("", PORT) + data, err = socket.connect("", PORT) if not data then pass("ok") - data = connect(HOST, PORT) + data = socket.connect(HOST, PORT) else fail("should not have connected!") end end empty_connect() +io.write("active close: ") +function active_close() + reconnect() + if socket._isclosed(data) then fail("should not be closed") end + data:close() + if not socket._isclosed(data) then fail("should be closed") end + data = nil + local udp = socket.udp() + if socket._isclosed(udp) then fail("should not be closed") end + udp:close() + if not socket._isclosed(udp) then fail("should be closed") end + pass("ok") +end + +active_close() + ------------------------------------------------------------------------ test("method registration") @@ -133,7 +151,7 @@ test_methods(control, { }) if udpsocket then - test_methods(udpsocket(), { + test_methods(socket.udp(), { "close", "timeout", "send", @@ -147,50 +165,27 @@ if udpsocket then }) end -test_methods(bind("*", 0), { +test_methods(socket.bind("*", 0), { "close", "timeout", "accept" }) -if pipe then - local p1, p2 = pipe() - test_methods(p1, { - "close", - "timeout", - "send", - "receive" - }) - test_methods(p2, { - "close", - "timeout", - "send", - "receive" - }) -end - -if filesocket then - test_methods(filesocket(0), { - "close", - "timeout", - "send", - "receive" - }) -end - ------------------------------------------------------------------------ test("select function") function test_selectbugs() - local r, s, e = select(nil, nil, 0.1) + local r, s, e = socket.select(nil, nil, 0.1) assert(type(r) == "table" and type(s) == "table" and e == "timeout") pass("both nil: ok") - local udp = udpsocket() + local udp = socket.udp() udp:close() - r, s, e = select({ data }, { data }, 0.1) + r, s, e = socket.select({ udp }, { udp }, 0.1) assert(type(r) == "table" and type(s) == "table" and e == "timeout") pass("closed sockets: ok") - e = call(select, {"wrong", 1, 0.1}, "x", nil) - assert(e == nil) + e = pcall(socket.select, "wrong", 1, 0.1) + assert(e == false) + e = pcall(socket.select, {}, 1, 0.1) + assert(e == false) pass("invalid input: ok") end @@ -202,8 +197,8 @@ reconnect() function test_asciiline(len) local str, str10, back, err - str = strrep("x", mod(len, 10)) - str10 = strrep("aZb.c#dAe?", floor(len/10)) + str = string.rep("x", math.mod(len, 10)) + str10 = string.rep("aZb.c#dAe?", math.floor(len/10)) str = str .. str10 pass(len .. " byte(s) line") remote "str = data:receive()" @@ -229,8 +224,9 @@ reconnect() function test_rawline(len) local str, str10, back, err - str = strrep(strchar(47), mod(len, 10)) - str10 = strrep(strchar(120,21,77,4,5,0,7,36,44,100), floor(len/10)) + str = string.rep(string.char(47), math.mod(len, 10)) + str10 = string.rep(string.char(120,21,77,4,5,0,7,36,44,100), + math.floor(len/10)) str = str .. str10 pass(len .. " byte(s) line") remote "str = data:receive()" @@ -260,12 +256,12 @@ test("raw transfer") reconnect() function test_raw(len) - local half = floor(len/2) + local half = math.floor(len/2) local s1, s2, back, err - s1 = strrep("x", half) - s2 = strrep("y", len-half) + s1 = string.rep("x", half) + s2 = string.rep("y", len-half) pass(len .. " byte(s) block") -remote (format("str = data:receive(%d)", len)) +remote (string.format("str = data:receive(%d)", len)) err = data:send(s1) if err then fail(err) end err = data:send(s2) @@ -312,17 +308,18 @@ test("mixed patterns") reconnect() function test_mixed(len) - local inter = floor(len/3) - local p1 = "unix " .. strrep("x", inter) .. "line\n" - local p2 = "dos " .. strrep("y", inter) .. "line\r\n" - local p3 = "raw " .. strrep("z", inter) .. "bytes" + local inter = math.floor(len/3) + local p1 = "unix " .. string.rep("x", inter) .. "line\n" + local p2 = "dos " .. string.rep("y", inter) .. "line\r\n" + local p3 = "raw " .. string.rep("z", inter) .. "bytes" local bp1, bp2, bp3 pass(len .. " byte(s) patterns") -remote (format("str = data:receive(%d)", strlen(p1)+strlen(p2)+strlen(p3))) +remote (string.format("str = data:receive(%d)", + string.len(p1)+string.len(p2)+string.len(p3))) err = data:send(p1, p2, p3) if err then fail(err) end remote "data:send(str)" - bp1, bp2, bp3, err = data:receive("*lu", "*l", strlen(p3)) + bp1, bp2, bp3, err = data:receive("*lu", "*l", string.len(p3)) if err then fail(err) end if bp1.."\n" == p1 and bp2.."\r\n" == p2 and bp3 == p3 then pass("patterns match") @@ -349,7 +346,7 @@ function test_closed() local str = 'little string' reconnect() pass("trying read detection") - remote (format ([[ + remote (string.format ([[ data:send('%s') data:close() data = nil @@ -366,7 +363,7 @@ function test_closed() data:close() data = nil ]] - err, total = data:send(strrep("ugauga", 100000)) + err, total = data:send(string.rep("ugauga", 100000)) if not err then pass("failed: output buffer is at least %d bytes long!", total) elseif err ~= "closed" then @@ -384,19 +381,19 @@ function test_blockingtimeoutreceive(len, tm, sl) local str, err, total reconnect() pass("%d bytes, %ds return timeout, %ds pause", len, tm, sl) - remote (format ([[ + remote (string.format ([[ data:timeout(%d) - str = strrep('a', %d) + str = string.rep('a', %d) data:send(str) print('server: sleeping for %ds') - _sleep(%d) + socket._sleep(%d) print('server: woke up') data:send(str) ]], 2*tm, len, sl, sl)) data:timeout(tm, "return") str, err, elapsed = data:receive(2*len) check_timeout(tm, sl, elapsed, err, "receive", "return", - strlen(str) == 2*len) + string.len(str) == 2*len) end test_blockingtimeoutreceive(800091, 1, 3) test_blockingtimeoutreceive(800091, 2, 3) @@ -409,16 +406,16 @@ function test_returntimeoutsend(len, tm, sl) local str, err, total reconnect() pass("%d bytes, %ds return timeout, %ds pause", len, tm, sl) - remote (format ([[ + remote (string.format ([[ data:timeout(%d) str = data:receive(%d) print('server: sleeping for %ds') - _sleep(%d) + socket._sleep(%d) print('server: woke up') str = data:receive(%d) ]], 2*tm, len, sl, sl, len)) data:timeout(tm, "return") - str = strrep("a", 2*len) + str = string.rep("a", 2*len) err, total, elapsed = data:send(str) check_timeout(tm, sl, elapsed, err, "send", "return", total == 2*len) @@ -435,19 +432,19 @@ function test_blockingtimeoutreceive(len, tm, sl) local str, err, total reconnect() pass("%d bytes, %ds blocking timeout, %ds pause", len, tm, sl) - remote (format ([[ + remote (string.format ([[ data:timeout(%d) - str = strrep('a', %d) + str = string.rep('a', %d) data:send(str) print('server: sleeping for %ds') - _sleep(%d) + socket._sleep(%d) print('server: woke up') data:send(str) ]], 2*tm, len, sl, sl)) data:timeout(tm) str, err, elapsed = data:receive(2*len) check_timeout(tm, sl, elapsed, err, "receive", "blocking", - strlen(str) == 2*len) + string.len(str) == 2*len) end test_blockingtimeoutreceive(800091, 1, 3) test_blockingtimeoutreceive(800091, 2, 3) @@ -461,16 +458,16 @@ function test_blockingtimeoutsend(len, tm, sl) local str, err, total reconnect() pass("%d bytes, %ds blocking timeout, %ds pause", len, tm, sl) - remote (format ([[ + remote (string.format ([[ data:timeout(%d) str = data:receive(%d) print('server: sleeping for %ds') - _sleep(%d) + socket._sleep(%d) print('server: woke up') str = data:receive(%d) ]], 2*tm, len, sl, sl, len)) data:timeout(tm) - str = strrep("a", 2*len) + str = string.rep("a", 2*len) err, total, elapsed = data:send(str) check_timeout(tm, sl, elapsed, err, "send", "blocking", total == 2*len) @@ -481,4 +478,4 @@ test_blockingtimeoutsend(800091, 3, 2) test_blockingtimeoutsend(800091, 3, 1) ------------------------------------------------------------------------ -test(format("done in %.2fs", _time() - start)) +test(string.format("done in %.2fs", socket._time() - start)) diff --git a/test/testsrvr.lua b/test/testsrvr.lua index 227e341..141c058 100644 --- a/test/testsrvr.lua +++ b/test/testsrvr.lua @@ -1,8 +1,8 @@ HOST = HOST or "localhost" PORT = PORT or "8080" -server, error = bind(HOST, PORT) -if not server then print("server: " .. tostring(error)) exit() end +server, error = socket.bind(HOST, PORT) +if not server then print("server: " .. tostring(error)) os.exit() end while 1 do print("server: waiting for client connection..."); control = server:accept() @@ -19,6 +19,6 @@ while 1 do print("server: closing connection...") break end - dostring(command) + (loadstring(command))() end end diff --git a/test/urltest.lua b/test/urltest.lua index da7475a..8ca36fe 100644 --- a/test/urltest.lua +++ b/test/urltest.lua @@ -30,7 +30,7 @@ end local check_parse_path = function(path, expect) local parsed = URL.parse_path(path) - for i = 1, max(getn(parsed), getn(expect)) do + for i = 1, math.max(table.getn(parsed), table.getn(expect)) do if parsed[i] ~= expect[i] then print(path) write("segment: ", i, " = '", Code.hexa(tostring(parsed[i])), From 53857360bb1ca9cd2080b69d930763ae59db9b06 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 20 Mar 2003 00:24:44 +0000 Subject: [PATCH 112/483] Finish port to Lua 5. Everything is working fine. Still doesn't work in Windows. --- etc/check-links.lua | 58 ++++++++-------- etc/get.lua | 46 ++++++------- src/ftp.lua | 51 +++++++------- src/http.lua | 10 +-- src/mbox.lua | 42 ++++++------ src/smtp.lua | 21 +++--- src/url.lua | 18 ++--- test/ftptest.lua | 122 +++++++++++++++++---------------- test/httptest.lua | 18 ++--- test/smtptest.lua | 159 +++++++++++++++++++++++--------------------- test/urltest.lua | 21 +++--- 11 files changed, 289 insertions(+), 277 deletions(-) diff --git a/etc/check-links.lua b/etc/check-links.lua index 24747a9..0dca27c 100644 --- a/etc/check-links.lua +++ b/etc/check-links.lua @@ -1,13 +1,9 @@ -dofile("../lua/http.lua") -HTTP.TIMEOUT = 10 -dofile("../lua/code.lua") -dofile("../lua/url.lua") -dofile("../lua/concat.lua") +socket.http.TIMEOUT = 10 cache = {} function readfile(path) - path = Code.unescape(path) + path = socket.code.unescape(path) local file, error = openfile(path, "r") if file then local body = read(file, "*a") @@ -17,7 +13,7 @@ function readfile(path) end function getstatus(url) - local parsed = URL.parse_url(url, { scheme = "file" }) + local parsed = socket.url.parse(url, { scheme = "file" }) if cache[url] then return cache[url].res end local res if parsed.scheme == "http" then @@ -25,10 +21,10 @@ function getstatus(url) local response = { body_cb = function(chunk, err) return nil end } - local blocksize = HTTP.BLOCKSIZE - HTTP.BLOCKSIZE = 1 - response = HTTP.request_cb(request, response) - HTTP.BLOCKSIZE = blocksize + local blocksize = socket.http.BLOCKSIZE + socket.http.BLOCKSIZE = 1 + response = socket.http.request_cb(request, response) + socket.http.BLOCKSIZE = blocksize if response.code == 200 then res = nil else res = response.status or response.error end elseif parsed.scheme == "file" then @@ -37,17 +33,17 @@ function getstatus(url) closefile(file) res = nil else res = error end - else res = format("unhandled scheme '%s'", parsed.scheme) end + else res = string.format("unhandled scheme '%s'", parsed.scheme) end cache[url] = { res = res } return res end function retrieve(url) - local parsed = URL.parse_url(url, { scheme = "file" }) + local parsed = socket.url.parse(url, { scheme = "file" }) local base, body, error base = url if parsed.scheme == "http" then - local response = HTTP.request{url = url} + local response = socket.http.request{url = url} if response.code ~= 200 then error = response.status or response.error else @@ -56,23 +52,23 @@ function retrieve(url) end elseif parsed.scheme == "file" then body, error = readfile(parsed.path) - else error = format("unhandled scheme '%s'", parsed.scheme) end + else error = string.format("unhandled scheme '%s'", parsed.scheme) end return base, body, error end function getlinks(body, base) -- get rid of comments - body = gsub(body, "%<%!%-%-.-%-%-%>", "") + body = string.gsub(body, "%<%!%-%-.-%-%-%>", "") local links = {} -- extract links - gsub(body, '[Hh][Rr][Ee][Ff]%s*=%s*"([^"]*)"', function(href) - tinsert(%links, URL.absolute_url(%base, href)) + string.gsub(body, '[Hh][Rr][Ee][Ff]%s*=%s*"([^"]*)"', function(href) + table.insert(links, socket.url.absolute(base, href)) end) - gsub(body, "[Hh][Rr][Ee][Ff]%s*=%s*'([^']*)'", function(href) - tinsert(%links, URL.absolute_url(%base, href)) + string.gsub(body, "[Hh][Rr][Ee][Ff]%s*=%s*'([^']*)'", function(href) + table.insert(links, socket.url.absolute(base, href)) end) - gsub(body, "[Hh][Rr][Ee][Ff]%s*=%s*(%a+)", function(href) - tinsert(%links, URL.absolute_url(%base, href)) + string.gsub(body, "[Hh][Rr][Ee][Ff]%s*=%s*(%a+)", function(href) + table.insert(links, socket.url.absolute(base, href)) end) return links end @@ -81,19 +77,19 @@ function checklinks(url) local base, body, error = retrieve(url) if not body then print(error) return end local links = getlinks(body, base) - for i = 1, getn(links) do - write(_STDERR, "\t", links[i], "\n") - local err = getstatus(links[i]) - if err then write('\t', links[i], ": ", err, "\n") end + for _, l in ipairs(links) do + io.stderr:write("\t", l, "\n") + local err = getstatus(l) + if err then io.stderr:write('\t', l, ": ", err, "\n") end end end arg = arg or {} -if getn(arg) < 1 then - write("Usage:\n luasocket -f check-links.lua {}\n") +if table.getn(arg) < 1 then + print("Usage:\n luasocket check-links.lua {}") exit() end -for i = 1, getn(arg) do - write("Checking ", arg[i], "\n") - checklinks(URL.absolute_url("file:", arg[i])) +for _, a in ipairs(arg) do + print("Checking ", a) + checklinks(socket.url.absolute("file:", a)) end diff --git a/etc/get.lua b/etc/get.lua index ecfecd1..33da653 100644 --- a/etc/get.lua +++ b/etc/get.lua @@ -13,8 +13,8 @@ function nicetime(s) end end end - if l == "s" then return format("%2.0f%s", s, l) - else return format("%5.2f%s", s, l) end + if l == "s" then return string.format("%2.0f%s", s, l) + else return string.format("%5.2f%s", s, l) end end -- formats a number of bytes into human readable form @@ -32,21 +32,21 @@ function nicesize(b) end end end - return format("%7.2f%2s", b, l) + return string.format("%7.2f%2s", b, l) end -- returns a string with the current state of the download function gauge(got, dt, size) local rate = got / dt if size and size >= 1 then - return format("%s received, %s/s throughput, " .. + return string.format("%s received, %s/s throughput, " .. "%.0f%% done, %s remaining", nicesize(got), nicesize(rate), 100*got/size, nicetime((size-got)/rate)) else - return format("%s received, %s/s throughput, %s elapsed", + return string.format("%s received, %s/s throughput, %s elapsed", nicesize(got), nicesize(rate), nicetime(dt)) @@ -57,22 +57,22 @@ end -- kind of copied from luasocket's manual callback examples function receive2disk(file, size) local aux = { - start = _time(), + start = socket._time(), got = 0, - file = openfile(file, "wb"), + file = io.open(file, "wb"), size = size } local receive_cb = function(chunk, err) - local dt = _time() - %aux.start -- elapsed time since start + local dt = socket._time() - %aux.start -- elapsed time since start if not chunk or chunk == "" then - write("\n") - closefile(%aux.file) + io.write("\n") + aux.file:close() return end - write(%aux.file, chunk) - %aux.got = %aux.got + strlen(chunk) -- total bytes received + aux.file:write(chunk) + aux.got = aux.got + string.len(chunk) -- total bytes received if dt < 0.1 then return 1 end -- not enough time for estimate - write("\r", gauge(%aux.got, dt, %aux.size)) + io.write("\r", gauge(aux.got, dt, aux.size)) return 1 end return receive_cb @@ -80,7 +80,7 @@ end -- downloads a file using the ftp protocol function getbyftp(url, file) - local err = FTP.get_cb { + local err = socket.ftp.get_cb { url = url, content_cb = receive2disk(file), type = "i" @@ -91,7 +91,7 @@ end -- downloads a file using the http protocol function getbyhttp(url, file, size) - local response = HTTP.request_cb( + local response = socket.http.request_cb( {url = url}, {body_cb = receive2disk(file, size)} ) @@ -101,7 +101,7 @@ end -- determines the size of a http file function gethttpsize(url) - local response = HTTP.request { + local response = socket.http.request { method = "HEAD", url = url } @@ -113,11 +113,11 @@ end -- determines the scheme and the file name of a given url function getschemeandname(url, name) -- this is an heuristic to solve a common invalid url poblem - if not strfind(url, "//") then url = "//" .. url end - local parsed = URL.parse_url(url, {scheme = "http"}) + if not string.find(url, "//") then url = "//" .. url end + local parsed = socket.url.parse(url, {scheme = "http"}) if name then return parsed.scheme, name end - local segment = URL.parse_path(parsed.path) - name = segment[getn(segment)] + local segment = socket.url.parse_path(parsed.path) + name = segment[table.getn(segment)] if segment.is_directory then name = nil end return parsed.scheme, name end @@ -134,7 +134,7 @@ end -- main program arg = arg or {} -if getn(arg) < 1 then - write("Usage:\n luasocket -f get.lua []\n") - exit(1) +if table.getn(arg) < 1 then + io.write("Usage:\n luasocket get.lua []\n") + os.exit(1) else get(arg[1], arg[2]) end diff --git a/src/ftp.lua b/src/ftp.lua index 1fa48f7..f6fffbb 100644 --- a/src/ftp.lua +++ b/src/ftp.lua @@ -8,7 +8,7 @@ ----------------------------------------------------------------------------- local Public, Private = {}, {} -FTP = Public +socket.ftp = Public ----------------------------------------------------------------------------- -- Program constants @@ -47,7 +47,7 @@ end ----------------------------------------------------------------------------- function Private.try_receive(...) local sock = arg[1] - local data, err = call(sock.receive, arg) + local data, err = sock.receive(unpack(arg)) if err then sock:close() end return data, err end @@ -64,9 +64,9 @@ function Private.get_pasv(pasv) local a, b, c, d, p1, p2, _ local ip, port _,_, a, b, c, d, p1, p2 = - strfind(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)") + string.find(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)") if not (a and b and c and d and p1 and p2) then return nil, nil end - ip = format("%d.%d.%d.%d", a, b, c, d) + ip = string.format("%d.%d.%d.%d", a, b, c, d) port = tonumber(p1)*256 + tonumber(p2) return ip, port end @@ -100,13 +100,13 @@ function Private.get_answer(control) local line, err = Private.try_receive(control) local answer = line if err then return nil, err end - _,_, code, sep = strfind(line, "^(%d%d%d)(.)") + _,_, code, sep = string.find(line, "^(%d%d%d)(.)") if not code or not sep then return nil, answer end if sep == "-" then -- answer is multiline repeat line, err = Private.try_receive(control) if err then return nil, err end - _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)") + _,_, lastcode, sep = string.find(line, "^(%d%d%d)(.)") answer = answer .. "\n" .. line until code == lastcode and sep == " " -- answer ends with same code end @@ -126,8 +126,8 @@ function Private.check_answer(control, success) local answer, code = Private.get_answer(control) if not answer then return nil, code end if type(success) ~= "table" then success = {success} end - for i = 1, getn(success) do - if code == success[i] then + for _, s in ipairs(success) do + if code == s then return code, answer end end @@ -213,13 +213,13 @@ function Private.port(control) local code, answer local server, ctl_ip ctl_ip, answer = control:getsockname() - server, answer = bind(ctl_ip, 0) + server, answer = socket.bind(ctl_ip, 0) server:timeout(Public.TIMEOUT) local ip, p, ph, pl ip, p = server:getsockname() - pl = mod(p, 256) + pl = math.mod(p, 256) ph = (p - pl)/256 - local arg = gsub(format("%s,%d,%d", ip, ph, pl), "%.", ",") + local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") code, answer = Private.command(control, "port", arg, {200}) if not code then server:close() @@ -321,7 +321,7 @@ function Private.send_indirect(data, send_cb, chunk, size) data:close() return err end - sent = sent + strlen(chunk) + sent = sent + string.len(chunk) if sent >= size then break end chunk, size = send_cb() end @@ -374,7 +374,7 @@ end ----------------------------------------------------------------------------- function Private.change_type(control, params) local type, _ - _, _, type = strfind(params or "", "type=(.)") + _, _, type = string.find(params or "", "type=(.)") if type == "a" or type == "i" then local code, err = Private.command(control, "type", type, {200}) if not code then return err end @@ -391,7 +391,7 @@ end ----------------------------------------------------------------------------- function Private.open(parsed) -- start control connection - local control, err = connect(parsed.host, parsed.port) + local control, err = socket.connect(parsed.host, parsed.port) if not control then return nil, err end -- make sure we don't block forever control:timeout(Public.TIMEOUT) @@ -423,7 +423,7 @@ end -- err: error message if any ----------------------------------------------------------------------------- function Private.change_dir(control, segment) - local n = getn(segment) + local n = table.getn(segment) for i = 1, n-1 do local code, answer = Private.cwd(control, segment[i]) if not code then return answer end @@ -443,7 +443,7 @@ end function Private.upload(control, request, segment) local code, name, content_cb -- get remote file name - name = segment[getn(segment)] + name = segment[table.getn(segment)] if not name then control:close() return "Invalid file path" @@ -472,7 +472,7 @@ function Private.download(control, request, segment) is_directory = segment.is_directory content_cb = request.content_cb -- get remote file name - name = segment[getn(segment)] + name = segment[table.getn(segment)] if not name and not is_directory then control:close() return "Invalid file path" @@ -498,7 +498,7 @@ end -- parsed: a table with parsed components ----------------------------------------------------------------------------- function Private.parse_url(request) - local parsed = URL.parse_url(request.url, { + local parsed = socket.url.parse(request.url, { host = "", user = "anonymous", port = 21, @@ -521,9 +521,10 @@ end -- Returns -- dirs: a table with parsed directory components ----------------------------------------------------------------------------- -function Private.parse_path(parsed) - local segment = URL.parse_path(parsed.path) - segment.is_directory = segment.is_directory or (parsed.params == "type=d") +function Private.parse_path(parsed_url) + local segment = socket.url.parse_path(parsed_url.path) + segment.is_directory = segment.is_directory or + (parsed_url.params == "type=d") return segment end @@ -560,7 +561,7 @@ end function Public.get_cb(request) local parsed = Private.parse_url(request) if parsed.scheme ~= "ftp" then - return format("unknown scheme '%s'", parsed.scheme) + return string.format("unknown scheme '%s'", parsed.scheme) end local control, err = Private.open(parsed) if not control then return err end @@ -586,7 +587,7 @@ end function Public.put_cb(request) local parsed = Private.parse_url(request) if parsed.scheme ~= "ftp" then - return format("unknown scheme '%s'", parsed.scheme) + return string.format("unknown scheme '%s'", parsed.scheme) end local control, err = Private.open(parsed) if not control then return err end @@ -612,7 +613,7 @@ end function Public.put(url_or_request, content) local request = Private.build_request(url_or_request) request.content_cb = function() - return content, strlen(content) + return content, string.len(content) end return Public.put_cb(request) end @@ -630,7 +631,7 @@ end -- err: error message in case of error, nil otherwise ----------------------------------------------------------------------------- function Public.get(url_or_request) - local cat = Concat.create() + local cat = socket.concat.create() local request = Private.build_request(url_or_request) request.content_cb = function(chunk, err) if chunk then cat:addstring(chunk) end diff --git a/src/http.lua b/src/http.lua index 9543d59..3275e3b 100644 --- a/src/http.lua +++ b/src/http.lua @@ -8,7 +8,7 @@ ----------------------------------------------------------------------------- local Public, Private = {}, {} -http = Public +socket.http = Public ----------------------------------------------------------------------------- -- Program constants @@ -427,7 +427,7 @@ end ----------------------------------------------------------------------------- function Private.authorize(request, parsed, response) request.headers["authorization"] = "Basic " .. - Code.base64(parsed.user .. ":" .. parsed.password) + socket.code.base64(parsed.user .. ":" .. parsed.password) local authorize = { redirects = request.redirects, method = request.method, @@ -471,7 +471,7 @@ function Private.redirect(request, response) method = request.method, -- the RFC says the redirect URL has to be absolute, but some -- servers do not respect that - url = URL.absolute_url(request.url, response.headers["location"]), + url = socket.url.absolute(request.url, response.headers["location"]), body_cb = request.body_cb, headers = request.headers } @@ -535,7 +535,7 @@ end -- error: error message, or nil if successfull ----------------------------------------------------------------------------- function Public.request_cb(request, response) - local parsed = URL.parse_url(request.url, { + local parsed = socket.url.parse(request.url, { host = "", port = Public.PORT, path ="/", @@ -622,7 +622,7 @@ function Public.request(request) return request.body, string.len(request.body) end end - local cat = Concat.create() + local cat = socket.concat.create() response.body_cb = function(chunk, err) if chunk then cat:addstring(chunk) end return 1 diff --git a/src/mbox.lua b/src/mbox.lua index 2969111..4a72331 100644 --- a/src/mbox.lua +++ b/src/mbox.lua @@ -4,11 +4,11 @@ mbox = Public function Public.split_message(message_s) local message = {} - message_s = gsub(message_s, "\r\n", "\n") - gsub(message_s, "^(.-\n)\n", function (h) %message.headers = h end) - gsub(message_s, "^.-\n\n(.*)", function (b) %message.body = b end) + message_s = string.gsub(message_s, "\r\n", "\n") + string.gsub(message_s, "^(.-\n)\n", function (h) %message.headers = h end) + string.gsub(message_s, "^.-\n\n(.*)", function (b) %message.body = b end) if not message.body then - gsub(message_s, "^\n(.*)", function (b) %message.body = b end) + string.gsub(message_s, "^\n(.*)", function (b) %message.body = b end) end if not message.headers and not message.body then message.headers = message_s @@ -18,26 +18,26 @@ end function Public.split_headers(headers_s) local headers = {} - headers_s = gsub(headers_s, "\r\n", "\n") - headers_s = gsub(headers_s, "\n[ ]+", " ") - gsub("\n" .. headers_s, "\n([^\n]+)", function (h) tinsert(%headers, h) end) + headers_s = string.gsub(headers_s, "\r\n", "\n") + headers_s = string.gsub(headers_s, "\n[ ]+", " ") + string.gsub("\n" .. headers_s, "\n([^\n]+)", function (h) table.insert(%headers, h) end) return headers end function Public.parse_header(header_s) - header_s = gsub(header_s, "\n[ ]+", " ") - header_s = gsub(header_s, "\n+", "") - local _, __, name, value = strfind(header_s, "([^%s:]-):%s*(.*)") + header_s = string.gsub(header_s, "\n[ ]+", " ") + header_s = string.gsub(header_s, "\n+", "") + local _, __, name, value = string.find(header_s, "([^%s:]-):%s*(.*)") return name, value end function Public.parse_headers(headers_s) local headers_t = %Public.split_headers(headers_s) local headers = {} - for i = 1, getn(headers_t) do + for i = 1, table.getn(headers_t) do local name, value = %Public.parse_header(headers_t[i]) if name then - name = strlower(name) + name = string.lower(name) if headers[name] then headers[name] = headers[name] .. ", " .. value else headers[name] = value end @@ -47,34 +47,34 @@ function Public.parse_headers(headers_s) end function Public.parse_from(from) - local _, __, name, address = strfind(from, "^%s*(.-)%s*%<(.-)%>") + local _, __, name, address = string.find(from, "^%s*(.-)%s*%<(.-)%>") if not address then - _, __, address = strfind(from, "%s*(.+)%s*") + _, __, address = string.find(from, "%s*(.+)%s*") end name = name or "" address = address or "" if name == "" then name = address end - name = gsub(name, '"', "") + name = string.gsub(name, '"', "") return name, address end function Public.split_mbox(mbox_s) mbox = {} - mbox_s = gsub(mbox_s, "\r\n", "\n") .."\n\nFrom \n" + mbox_s = string.gsub(mbox_s, "\r\n", "\n") .."\n\nFrom \n" local nj, i, j = 1, 1, 1 while 1 do - i, nj = strfind(mbox_s, "\n\nFrom .-\n", j) + i, nj = string.find(mbox_s, "\n\nFrom .-\n", j) if not i then break end - local message = strsub(mbox_s, j, i-1) - tinsert(mbox, message) + local message = string.sub(mbox_s, j, i-1) + table.insert(mbox, message) j = nj+1 end return mbox end -function Public.parse_mbox(mbox_s) +function Public.parse(mbox_s) local mbox = %Public.split_mbox(mbox_s) - for i = 1, getn(mbox) do + for i = 1, table.getn(mbox) do mbox[i] = %Public.parse_message(mbox[i]) end return mbox diff --git a/src/smtp.lua b/src/smtp.lua index 774dddb..5da9a6f 100644 --- a/src/smtp.lua +++ b/src/smtp.lua @@ -8,7 +8,7 @@ ----------------------------------------------------------------------------- local Public, Private = {}, {} -SMTP = Public +socket.smtp = Public ----------------------------------------------------------------------------- -- Program constants @@ -47,7 +47,7 @@ end ----------------------------------------------------------------------------- function Private.try_receive(...) local sock = arg[1] - local data, err = call(sock.receive, arg) + local data, err = sock.receive(unpack(arg)) if err then sock:close() end return data, err end @@ -81,13 +81,13 @@ function Private.get_answer(control) local line, err = Private.try_receive(control) local answer = line if err then return nil, err end - _,_, code, sep = strfind(line, "^(%d%d%d)(.)") + _,_, code, sep = string.find(line, "^(%d%d%d)(.)") if not code or not sep then return nil, answer end if sep == "-" then -- answer is multiline repeat line, err = Private.try_receive(control) if err then return nil, err end - _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)") + _,_, lastcode, sep = string.find(line, "^(%d%d%d)(.)") answer = answer .. "\n" .. line until code == lastcode and sep == " " -- answer ends with same code end @@ -108,7 +108,7 @@ function Private.check_answer(control, success) local answer, code = Private.get_answer(control) if not answer then return nil, code end if type(success) ~= "table" then success = {success} end - for i = 1, getn(success) do + for i = 1, table.getn(success) do if code == success[i] then return code, answer end @@ -157,7 +157,7 @@ end -- answer: complete server reply or error message ----------------------------------------------------------------------------- function Private.send_mail(sock, sender) - local param = format("FROM:<%s>", sender or "") + local param = string.format("FROM:<%s>", sender or "") local err = Private.send_command(sock, "MAIL", param) if err then return nil, err end return Private.check_answer(sock, 250) @@ -198,7 +198,7 @@ function Private.send_data(sock, headers, body) local code, answer = Private.check_answer(sock, 354) if not code then return nil, answer end -- avoid premature end in message body - body = gsub(body or "", "\n%.", "\n%.%.") + body = string.gsub(body or "", "\n%.", "\n%.%.") -- mark end of message body body = body .. "\r\n.\r\n" err = Private.send_headers(sock, headers) @@ -220,8 +220,9 @@ function Private.send_rcpt(sock, rcpt) local err local code, answer = nil, "No recipient specified" if type(rcpt) ~= "table" then rcpt = {rcpt} end - for i = 1, getn(rcpt) do - err = Private.send_command(sock, "RCPT", format("TO:<%s>", rcpt[i])) + for i = 1, table.getn(rcpt) do + err = Private.send_command(sock, "RCPT", + string.format("TO:<%s>", rcpt[i])) if err then return nil, err end code, answer = Private.check_answer(sock, {250, 251}) if not code then return code, answer end @@ -242,7 +243,7 @@ function Private.open(server) -- default server server = server or Public.SERVER -- connect to server and make sure we won't hang - local sock, err = connect(server, Public.PORT) + local sock, err = socket.connect(server, Public.PORT) if not sock then return nil, err end sock:timeout(Public.TIMEOUT) -- initial server greeting diff --git a/src/url.lua b/src/url.lua index 4d2bfa7..2cf9669 100644 --- a/src/url.lua +++ b/src/url.lua @@ -8,7 +8,7 @@ ---------------------------------------------------------------------------- local Public, Private = {}, {} -URL = Public +socket.url = Public ----------------------------------------------------------------------------- -- Parses a url and returns a table with all its parts according to RFC 2396 @@ -28,7 +28,7 @@ URL = Public -- Obs: -- the leading '/' in {/} is considered part of ----------------------------------------------------------------------------- -function Public.parse_url(url, default) +function Public.parse(url, default) -- initialize default parameters local parsed = default or {} -- empty url is parsed to nil @@ -70,7 +70,7 @@ end -- Returns -- a stringing with the corresponding URL ----------------------------------------------------------------------------- -function Public.build_url(parsed) +function Public.build(parsed) local url = parsed.path or "" if parsed.params then url = url .. ";" .. parsed.params end if parsed.query then url = url .. "?" .. parsed.query end @@ -102,9 +102,9 @@ end -- Returns -- corresponding absolute url ----------------------------------------------------------------------------- -function Public.absolute_url(base_url, relative_url) - local base = Public.parse_url(base_url) - local relative = Public.parse_url(relative_url) +function Public.absolute(base_url, relative_url) + local base = Public.parse(base_url) + local relative = Public.parse(relative_url) if not base then return relative_url elseif not relative then return base_url elseif relative.scheme then return relative_url @@ -124,7 +124,7 @@ function Public.absolute_url(base_url, relative_url) relative.path = Private.absolute_path(base.path,relative.path) end end - return Public.build_url(relative) + return Public.build(relative) end end @@ -141,7 +141,7 @@ function Public.parse_path(path) path = string.gsub(path, "%s", "") string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) for i = 1, table.getn(parsed) do - parsed[i] = Code.unescape(parsed[i]) + parsed[i] = socket.code.unescape(parsed[i]) end if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end @@ -201,7 +201,7 @@ function Private.protect_segment(s) local segment_set = Private.segment_set return string.gsub(s, "(%W)", function (c) if segment_set[c] then return c - else return Code.escape(c) end + else return socket.code.escape(c) end end) end diff --git a/test/ftptest.lua b/test/ftptest.lua index 34cccf1..ee3af91 100644 --- a/test/ftptest.lua +++ b/test/ftptest.lua @@ -1,29 +1,32 @@ dofile("noglobals.lua") local similar = function(s1, s2) - return strlower(gsub(s1, "%s", "")) == strlower(gsub(s2, "%s", "")) -end - -local capture = function(cmd) - readfrom("| " .. cmd) - local s = read("*a") - readfrom() - return s + return + string.lower(string.gsub(s1, "%s", "")) == + string.lower(string.gsub(s2, "%s", "")) end local readfile = function(name) - local f = readfrom(name) - if not f then return nil end - local s = read("*a") - readfrom() - return s + local f = io.open(name, "r") + if not f then return nil end + local s = f:read("*a") + f:close() + return s +end + +local capture = function(cmd) + local f = io.popen(cmd) + if not f then return nil end + local s = f:read("*a") + f:close() + return s end local check = function(v, e, o) e = e or "failed!" o = o or "ok" if v then print(o) - else print(e) exit() end + else print(e) os.exit() end end -- needs an account luasocket:password @@ -31,81 +34,82 @@ end local index, err, saved, back, expected -local t = _time() +local t = socket._time() -index = readfile("index.html") +index = readfile("test/index.html") -write("testing file upload: ") -remove("/var/ftp/dir1/index.up.html") -err = FTP.put("ftp://localhost/dir1/index.up.html;type=i", index) -saved = readfile("/var/ftp/dir1/index.up.html") +io.write("testing wrong scheme: ") +back, err = socket.ftp.get("wrong://banana.com/lixo") +check(not back and err == "unknown scheme 'wrong'", err) + +io.write("testing invalid url: ") +back, err = socket.ftp.get("localhost/dir1/index.html;type=i") +local c, e = socket.connect("", 21) +check(not back and err == e, err) + +io.write("testing anonymous file upload: ") +os.remove("/var/ftp/pub/index.up.html") +err = socket.ftp.put("ftp://localhost/pub/index.up.html;type=i", index) +saved = readfile("/var/ftp/pub/index.up.html") check(not err and saved == index, err) -write("testing file download: ") -back, err = FTP.get("ftp://localhost/dir1/index.up.html;type=i") +io.write("testing anonymous file download: ") +back, err = socket.ftp.get("ftp://localhost/pub/index.up.html;type=i") check(not err and back == index, err) -write("testing no directory changes: ") -back, err = FTP.get("ftp://localhost/index.html;type=i") +io.write("testing no directory changes: ") +back, err = socket.ftp.get("ftp://localhost/index.html;type=i") check(not err and back == index, err) -write("testing multiple directory changes: ") -back, err = FTP.get("ftp://localhost/dir1/dir2/dir3/dir4/dir5/dir6/index.html;type=i") +io.write("testing multiple directory changes: ") +back, err = socket.ftp.get("ftp://localhost/pub/dir1/dir2/dir3/dir4/dir5/index.html;type=i") check(not err and back == index, err) -write("testing authenticated upload: ") -remove("/home/luasocket/index.up.html") -err = FTP.put("ftp://luasocket:password@localhost/index.up.html;type=i", index) +io.write("testing authenticated upload: ") +os.remove("/home/luasocket/index.up.html") +err = socket.ftp.put("ftp://luasocket:password@localhost/index.up.html;type=i", index) saved = readfile("/home/luasocket/index.up.html") check(not err and saved == index, err) -write("testing authenticated download: ") -back, err = FTP.get("ftp://luasocket:password@localhost/index.up.html;type=i") +io.write("testing authenticated download: ") +back, err = socket.ftp.get("ftp://luasocket:password@localhost/index.up.html;type=i") check(not err and back == index, err) -write("testing weird-character translation: ") -back, err = FTP.get("ftp://luasocket:password@localhost/%2fvar/ftp/dir1/index.html;type=i") +io.write("testing weird-character translation: ") +back, err = socket.ftp.get("ftp://luasocket:password@localhost/%2fvar/ftp/pub/index.html;type=i") check(not err and back == index, err) -write("testing parameter overriding: ") -back, err = FTP.get { - url = "//stupid:mistake@localhost/dir1/index.html", +io.write("testing parameter overriding: ") +back, err = socket.ftp.get { + url = "//stupid:mistake@localhost/index.html", user = "luasocket", password = "password", type = "i" } check(not err and back == index, err) -write("testing wrong scheme: ") -back, err = FTP.get("wrong://banana.com/lixo") -check(not back and err == "unknown scheme 'wrong'", err) - -write("testing invalid url: ") -back, err = FTP.get("localhost/dir1/index.html;type=i") -local c, e = connect("", 21) -check(not back and err == e, err) - -write("testing directory listing: ") -expected = capture("ls -F /var/ftp/dir1 | grep -v /") -back, err = FTP.get("ftp://localhost/dir1;type=d") -check(similar(back, expected)) - -write("testing home directory listing: ") +io.write("testing home directory listing: ") expected = capture("ls -F /var/ftp | grep -v /") -back, err = FTP.get("ftp://localhost/") +back, err = socket.ftp.get("ftp://localhost/") check(back and similar(back, expected), nil, err) -write("testing upload denial: ") -err = FTP.put("ftp://localhost/index.up.html;type=a", index) +io.write("testing directory listing: ") +expected = capture("ls -F /var/ftp/pub | grep -v /") +back, err = socket.ftp.get("ftp://localhost/pub;type=d") +check(similar(back, expected)) + +io.write("testing upload denial: ") +err = socket.ftp.put("ftp://localhost/index.up.html;type=a", index) check(err, err) -write("testing authentication failure: ") -err = FTP.put("ftp://luasocket:wrong@localhost/index.html;type=a", index) +io.write("testing authentication failure: ") +err = socket.ftp.put("ftp://luasocket:wrong@localhost/index.html;type=a", index) +print(err) check(err, err) -write("testing wrong file: ") -back, err = FTP.get("ftp://localhost/index.wrong.html;type=a") +io.write("testing wrong file: ") +back, err = socket.ftp.get("ftp://localhost/index.wrong.html;type=a") check(err, err) print("passed all tests") -print(format("done in %.2fs", _time() - t)) +print(string.format("done in %.2fs", socket._time() - t)) diff --git a/test/httptest.lua b/test/httptest.lua index 2941390..8b84f84 100644 --- a/test/httptest.lua +++ b/test/httptest.lua @@ -31,7 +31,7 @@ local check = function (v, e) end local check_request = function(request, expect, ignore) - local response = http.request(request) + local response = socket.http.request(request) for i,v in response do if not ignore[i] then if v ~= expect[i] then %fail(i .. " differs!") end @@ -56,13 +56,13 @@ index = readfile("test/index.html") io.write("testing request uri correctness: ") local forth = cgiprefix .. "/request-uri?" .. "this+is+the+query+string" -local back = http.get("http://" .. HOST .. forth) +local back = socket.http.get("http://" .. HOST .. forth) if similar(back, forth) then print("ok") else fail("failed!") end io.write("testing query string correctness: ") forth = "this+is+the+query+string" -back = http.get("http://" .. HOST .. cgiprefix .. "/query-string?" .. forth) +back = socket.http.get("http://" .. HOST .. cgiprefix .. "/query-string?" .. forth) if similar(back, forth) then print("ok") else fail("failed!") end @@ -178,7 +178,7 @@ io.write("testing manual basic auth: ") request = { url = "http://" .. HOST .. prefix .. "/auth/index.html", headers = { - authorization = "Basic " .. Code.base64("luasocket:password") + authorization = "Basic " .. socket.code.base64("luasocket:password") } } expect = { @@ -279,11 +279,11 @@ check_request(request, expect, ignore) local body io.write("testing simple get function: ") -body = http.get("http://" .. HOST .. prefix .. "/index.html") +body = socket.http.get("http://" .. HOST .. prefix .. "/index.html") check(body == index) io.write("testing simple get function with table args: ") -body = http.get { +body = socket.http.get { url = "http://really:wrong@" .. HOST .. prefix .. "/auth/index.html", user = "luasocket", password = "password" @@ -291,18 +291,18 @@ body = http.get { check(body == index) io.write("testing simple post function: ") -body = http.post("http://" .. HOST .. cgiprefix .. "/cat", index) +body = socket.http.post("http://" .. HOST .. cgiprefix .. "/cat", index) check(body == index) io.write("testing simple post function with table args: ") -body = http.post { +body = socket.http.post { url = "http://" .. HOST .. cgiprefix .. "/cat", body = index } check(body == index) io.write("testing HEAD method: ") -response = http.request { +response = socket.http.request { method = "HEAD", url = "http://www.tecgraf.puc-rio.br/~diego/" } diff --git a/test/smtptest.lua b/test/smtptest.lua index 6b01134..1bba27f 100644 --- a/test/smtptest.lua +++ b/test/smtptest.lua @@ -1,122 +1,130 @@ local sent = {} -local from = "luasock@tecgraf.puc-rio.br" -local server = "mail.tecgraf.puc-rio.br" -local rcpt = "luasock@tecgraf.puc-rio.br" +local from = "diego@localhost" +local server = "localhost" +local rcpt = "luasocket@localhost" -local name = "/var/spool/mail/luasock" +local files = { + "/var/spool/mail/luasocket", + "/var/spool/mail/luasock1", + "/var/spool/mail/luasock2", + "/var/spool/mail/luasock3", +} -local t = _time() +local t = socket._time() local err -dofile("parsembox.lua") -local parse = parse +dofile("mbox.lua") +local parse = mbox.parse dofile("noglobals.lua") local total = function() local t = 0 - for i = 1, getn(%sent) do - t = t + %sent[i].count + for i = 1, table.getn(sent) do + t = t + sent[i].count end return t end local similar = function(s1, s2) - return strlower(gsub(s1, "%s", "")) == strlower(gsub(s2, "%s", "")) -end - -local readfile = function(name) - local f = readfrom(name) - if not f then return nil end - local s = read("*a") - readfrom() - return s -end - -local capture = function(cmd) - readfrom("| " .. cmd) - local s = read("*a") - readfrom() - return s + return + string.lower(string.gsub(s1, "%s", "")) == + string.lower(string.gsub(s2, "%s", "")) end local fail = function(s) s = s or "failed!" print(s) - exit() + os.exit() +end + +local readfile = function(name) + local f = io.open(name, "r") + if not f then + fail("unable to open file!") + return nil + end + local s = f:read("*a") + f:close() + return s end local empty = function() - local f = openfile(%name, "w") - closefile(f) + for i,v in ipairs(files) do + local f = io.open(v, "w") + if not f then + fail("unable to open file!") + end + f:close() + end end local get = function() - return %readfile(%name) -end - -local list = function() - return %capture("ls -l " .. %name) + s = "" + for i,v in ipairs(files) do + s = s .. "\n" .. readfile(v) + end + return s end local check_headers = function(sent, got) sent = sent or {} got = got or {} for i,v in sent do - if not %similar(v, got[i]) then %fail("header " .. v .. "failed!") end + if not similar(v, got[i]) then fail("header " .. v .. "failed!") end end end local check_body = function(sent, got) sent = sent or "" got = got or "" - if not %similar(sent, got) then %fail("bodies differ!") end + if not similar(sent, got) then fail("bodies differ!") end end local check = function(sent, m) - write("checking ", m.headers.title, ": ") - for i = 1, getn(sent) do + io.write("checking ", m.headers.title, ": ") + for i = 1, table.getn(sent) do local s = sent[i] if s.title == m.headers.title and s.count > 0 then - %check_headers(s.headers, m.headers) - %check_body(s.body, m.body) + check_headers(s.headers, m.headers) + check_body(s.body, m.body) s.count = s.count - 1 print("ok") return end end - %fail("not found") + fail("not found") end local insert = function(sent, message) if type(message.rcpt) == "table" then - message.count = getn(message.rcpt) + message.count = table.getn(message.rcpt) else message.count = 1 end message.headers = message.headers or {} message.headers.title = message.title - tinsert(sent, message) + table.insert(sent, message) end local mark = function() - local time = _time() + local time = socket._time() return { time = time } end local wait = function(sentinel, n) local to - write("waiting for ", n, " messages: ") + io.write("waiting for ", n, " messages: ") while 1 do - local mbox = %parse.mbox(%get()) - if n == getn(mbox) then break end - if _time() - sentinel.time > 50 then + local mbox = parse(get()) + if n == table.getn(mbox) then break end + if socket._time() - sentinel.time > 50 then to = 1 break end - _sleep(1) - write(".") - flush(_STDOUT) + socket._sleep(1) + io.write(".") + io.stdout:flush() end - if to then %fail("timeout") + if to then fail("timeout") else print("ok") end end @@ -129,16 +137,16 @@ Otherwise the mailer would think that the dot . is the end of the message -and the remaining will cause +and the remaining text would cause a lot of trouble. ]] insert(sent, { from = from, rcpt = { - "luasock2@tecgraf.puc-rio.br", - "luasock", - "luasock1" + "luasocket@localhost", + "luasock3@dell-diego.cs.princeton.edu", + "luasock1@dell-diego.cs.princeton.edu" }, body = "multiple rcpt body", title = "multiple rcpt", @@ -147,8 +155,8 @@ insert(sent, { insert(sent, { from = from, rcpt = { - "luasock2@tecgraf.puc-rio.br", - "luasock", + "luasock2@localhost", + "luasock3", "luasock1" }, headers = { @@ -199,9 +207,9 @@ insert(sent, { title = "minimum message" }) -write("testing host not found: ") -local c, e = connect("wrong.host", 25) -local err = SMTP.mail{ +io.write("testing host not found: ") +local c, e = socket.connect("wrong.host", 25) +local err = socket.smtp.mail{ from = from, rcpt = rcpt, server = "wrong.host" @@ -209,44 +217,43 @@ local err = SMTP.mail{ if e ~= err then fail("wrong error message") else print("ok") end -write("testing invalid from: ") -local err = SMTP.mail{ +io.write("testing invalid from: ") +local err = socket.smtp.mail{ from = ' " " (( _ * ', rcpt = rcpt, } if not err then fail("wrong error message") else print(err) end -write("testing no rcpt: ") -local err = SMTP.mail{ +io.write("testing no rcpt: ") +local err = socket.smtp.mail{ from = from, } if not err then fail("wrong error message") else print(err) end -write("clearing mailbox: ") +io.write("clearing mailbox: ") empty() print("ok") -write("sending messages: ") -for i = 1, getn(sent) do - err = SMTP.mail(sent[i]) +io.write("sending messages: ") +for i = 1, table.getn(sent) do + err = socket.smtp.mail(sent[i]) if err then fail(err) end - write("+") - flush(_STDOUT) + io.write("+") + io.stdout:flush() end print("ok") wait(mark(), total()) -write("parsing mailbox: ") -local mbox = parse.mbox(get()) -print(getn(mbox) .. " messages found!") +io.write("parsing mailbox: ") +local mbox = parse(get()) +print(table.getn(mbox) .. " messages found!") -for i = 1, getn(mbox) do +for i = 1, table.getn(mbox) do check(sent, mbox[i]) end - print("passed all tests") -print(format("done in %.2fs", _time() - t)) +print(string.format("done in %.2fs", socket._time() - t)) diff --git a/test/urltest.lua b/test/urltest.lua index 8ca36fe..b97844d 100644 --- a/test/urltest.lua +++ b/test/urltest.lua @@ -1,5 +1,8 @@ + + + local check_build_url = function(parsed) - local built = URL.build_url(parsed) + local built = socket.url.build(parsed) if built ~= parsed.url then print("built is different from expected") print(built) @@ -9,7 +12,7 @@ local check_build_url = function(parsed) end local check_protect = function(parsed, path, unsafe) - local built = URL.build_path(parsed, unsafe) + local built = socket.url.build_path(parsed, unsafe) if built ~= path then print(built, path) print("path composition failed.") @@ -18,9 +21,9 @@ local check_protect = function(parsed, path, unsafe) end local check_invert = function(url) - local parsed = URL.parse_url(url) - parsed.path = URL.build_path(URL.parse_path(parsed.path)) - local rebuilt = URL.build_url(parsed) + local parsed = socket.url.parse(url) + parsed.path = socket.url.build_path(socket.url.parse_path(parsed.path)) + local rebuilt = socket.url.build(parsed) if rebuilt ~= url then print(url, rebuilt) print("original and rebuilt are different") @@ -29,7 +32,7 @@ local check_invert = function(url) end local check_parse_path = function(path, expect) - local parsed = URL.parse_path(path) + local parsed = socket.url.parse_path(path) for i = 1, math.max(table.getn(parsed), table.getn(expect)) do if parsed[i] ~= expect[i] then print(path) @@ -48,7 +51,7 @@ local check_parse_path = function(path, expect) print("is_absolute mismatch") exit() end - local built = URL.build_path(expect) + local built = socket.url.build_path(expect) if built ~= path then print(built, path) print("path composition failed.") @@ -57,7 +60,7 @@ local check_parse_path = function(path, expect) end local check_absolute_url = function(base, relative, absolute) - local res = URL.absolute_url(base, relative) + local res = socket.url.absolute(base, relative) if res ~= absolute then write("absolute: In test for '", relative, "' expected '", absolute, "' but got '", res, "'\n") @@ -68,7 +71,7 @@ end local check_parse_url = function(gaba) local url = gaba.url gaba.url = nil - local parsed = URL.parse_url(url) + local parsed = socket.url.parse(url) for i, v in gaba do if v ~= parsed[i] then write("parse: In test for '", url, "' expected ", i, " = '", From d3d4156ef9d4d7e934e246dc265138be224f2612 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 20 Mar 2003 23:11:25 +0000 Subject: [PATCH 113/483] Completed port to Lua 5.0-beta. --- src/buffer.c | 10 +++++----- src/inet.c | 4 ++-- src/select.c | 2 ++ src/timeout.c | 2 +- src/udp.c | 20 ++++++++++---------- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/buffer.c b/src/buffer.c index 4260f20..2938b52 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -67,7 +67,7 @@ int buf_send(lua_State *L, p_buf buf) tm_markstart(&base->base_tm); for (arg = 2; arg <= top; arg++) { /* first arg is socket object */ size_t done, len; - cchar *data = luaL_opt_lstr(L, arg, NULL, &len); + cchar *data = luaL_optlstring(L, arg, NULL, &len); if (!data || err != PRIV_DONE) break; err = sendraw(L, buf, data, len, &done); total += done; @@ -111,12 +111,12 @@ int buf_receive(lua_State *L, p_buf buf) top++; } /* make sure we have enough stack space */ - luaL_check_stack(L, top+LUA_MINSTACK, "too many arguments"); + luaL_checkstack(L, top+LUA_MINSTACK, "too many arguments"); /* receive all patterns */ for (arg = 2; arg <= top && err == PRIV_DONE; arg++) { if (!lua_isnumber(L, arg)) { static cchar *patternnames[] = {"*l", "*lu", "*a", "*w", NULL}; - cchar *pattern = luaL_opt_string(L, arg, NULL); + cchar *pattern = luaL_optstring(L, arg, NULL); /* get next pattern */ switch (luaL_findstring(pattern, patternnames)) { case 0: /* DOS line pattern */ @@ -126,10 +126,10 @@ int buf_receive(lua_State *L, p_buf buf) case 2: /* Until closed pattern */ err = recvall(L, buf); break; case 3: /* Word pattern */ - luaL_arg_check(L, 0, arg, "word patterns are deprecated"); + luaL_argcheck(L, 0, arg, "word patterns are deprecated"); break; default: /* else it is an error */ - luaL_arg_check(L, 0, arg, "invalid receive pattern"); + luaL_argcheck(L, 0, arg, "invalid receive pattern"); break; } /* raw pattern */ diff --git a/src/inet.c b/src/inet.c index 3e89e88..eb4124b 100644 --- a/src/inet.c +++ b/src/inet.c @@ -86,7 +86,7 @@ void inet_construct(lua_State *L, p_inet inet) \*-------------------------------------------------------------------------*/ static int inet_lua_toip(lua_State *L) { - cchar *address = luaL_check_string(L, 1); + cchar *address = luaL_checkstring(L, 1); struct in_addr addr; struct hostent *hp; if (inet_aton(address, &addr)) @@ -114,7 +114,7 @@ static int inet_lua_toip(lua_State *L) \*-------------------------------------------------------------------------*/ static int inet_lua_tohostname(lua_State *L) { - cchar *address = luaL_check_string(L, 1); + cchar *address = luaL_checkstring(L, 1); struct in_addr addr; struct hostent *hp; if (inet_aton(address, &addr)) diff --git a/src/select.c b/src/select.c index 5c08730..4dcfd26 100644 --- a/src/select.c +++ b/src/select.c @@ -1,4 +1,6 @@ #include +#include + #include "lspriv.h" #include "lsselect.h" #include "lsfd.h" diff --git a/src/timeout.c b/src/timeout.c index 940ddca..dfece82 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -149,7 +149,7 @@ static int tm_lua_time(lua_State *L) \*-------------------------------------------------------------------------*/ int tm_lua_sleep(lua_State *L) { - double n = luaL_check_number(L, 1); + double n = luaL_checknumber(L, 1); #ifdef WIN32 Sleep(n*1000); #else diff --git a/src/udp.c b/src/udp.c index 29004fd..fd569c6 100644 --- a/src/udp.c +++ b/src/udp.c @@ -135,7 +135,7 @@ static int udp_lua_receive(lua_State *L) { p_udp udp = (p_udp) lua_touserdata(L, 1); unsigned char buffer[UDP_DATAGRAMSIZE]; - size_t got, wanted = (size_t) luaL_opt_number(L, 2, sizeof(buffer)); + size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buffer)); int err; p_tm tm = &udp->base_tm; wanted = MIN(wanted, sizeof(buffer)); @@ -164,7 +164,7 @@ static int udp_lua_receivefrom(lua_State *L) struct sockaddr_in peer; int peer_len = sizeof(peer); unsigned char buffer[UDP_DATAGRAMSIZE]; - size_t wanted = (size_t) luaL_opt_number(L, 2, sizeof(buffer)); + size_t wanted = (size_t) luaL_optnumber(L, 2, sizeof(buffer)); size_t got; int err; if (udp->udp_connected) luaL_error(L, "receivefrom on connected socket"); @@ -200,7 +200,7 @@ static int udp_lua_send(lua_State *L) p_tm tm = &udp->base_tm; size_t wanted, sent = 0; int err; - cchar *data = luaL_check_lstr(L, 2, &wanted); + cchar *data = luaL_checklstring(L, 2, &wanted); if (!udp->udp_connected) luaL_error(L, "send on unconnected socket"); tm_markstart(tm); err = compat_send(udp->fd, data, wanted, &sent, tm_getremaining(tm)); @@ -224,9 +224,9 @@ static int udp_lua_sendto(lua_State *L) { p_udp udp = (p_udp) lua_touserdata(L, 1); size_t wanted, sent = 0; - cchar *data = luaL_check_lstr(L, 2, &wanted); - cchar *ip = luaL_check_string(L, 3); - ushort port = (ushort) luaL_check_number(L, 4); + cchar *data = luaL_checklstring(L, 2, &wanted); + cchar *ip = luaL_checkstring(L, 3); + ushort port = (ushort) luaL_checknumber(L, 4); p_tm tm = &udp->base_tm; struct sockaddr_in peer; int err; @@ -255,8 +255,8 @@ static int udp_lua_sendto(lua_State *L) static int udp_lua_setsockname(lua_State * L) { p_udp udp = (p_udp) lua_touserdata(L, 1); - cchar *address = luaL_check_string(L, 2); - ushort port = (ushort) luaL_check_number(L, 3); + cchar *address = luaL_checkstring(L, 2); + ushort port = (ushort) luaL_checknumber(L, 3); cchar *err = inet_trybind((p_inet) udp, address, port); if (err) lua_pushstring(L, err); else lua_pushnil(L); @@ -275,8 +275,8 @@ static int udp_lua_setsockname(lua_State * L) static int udp_lua_setpeername(lua_State *L) { p_udp udp = (p_udp) lua_touserdata(L, 1); - cchar *address = luaL_check_string(L, 2); - ushort port = (ushort) luaL_check_number(L, 3); + cchar *address = luaL_checkstring(L, 2); + ushort port = (ushort) luaL_checknumber(L, 3); cchar *err = inet_tryconnect((p_inet) udp, address, port); if (!err) { udp->udp_connected = 1; From cf4d923d70992c152917cf6100753ebf0eac89d6 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 21 Mar 2003 01:07:23 +0000 Subject: [PATCH 114/483] Ported to Win32! --- NEW | 5 +++++ TODO | 38 ++++++++++++++++++++++++++++++++++++++ src/luasocket.c | 2 +- src/timeout.c | 4 ++-- src/unix.c | 10 ++++------ src/unix.h | 39 ++++----------------------------------- test/testclnt.lua | 2 +- 7 files changed, 55 insertions(+), 45 deletions(-) create mode 100644 NEW create mode 100644 TODO diff --git a/NEW b/NEW new file mode 100644 index 0000000..749641a --- /dev/null +++ b/NEW @@ -0,0 +1,5 @@ +Socket structures are independent +UDPBUFFERSIZE is now internal +Better treatment of closed connections: test!!! +HTTP post now deals with 1xx codes +connect, bind etc only try first address returned by resolver diff --git a/TODO b/TODO new file mode 100644 index 0000000..50031b1 --- /dev/null +++ b/TODO @@ -0,0 +1,38 @@ +- Inicializaccao das classes pode falhar? + +* Como mostrar um erro em lua_socketlibopen()... +* O location do "redirect" pode ser relativo ao servidor atual (não pode, + mas os servidores fazem merda...) +* - Ajeitar para Lua 4.1 + +- Padronizar os retornos de funccao +- Thread-safe + - proteger gethostby*.* com um mutex GLOBAL! + - proteger o atomizar o conjunto (timedout, receive), (timedout, send) +- Usar "require" nos módulos +- SSL +- Fazer compilar com g++ +- usar lua_verror +- separar as classes em arquivos +- criar mais uma classe, a de stream, entre p_sock e p_client +- criar um internal include file ls.h +- impedir que voe quando chamar accept(udpsocket()) +- trocar recv and send por read e write (ver se funciona) + +- checar operações em closed sockets +- checar teste de writable socket com select + +- trocar IPv4 para networking ou ipc + +- checar todos os metodos +- checar options em UDP +- checar todas as globais +- checar os metodos virtuais +- checar garbage collection + +- unix 92 bytes maximo no endereço, incluindo o zero +- unix 9216 maximo de datagram size + +- retorno de send/receive em datagram sockets pode ser refused... + +- adicionar um método sock:setoption??? diff --git a/src/luasocket.c b/src/luasocket.c index 26bc014..f6d1df7 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -44,11 +44,11 @@ \*-------------------------------------------------------------------------*/ LUASOCKET_API int lua_socketlibopen(lua_State *L) { + compat_open(L); priv_open(L); select_open(L); base_open(L); tm_open(L); - compat_open(L); fd_open(L); sock_open(L); inet_open(L); diff --git a/src/timeout.c b/src/timeout.c index dfece82..50a84da 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -151,9 +151,9 @@ int tm_lua_sleep(lua_State *L) { double n = luaL_checknumber(L, 1); #ifdef WIN32 - Sleep(n*1000); + Sleep((int)n*1000); #else - sleep(n); + sleep((int)n); #endif return 0; } diff --git a/src/unix.c b/src/unix.c index 0fc08bd..511a6bb 100644 --- a/src/unix.c +++ b/src/unix.c @@ -1,8 +1,6 @@ /*=========================================================================*\ * Network compatibilization module \*=========================================================================*/ -#include - #include #include @@ -17,14 +15,14 @@ static cchar *try_setbooloption(lua_State *L, COMPAT_FD sock, int name); /*=========================================================================*\ * Exported functions. \*=========================================================================*/ -void compat_open(lua_State *L) +int compat_open(lua_State *L) { - /* Instals a handler to ignore sigpipe. This function is not - needed on the WinSock2, since it's sockets don't raise signals. */ + /* Instals a handler to ignore sigpipe. */ struct sigaction new; memset(&new, 0, sizeof(new)); new.sa_handler = SIG_IGN; sigaction(SIGPIPE, &new, NULL); + return 1; } COMPAT_FD compat_accept(COMPAT_FD s, struct sockaddr *addr, @@ -59,7 +57,7 @@ int compat_send(COMPAT_FD c, cchar *data, size_t count, size_t *sent, err = PRIV_CLOSED; #ifdef __CYGWIN__ /* this is for CYGWIN, which is like Unix but has Win32 bugs */ - if (sent < 0 && errno == EWOULDBLOCK) err = PRIV_DONE; + if (errno == EWOULDBLOCK) err = PRIV_DONE; #endif *sent = 0; } else { diff --git a/src/unix.h b/src/unix.h index 944b471..5f89569 100644 --- a/src/unix.h +++ b/src/unix.h @@ -1,7 +1,5 @@ -#ifndef COMPAT_H_ -#define COMPAT_H_ - -#include "lspriv.h" +#ifndef UNIX_H_ +#define UNIX_H_ /*=========================================================================*\ * BSD include files @@ -24,46 +22,17 @@ #include /* sigpipe handling */ #include - +/* IP stuff*/ #include #include #define COMPAT_FD int #define COMPAT_INVALIDFD (-1) -/* we are lazy... */ -typedef struct sockaddr SA; - -/*=========================================================================*\ -* Exported functions -\*=========================================================================*/ -void compat_open(lua_State *L); - #define compat_bind bind #define compat_connect connect #define compat_listen listen #define compat_close close #define compat_select select -COMPAT_FD compat_socket(int domain, int type, int protocol); -COMPAT_FD compat_accept(COMPAT_FD s, SA *addr, int *len, int deadline); -int compat_send(COMPAT_FD c, cchar *data, size_t count, size_t *done, - int deadline); -int compat_recv(COMPAT_FD c, uchar *data, size_t count, size_t *done, - int deadline); -int compat_sendto(COMPAT_FD c, cchar *data, size_t count, size_t *done, - int deadline, SA *addr, int len); -int compat_recvfrom(COMPAT_FD c, uchar *data, size_t count, size_t *got, - int deadline, SA *addr, int *len); -void compat_setnonblocking(COMPAT_FD sock); -void compat_setblocking(COMPAT_FD sock); -void compat_setreuseaddr(COMPAT_FD sock); - -const char *compat_hoststrerror(void); -const char *compat_socketstrerror(void); -const char *compat_bindstrerror(void); -const char *compat_connectstrerror(void); - -cchar *compat_trysetoptions(lua_State *L, COMPAT_FD sock); - -#endif /* COMPAT_H_ */ +#endif /* UNIX_H_ */ diff --git a/test/testclnt.lua b/test/testclnt.lua index 15f1dd8..7c65823 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -13,7 +13,7 @@ function fail(...) end function warn(...) - local s = format(unpack(arg)) + local s = string.format(unpack(arg)) io.write("WARNING: ", s, "\n") end From 307603b24dde69eac62d2cb52123488137520c9c Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 21 Mar 2003 23:49:18 +0000 Subject: [PATCH 115/483] The test directory! --- test/auth/.htaccess | 4 + test/auth/.htpasswd | 1 + test/auth/index.html | 3002 +++++++++++++++++++++++++++++++++++++++ test/cgi/cat | 6 + test/cgi/cat-index-html | 5 + test/cgi/env | 5 + test/cgi/query-string | 4 + test/cgi/redirect-loop | 3 + test/cgi/request-uri | 4 + test/index.html | 3002 +++++++++++++++++++++++++++++++++++++++ test/upload.html | 15 + 11 files changed, 6051 insertions(+) create mode 100644 test/auth/.htaccess create mode 100644 test/auth/.htpasswd create mode 100644 test/auth/index.html create mode 100755 test/cgi/cat create mode 100755 test/cgi/cat-index-html create mode 100755 test/cgi/env create mode 100755 test/cgi/query-string create mode 100755 test/cgi/redirect-loop create mode 100755 test/cgi/request-uri create mode 100644 test/index.html create mode 100644 test/upload.html diff --git a/test/auth/.htaccess b/test/auth/.htaccess new file mode 100644 index 0000000..b9f100e --- /dev/null +++ b/test/auth/.htaccess @@ -0,0 +1,4 @@ +AuthName "Test Realm" +AuthType Basic +AuthUserFile /home/diego/tec/luasocket/test/auth/.htpasswd +require valid-user diff --git a/test/auth/.htpasswd b/test/auth/.htpasswd new file mode 100644 index 0000000..824bac9 --- /dev/null +++ b/test/auth/.htpasswd @@ -0,0 +1 @@ +luasocket:JFMQjUyOPu3yk diff --git a/test/auth/index.html b/test/auth/index.html new file mode 100644 index 0000000..786694e --- /dev/null +++ b/test/auth/index.html @@ -0,0 +1,3002 @@ + + +This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+ + diff --git a/test/cgi/cat b/test/cgi/cat new file mode 100755 index 0000000..8d41255 --- /dev/null +++ b/test/cgi/cat @@ -0,0 +1,6 @@ +#!/bin/sh +echo Content-type: text/plain +echo + +cat > /tmp/luasocket.cat.tmp +cat /tmp/luasocket.cat.tmp diff --git a/test/cgi/cat-index-html b/test/cgi/cat-index-html new file mode 100755 index 0000000..7595043 --- /dev/null +++ b/test/cgi/cat-index-html @@ -0,0 +1,5 @@ +#!/bin/sh +echo Content-type: text/plain +echo + +cat ../index.html diff --git a/test/cgi/env b/test/cgi/env new file mode 100755 index 0000000..412a716 --- /dev/null +++ b/test/cgi/env @@ -0,0 +1,5 @@ +#!/bin/sh +echo Content-type: text/plain +echo + +env diff --git a/test/cgi/query-string b/test/cgi/query-string new file mode 100755 index 0000000..2342af5 --- /dev/null +++ b/test/cgi/query-string @@ -0,0 +1,4 @@ +#!/bin/sh +echo Content-type: text/plain +echo +echo $QUERY_STRING diff --git a/test/cgi/redirect-loop b/test/cgi/redirect-loop new file mode 100755 index 0000000..bd32f20 --- /dev/null +++ b/test/cgi/redirect-loop @@ -0,0 +1,3 @@ +#!/bin/sh +echo Location: http://$HTTP_HOST$REQUEST_URI +echo diff --git a/test/cgi/request-uri b/test/cgi/request-uri new file mode 100755 index 0000000..20ebe9f --- /dev/null +++ b/test/cgi/request-uri @@ -0,0 +1,4 @@ +#!/bin/sh +echo Content-type: text/plain +echo +echo $REQUEST_URI diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..786694e --- /dev/null +++ b/test/index.html @@ -0,0 +1,3002 @@ + + +This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+This document can contain anything, as long as it is long enough!
+ + diff --git a/test/upload.html b/test/upload.html new file mode 100644 index 0000000..b4674a8 --- /dev/null +++ b/test/upload.html @@ -0,0 +1,15 @@ + + +POST test + + + +
+
+ +

+ +

+
+ + From f18d1b7cd0ec4708518ab5e18ea33b6eadca0301 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 28 Mar 2003 21:08:50 +0000 Subject: [PATCH 116/483] Closer to release... --- TODO | 1 + etc/check-links.lua | 8 ++++++-- etc/dict.lua | 19 +++++++++++++++---- etc/get.lua | 12 ++++++++---- samples/daytimeclnt.lua | 6 +++--- samples/echoclnt.lua | 12 ++++++------ samples/echosrvr.lua | 6 +++--- samples/listener.lua | 6 +++++- src/buffer.c | 2 ++ src/buffer.h | 3 ++- src/ftp.lua | 1 - src/http.lua | 1 - src/inet.c | 9 ++++++--- src/inet.h | 4 +++- src/luasocket.c | 7 +++++++ src/select.c | 18 ++++++++++++++++-- src/select.h | 4 ++++ src/smtp.lua | 1 - src/socket.h | 6 ++++++ src/timeout.c | 11 +++++------ src/timeout.h | 5 +++++ src/udp.c | 26 +++++++++++++++++++------- src/udp.h | 6 ++++++ src/unix.c | 15 +++++++++------ src/unix.h | 5 +++++ src/url.lua | 1 - test/smtptest.lua | 2 +- test/testclnt.lua | 12 ++++++------ test/testsrvr.lua | 6 +++--- test/tftptest.lua | 22 ++++++++++------------ test/urltest.lua | 3 +-- 31 files changed, 163 insertions(+), 77 deletions(-) diff --git a/TODO b/TODO index 50031b1..25d2586 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,5 @@ - Inicializaccao das classes pode falhar? +- Ajeitar melhor a hierarquia de classes. Ajeitar o file... * Como mostrar um erro em lua_socketlibopen()... * O location do "redirect" pode ser relativo ao servidor atual (não pode, diff --git a/etc/check-links.lua b/etc/check-links.lua index 0dca27c..705c0ce 100644 --- a/etc/check-links.lua +++ b/etc/check-links.lua @@ -1,3 +1,7 @@ +----------------------------------------------------------------------------- +-- Little program that checks links in HTML files +-- LuaSocket 1.5 sample files. +----------------------------------------------------------------------------- socket.http.TIMEOUT = 10 cache = {} @@ -14,7 +18,7 @@ end function getstatus(url) local parsed = socket.url.parse(url, { scheme = "file" }) - if cache[url] then return cache[url].res end + if cache[url] then return cache[url] end local res if parsed.scheme == "http" then local request = { url = url } @@ -34,7 +38,7 @@ function getstatus(url) res = nil else res = error end else res = string.format("unhandled scheme '%s'", parsed.scheme) end - cache[url] = { res = res } + cache[url] = res return res end diff --git a/etc/dict.lua b/etc/dict.lua index 4685ca1..6790cab 100644 --- a/etc/dict.lua +++ b/etc/dict.lua @@ -1,12 +1,16 @@ +----------------------------------------------------------------------------- +-- Little program to download DICT word definitions +-- LuaSocket 1.5 sample files +----------------------------------------------------------------------------- function get_status(sock, valid) local line, err = sock:receive() local code, par if not line then sock:close() return err end - _, _, code = strfind(line, "^(%d%d%d)") + _, _, code = string.find(line, "^(%d%d%d)") code = tonumber(code) if code ~= valid then return code end if code == 150 then - _,_,_, par = strfind(line, "^(%d%d%d) (%d*)") + _,_,_, par = string.find(line, "^(%d%d%d) (%d*)") par = tonumber(par) end return nil, par @@ -24,7 +28,7 @@ function get_def(sock) end function dict_open() - local sock, err = connect("dict.org", 2628) + local sock, err = socket.connect("dict.org", 2628) if not sock then return nil, err end sock:timeout(10) local code, par = get_status(sock, 220) @@ -48,7 +52,7 @@ function dict_define(sock, word, dict) end code, par = get_status(sock, 250) if code then return nil, code end - return gsub(defs, "%s%s$", "") + return string.gsub(defs, "%s%s$", "") end function dict_close(sock) @@ -65,3 +69,10 @@ function dict_get(word, dict) dict_close(sock) return defs, err end + +if arg and arg[1] then + defs, err = dict_get(arg[1], arg[2]) + print(defs or err) +else + io.write("Usage:\n luasocket dict.lua []\n") +end diff --git a/etc/get.lua b/etc/get.lua index 33da653..af46c16 100644 --- a/etc/get.lua +++ b/etc/get.lua @@ -1,3 +1,7 @@ +----------------------------------------------------------------------------- +-- Little program to download files from URLs +-- LuaSocket 1.5 sample files +----------------------------------------------------------------------------- -- formats a number of seconds into human readable form function nicetime(s) local l = "s" @@ -63,15 +67,15 @@ function receive2disk(file, size) size = size } local receive_cb = function(chunk, err) - local dt = socket._time() - %aux.start -- elapsed time since start + local dt = socket._time() - aux.start -- elapsed time since start if not chunk or chunk == "" then io.write("\n") aux.file:close() return end aux.file:write(chunk) - aux.got = aux.got + string.len(chunk) -- total bytes received - if dt < 0.1 then return 1 end -- not enough time for estimate + aux.got = aux.got + string.len(chunk) -- total bytes received + if dt < 0.1 then return 1 end -- not enough time for estimate io.write("\r", gauge(aux.got, dt, aux.size)) return 1 end @@ -122,7 +126,7 @@ function getschemeandname(url, name) return parsed.scheme, name end --- gets a file either by http or url, saving as name +-- gets a file either by http or ftp, saving as function get(url, name) local scheme scheme, name = getschemeandname(url, name) diff --git a/samples/daytimeclnt.lua b/samples/daytimeclnt.lua index 1107c6a..000dfd5 100644 --- a/samples/daytimeclnt.lua +++ b/samples/daytimeclnt.lua @@ -4,11 +4,11 @@ if arg then host = arg[1] or host port = arg[2] or port end -host = toip(host) -udp = udpsocket() +host = socket.toip(host) +udp = socket.udp() print("Using host '" ..host.. "' and port " ..port.. "...") err = udp:sendto("anything", host, port) if err then print(err) exit() end dgram, err = udp:receive() if not dgram then print(err) exit() end -write(dgram) +io.write(dgram) diff --git a/samples/echoclnt.lua b/samples/echoclnt.lua index 043b2f0..cd8b450 100644 --- a/samples/echoclnt.lua +++ b/samples/echoclnt.lua @@ -4,18 +4,18 @@ if arg then host = arg[1] or host port = arg[2] or port end -host = toip(host) -udp, err = udpsocket() +host = socket.toip(host) +udp, err = socket.udp() if not udp then print(err) exit() end err = udp:setpeername(host, port) if err then print(err) exit() end print("Using host '" ..host.. "' and port " .. port .. "...") while 1 do - line = read() - if not line then exit() end + line = io.read() + if not line then os.exit() end err = udp:send(line) - if err then print(err) exit() end + if err then print(err) os.exit() end dgram, err = udp:receive() - if not dgram then print(err) exit() end + if not dgram then print(err) os.exit() end print(dgram) end diff --git a/samples/echosrvr.lua b/samples/echosrvr.lua index 330f9e6..6117557 100644 --- a/samples/echosrvr.lua +++ b/samples/echosrvr.lua @@ -5,10 +5,10 @@ if arg then port = arg[2] or port end print("Binding to host '" ..host.. "' and port " ..port.. "...") -udp, err = udpsocket() -if not udp then print(err) exit() end +udp, err = socket.udp() +if not udp then print(err) os.exit() end err = udp:setsockname(host, port) -if err then print(err) exit() end +if err then print(err) os.exit() end udp:timeout(5) ip, port = udp:getsockname() print("Waiting packets on " .. ip .. ":" .. port .. "...") diff --git a/samples/listener.lua b/samples/listener.lua index 8e2c7ce..c035ab2 100644 --- a/samples/listener.lua +++ b/samples/listener.lua @@ -1,3 +1,7 @@ +----------------------------------------------------------------------------- +-- Little program to dump lines received at a given port +-- LuaSocket 1.5 sample files +----------------------------------------------------------------------------- host = host or "*" port = port or 8080 if arg then @@ -5,7 +9,7 @@ if arg then port = arg[2] or port end print("Binding to host '" ..host.. "' and port " ..port.. "...") -s, e = bind(host, port) +s, e = socket.bind(host, port) if not s then print(e) exit() diff --git a/src/buffer.c b/src/buffer.c index 2938b52..73df8b3 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -3,6 +3,8 @@ * Lua methods: * send: unbuffered send using C base_send * receive: buffered read using C base_receive +* +* RCS ID: $Id$ \*=========================================================================*/ #include #include diff --git a/src/buffer.h b/src/buffer.h index 7463a67..4943e3b 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -1,5 +1,6 @@ /*=========================================================================*\ * Buffered input/output routines +* * RCS ID: $Id$ \*=========================================================================*/ #ifndef BUF_H_ @@ -16,7 +17,7 @@ \*-------------------------------------------------------------------------*/ typedef struct t_buf_tag { size_t buf_first, buf_last; - uchar buf_data[BUF_SIZE]; + char buf_data[BUF_SIZE]; p_base buf_base; } t_buf; typedef t_buf *p_buf; diff --git a/src/ftp.lua b/src/ftp.lua index f6fffbb..4017eb5 100644 --- a/src/ftp.lua +++ b/src/ftp.lua @@ -2,7 +2,6 @@ -- FTP support for the Lua language -- LuaSocket 1.5 toolkit. -- Author: Diego Nehab --- Date: 26/12/2000 -- Conforming to: RFC 959, LTN7 -- RCS ID: $Id$ ----------------------------------------------------------------------------- diff --git a/src/http.lua b/src/http.lua index 3275e3b..59645ee 100644 --- a/src/http.lua +++ b/src/http.lua @@ -2,7 +2,6 @@ -- HTTP/1.1 client support for the Lua language. -- LuaSocket 1.5 toolkit. -- Author: Diego Nehab --- Date: 26/12/2000 -- Conforming to: RFC 2616, LTN7 -- RCS ID: $Id$ ----------------------------------------------------------------------------- diff --git a/src/inet.c b/src/inet.c index eb4124b..341c60e 100644 --- a/src/inet.c +++ b/src/inet.c @@ -1,11 +1,14 @@ /*=========================================================================*\ -* Internet domain class +* Internet domain class: inherits from the Socket class, and implement +* a few methods shared by all internet related objects * Lua methods: * getpeername: gets socket peer ip address and port * getsockname: gets local socket ip address and port * Global Lua fuctions: * toip: gets resolver info on host name * tohostname: gets resolver info on dotted-quad +* +* RCS ID: $Id$ \*=========================================================================*/ #include @@ -145,7 +148,7 @@ static int inet_lua_getpeername(lua_State *L) { p_sock sock = (p_sock) lua_touserdata(L, 1); struct sockaddr_in peer; - int peer_len = sizeof(peer); + size_t peer_len = sizeof(peer); if (getpeername(sock->fd, (SA *) &peer, &peer_len) < 0) { lua_pushnil(L); return 1; @@ -167,7 +170,7 @@ static int inet_lua_getsockname(lua_State *L) { p_sock sock = (p_sock) lua_touserdata(L, 1); struct sockaddr_in local; - int local_len = sizeof(local); + size_t local_len = sizeof(local); if (getsockname(sock->fd, (SA *) &local, &local_len) < 0) { lua_pushnil(L); return 1; diff --git a/src/inet.h b/src/inet.h index 3b0453e..93fcedf 100644 --- a/src/inet.h +++ b/src/inet.h @@ -1,5 +1,7 @@ /*=========================================================================*\ -* Internet domain class +* Internet domain class: inherits from the Socket class, and implement +* a few methods shared by all internet related objects +* * RCS ID: $Id$ \*=========================================================================*/ #ifndef INET_H_ diff --git a/src/luasocket.c b/src/luasocket.c index f6d1df7..358b25e 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -63,6 +63,13 @@ LUASOCKET_API int lua_socketlibopen(lua_State *L) lua_dofile(L, "http.lua"); lua_dofile(L, "smtp.lua"); lua_dofile(L, "ftp.lua"); +#else +#include "concat.loh" +#include "code.loh" +#include "url.loh" +#include "http.loh" +#include "smtp.loh" +#include "ftp.loh" #endif return 0; } diff --git a/src/select.c b/src/select.c index 4dcfd26..6afdb87 100644 --- a/src/select.c +++ b/src/select.c @@ -1,6 +1,13 @@ +/*=========================================================================*\ +* Select implementation +* Global Lua fuctions: +* select: waits until socket ready +* RCS ID: $Id$ +\*=========================================================================*/ #include #include +#include "luasocket.h" #include "lspriv.h" #include "lsselect.h" #include "lsfd.h" @@ -33,10 +40,17 @@ void select_open(lua_State *L) { /* push select auxiliar lua function and register * select_lua_select with it as an upvalue */ - luaL_loadfile(L, "lsselect.lua"); - lua_call(L, 0, 1); +#ifdef LUASOCKET_DEBUG + lua_dofile(L, "lsselect.lua"); +#else +#include "lsselect.loh" +#endif + lua_getglobal(L, LUASOCKET_LIBNAME); + lua_pushstring(L, "_select"); + lua_gettable(L, -2); lua_pushcclosure(L, select_lua_select, 1); priv_newglobal(L, "select"); + lua_pop(L, 1); /* create luasocket(select) table */ lua_pushstring(L, "luasocket(select)"); lua_newtable(L); diff --git a/src/select.h b/src/select.h index c3267ad..2b2ed19 100644 --- a/src/select.h +++ b/src/select.h @@ -1,3 +1,7 @@ +/*=========================================================================*\ +* Select implementation +* RCS ID: $Id$ +\*=========================================================================*/ #ifndef SLCT_H_ #define SLCT_H_ diff --git a/src/smtp.lua b/src/smtp.lua index 5da9a6f..0ba2b0f 100644 --- a/src/smtp.lua +++ b/src/smtp.lua @@ -2,7 +2,6 @@ -- SMTP support for the Lua language. -- LuaSocket 1.5 toolkit -- Author: Diego Nehab --- Date: 26/12/2000 -- Conforming to: RFC 821, LTN7 -- RCS ID: $Id$ ----------------------------------------------------------------------------- diff --git a/src/socket.h b/src/socket.h index c9dee20..9972639 100644 --- a/src/socket.h +++ b/src/socket.h @@ -1,3 +1,9 @@ +/*=========================================================================*\ +* Socket class: inherits from the File Descriptor class and is here just +* for extensibility in the future +* +* RCS ID: $id$ +\*=========================================================================*/ #ifndef SOCK_H_ #define SOCK_H_ diff --git a/src/timeout.c b/src/timeout.c index 50a84da..5549c89 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -1,5 +1,10 @@ /*=========================================================================*\ * Timeout management functions +* Global Lua functions: +* _sleep: (debug mode only) +* _time: (debug mode only) +* +* RCS ID: $Id$ \*=========================================================================*/ #include #include @@ -20,10 +25,8 @@ /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ -#ifdef LUASOCKET_DEBUG static int tm_lua_time(lua_State *L); static int tm_lua_sleep(lua_State *L); -#endif /*=========================================================================*\ * Exported functions. @@ -123,12 +126,10 @@ int tm_gettime(void) void tm_open(lua_State *L) { (void) L; -#ifdef LUASOCKET_DEBUG lua_pushcfunction(L, tm_lua_time); priv_newglobal(L, "_time"); lua_pushcfunction(L, tm_lua_sleep); priv_newglobal(L, "_sleep"); -#endif } /*=========================================================================*\ @@ -137,7 +138,6 @@ void tm_open(lua_State *L) /*-------------------------------------------------------------------------*\ * Returns the time the system has been up, in secconds. \*-------------------------------------------------------------------------*/ -#ifdef LUASOCKET_DEBUG static int tm_lua_time(lua_State *L) { lua_pushnumber(L, tm_gettime()/1000.0); @@ -157,4 +157,3 @@ int tm_lua_sleep(lua_State *L) #endif return 0; } -#endif diff --git a/src/timeout.h b/src/timeout.h index af7e591..1dc0a5a 100644 --- a/src/timeout.h +++ b/src/timeout.h @@ -1,3 +1,8 @@ +/*=========================================================================*\ +* Timeout management functions +* +* RCS ID: $Id$ +\*=========================================================================*/ #ifndef _TM_H #define _TM_H diff --git a/src/udp.c b/src/udp.c index fd569c6..361816c 100644 --- a/src/udp.c +++ b/src/udp.c @@ -1,5 +1,17 @@ /*=========================================================================*\ -* UDP socket object implementation (inherits from sock and inet) +* UDP class: inherits from Socked and Internet domain classes and provides +* all the functionality for UDP objects. +* Lua methods: +* send: using compat module +* sendto: using compat module +* receive: using compat module +* receivefrom: using compat module +* setpeername: using internet module +* setsockname: using internet module +* Global Lua functions: +* udp: creates the udp object +* +* RCS ID: $Id$ \*=========================================================================*/ #include @@ -21,7 +33,7 @@ static int udp_lua_receivefrom(lua_State *L); static int udp_lua_setpeername(lua_State *L); static int udp_lua_setsockname(lua_State *L); -static int udp_global_udpsocket(lua_State *L); +static int udp_global_udp(lua_State *L); static struct luaL_reg funcs[] = { {"send", udp_lua_send}, @@ -44,7 +56,7 @@ void udp_open(lua_State *L) priv_newclass(L, UDP_CLASS); udp_inherit(L, UDP_CLASS); /* declare global functions */ - lua_pushcfunction(L, udp_global_udpsocket); + lua_pushcfunction(L, udp_global_udp); priv_newglobal(L, "udp"); for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) priv_newglobalmethod(L, funcs[i].name); @@ -99,7 +111,7 @@ p_udp udp_push(lua_State *L) * On success: udp socket * On error: nil, followed by an error message \*-------------------------------------------------------------------------*/ -static int udp_global_udpsocket(lua_State *L) +static int udp_global_udp(lua_State *L) { int oldtop = lua_gettop(L); p_udp udp = udp_push(L); @@ -134,7 +146,7 @@ static int udp_global_udpsocket(lua_State *L) static int udp_lua_receive(lua_State *L) { p_udp udp = (p_udp) lua_touserdata(L, 1); - unsigned char buffer[UDP_DATAGRAMSIZE]; + char buffer[UDP_DATAGRAMSIZE]; size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buffer)); int err; p_tm tm = &udp->base_tm; @@ -162,8 +174,8 @@ static int udp_lua_receivefrom(lua_State *L) p_udp udp = (p_udp) lua_touserdata(L, 1); p_tm tm = &udp->base_tm; struct sockaddr_in peer; - int peer_len = sizeof(peer); - unsigned char buffer[UDP_DATAGRAMSIZE]; + size_t peer_len = sizeof(peer); + char buffer[UDP_DATAGRAMSIZE]; size_t wanted = (size_t) luaL_optnumber(L, 2, sizeof(buffer)); size_t got; int err; diff --git a/src/udp.h b/src/udp.h index 3c82c29..928a99f 100644 --- a/src/udp.h +++ b/src/udp.h @@ -1,3 +1,9 @@ +/*=========================================================================*\ +* UDP class: inherits from Socked and Internet domain classes and provides +* all the functionality for UDP objects. +* +* RCS ID: $Id$ +\*=========================================================================*/ #ifndef UDP_H_ #define UDP_H_ diff --git a/src/unix.c b/src/unix.c index 511a6bb..23984b0 100644 --- a/src/unix.c +++ b/src/unix.c @@ -1,8 +1,11 @@ /*=========================================================================*\ -* Network compatibilization module +* Network compatibilization module: Unix version +* +* RCS ID: $Id$ \*=========================================================================*/ #include #include +#include #include "lscompat.h" @@ -26,7 +29,7 @@ int compat_open(lua_State *L) } COMPAT_FD compat_accept(COMPAT_FD s, struct sockaddr *addr, - int *len, int deadline) + size_t *len, int deadline) { struct timeval tv; fd_set fds; @@ -72,7 +75,7 @@ int compat_send(COMPAT_FD c, cchar *data, size_t count, size_t *sent, } int compat_sendto(COMPAT_FD c, cchar *data, size_t count, size_t *sent, - int deadline, SA *addr, int len) + int deadline, SA *addr, size_t len) { struct timeval tv; fd_set fds; @@ -104,7 +107,7 @@ int compat_sendto(COMPAT_FD c, cchar *data, size_t count, size_t *sent, } } -int compat_recv(COMPAT_FD c, uchar *data, size_t count, size_t *got, +int compat_recv(COMPAT_FD c, char *data, size_t count, size_t *got, int deadline) { struct timeval tv; @@ -131,8 +134,8 @@ int compat_recv(COMPAT_FD c, uchar *data, size_t count, size_t *got, } } -int compat_recvfrom(COMPAT_FD c, uchar *data, size_t count, size_t *got, - int deadline, SA *addr, int *len) +int compat_recvfrom(COMPAT_FD c, char *data, size_t count, size_t *got, + int deadline, SA *addr, size_t *len) { struct timeval tv; fd_set fds; diff --git a/src/unix.h b/src/unix.h index 5f89569..863e478 100644 --- a/src/unix.h +++ b/src/unix.h @@ -1,3 +1,8 @@ +/*=========================================================================*\ +* Network compatibilization module: Unix version +* +* RCS ID: $Id$ +\*=========================================================================*/ #ifndef UNIX_H_ #define UNIX_H_ diff --git a/src/url.lua b/src/url.lua index 2cf9669..06de9d3 100644 --- a/src/url.lua +++ b/src/url.lua @@ -2,7 +2,6 @@ -- URI parsing, composition and relative URL resolution -- LuaSocket 1.5 toolkit. -- Author: Diego Nehab --- Date: 20/7/2001 -- Conforming to: RFC 2396, LTN7 -- RCS ID: $Id$ ---------------------------------------------------------------------------- diff --git a/test/smtptest.lua b/test/smtptest.lua index 1bba27f..27ba400 100644 --- a/test/smtptest.lua +++ b/test/smtptest.lua @@ -60,7 +60,7 @@ local empty = function() end local get = function() - s = "" + local s = "" for i,v in ipairs(files) do s = s .. "\n" .. readfile(v) end diff --git a/test/testclnt.lua b/test/testclnt.lua index 7c65823..3e80a36 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -1,5 +1,5 @@ -HOST = HOST or "localhost" -PORT = PORT or "8080" +host = host or "localhost" +port = port or "8080" function pass(...) local s = string.format(unpack(arg)) @@ -83,14 +83,14 @@ function tcpreconnect() if data then data:close() data = nil end data = server:accept() ]] - data, err = socket.connect(HOST, PORT) + data, err = socket.connect(host, port) if not data then fail(err) else pass("connected!") end end reconnect = tcpreconnect pass("attempting control connection...") -control, err = socket.connect(HOST, PORT) +control, err = socket.connect(host, port) if err then fail(err) else pass("connected!") end @@ -104,10 +104,10 @@ function empty_connect() if data then data:close() data = nil end data = server:accept() ]] - data, err = socket.connect("", PORT) + data, err = socket.connect("", port) if not data then pass("ok") - data = socket.connect(HOST, PORT) + data = socket.connect(host, port) else fail("should not have connected!") end end diff --git a/test/testsrvr.lua b/test/testsrvr.lua index 141c058..fb77ea5 100644 --- a/test/testsrvr.lua +++ b/test/testsrvr.lua @@ -1,7 +1,7 @@ -HOST = HOST or "localhost" -PORT = PORT or "8080" +host = host or "localhost" +port = port or "8080" -server, error = socket.bind(HOST, PORT) +server, error = socket.bind(host, port) if not server then print("server: " .. tostring(error)) os.exit() end while 1 do print("server: waiting for client connection..."); diff --git a/test/tftptest.lua b/test/tftptest.lua index b29657a..a435ad4 100644 --- a/test/tftptest.lua +++ b/test/tftptest.lua @@ -1,25 +1,23 @@ --- load tftpclng.lua -assert(dofile("../examples/tftpclnt.lua")) +-- load tftpclnt.lua +dofile("tftpclnt.lua") -- needs tftp server running on localhost, with root pointing to --- /home/i/diego/public/html/luasocket/test +-- a directory with index.html in it function readfile(file) - local f = openfile("file", "rb") - local a - if f then - a = read(f, "*a") - closefile(f) - end - return a + local f = io.open(file, "r") + if not f then return nil end + local a = f:read("*a") + f:close() + return a end host = host or "localhost" print("downloading") err = tftp_get(host, 69, "index.html", "index.got") assert(not err, err) -original = readfile("index.index") +original = readfile("test/index.html") retrieved = readfile("index.got") -remove("index.got") +os.remove("index.got") assert(original == retrieved, "files differ!") print("passed") diff --git a/test/urltest.lua b/test/urltest.lua index b97844d..7b1f0c0 100644 --- a/test/urltest.lua +++ b/test/urltest.lua @@ -1,5 +1,4 @@ - - +dofile("noglobals.lua") local check_build_url = function(parsed) local built = socket.url.build(parsed) From d0560e0b4419cc6812f83b88b1cb9009481eab1f Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 28 Mar 2003 21:24:30 +0000 Subject: [PATCH 117/483] Local index in for loop was getting changed... --- samples/tinyirc.lua | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/samples/tinyirc.lua b/samples/tinyirc.lua index b0c4168..d3e56e7 100644 --- a/samples/tinyirc.lua +++ b/samples/tinyirc.lua @@ -1,11 +1,11 @@ function set_add(set, sock) - tinsert(set, sock) + table.insert(set, sock) end function set_remove(set, sock) - for i = 1, getn(set) do + for i = 1, table.getn(set) do if set[i] == sock then - tremove(set, i) + table.remove(set, i) break end end @@ -20,10 +20,10 @@ if arg then port2 = arg[3] or port2 end -server1, error = bind(host, port1) +server1, error = socket.bind(host, port1) if not server1 then print(error) exit() end server1:timeout(1) -server2, error = bind(host, port2) +server2, error = socket.bind(host, port2) if not server2 then print(error) exit() end server2:timeout(1) @@ -35,7 +35,7 @@ sock_id[server2] = 2 next_id = 3 while 1 do - local readable, _, error = select(sock_set, nil) + local readable, _, error = socket.select(sock_set, nil) for _, sock in readable do -- is it a server socket if sock_id[sock] < 3 then @@ -44,8 +44,8 @@ while 1 do incomming:timeout(1) sock_id[incomming] = next_id set_add(sock_set, incomming) - write("Added client id ", next_id, ". ", - getn(sock_set)-2, " total.\n") + io.write("Added client id ", next_id, ". ", + table.getn(sock_set)-2, " total.\n") next_id = next_id + 1 end -- it is a client socket @@ -55,17 +55,17 @@ while 1 do if error then sock:close() set_remove(sock_set, sock) - write("Removed client number ", id, ". ", + io.write("Removed client number ", id, ". ", getn(sock_set)-2, " total.\n") else - write("Broadcasting line '", id, "> ", line, "'.\n") - _, writable, error = select(nil, sock_set, 1) + io.write("Broadcasting line '", id, "> ", line, "'.\n") + __, writable, error = socket.select(nil, sock_set, 1) if not error then - for _, outgoing in writable do - write("Sending to client ", sock_id[outgoing], "\n") + for ___, outgoing in writable do + io.write("Sending to client ", sock_id[outgoing], "\n") outgoing:send(id, "> ", line, "\r\n") end - else write("No one ready to listen!!!\n") end + else io.write("No one ready to listen!!!\n") end end end end From 335cdbf334dde609c32cc46ad9536980066504f6 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 28 Mar 2003 22:14:06 +0000 Subject: [PATCH 118/483] Compiled in SunOS --- src/select.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/select.c b/src/select.c index 6afdb87..0c14ba6 100644 --- a/src/select.c +++ b/src/select.c @@ -4,6 +4,7 @@ * select: waits until socket ready * RCS ID: $Id$ \*=========================================================================*/ +#include #include #include From 4755ad727e36f2c3ad644718a2d477fffa9b1d23 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 28 Mar 2003 22:41:26 +0000 Subject: [PATCH 119/483] Compiled in WinXP --- test/httptest.lua | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/test/httptest.lua b/test/httptest.lua index 8b84f84..1eb4b6a 100644 --- a/test/httptest.lua +++ b/test/httptest.lua @@ -49,26 +49,26 @@ local request, response, ignore, expect, index, prefix, cgiprefix local t = socket._time() -HOST = HOST or "localhost" +host = host or "localhost" prefix = prefix or "/luasocket" cgiprefix = cgiprefix or "/luasocket/cgi" index = readfile("test/index.html") io.write("testing request uri correctness: ") local forth = cgiprefix .. "/request-uri?" .. "this+is+the+query+string" -local back = socket.http.get("http://" .. HOST .. forth) +local back = socket.http.get("http://" .. host .. forth) if similar(back, forth) then print("ok") else fail("failed!") end io.write("testing query string correctness: ") forth = "this+is+the+query+string" -back = socket.http.get("http://" .. HOST .. cgiprefix .. "/query-string?" .. forth) +back = socket.http.get("http://" .. host .. cgiprefix .. "/query-string?" .. forth) if similar(back, forth) then print("ok") else fail("failed!") end io.write("testing document retrieval: ") request = { - url = "http://" .. HOST .. prefix .. "/index.html" + url = "http://" .. host .. prefix .. "/index.html" } expect = { body = index, @@ -82,7 +82,7 @@ check_request(request, expect, ignore) io.write("testing http redirection: ") request = { - url = "http://" .. HOST .. prefix + url = "http://" .. host .. prefix } expect = { body = index, @@ -97,7 +97,7 @@ check_request(request, expect, ignore) io.write("testing automatic auth failure: ") request = { - url = "http://really:wrong@" .. HOST .. prefix .. "/auth/index.html" + url = "http://really:wrong@" .. host .. prefix .. "/auth/index.html" } expect = { code = 401 @@ -111,7 +111,7 @@ check_request(request, expect, ignore) io.write("testing http redirection failure: ") request = { - url = "http://" .. HOST .. prefix, + url = "http://" .. host .. prefix, stay = 1 } expect = { @@ -137,7 +137,7 @@ check_request(request, expect, ignore) io.write("testing invalid url: ") request = { - url = HOST .. prefix + url = host .. prefix } local c, e = socket.connect("", 80) expect = { @@ -148,7 +148,7 @@ check_request(request, expect, ignore) io.write("testing document not found: ") request = { - url = "http://" .. HOST .. "/wrongdocument.html" + url = "http://" .. host .. "/wrongdocument.html" } expect = { code = 404 @@ -162,7 +162,7 @@ check_request(request, expect, ignore) io.write("testing auth failure: ") request = { - url = "http://" .. HOST .. prefix .. "/auth/index.html" + url = "http://" .. host .. prefix .. "/auth/index.html" } expect = { code = 401 @@ -176,7 +176,7 @@ check_request(request, expect, ignore) io.write("testing manual basic auth: ") request = { - url = "http://" .. HOST .. prefix .. "/auth/index.html", + url = "http://" .. host .. prefix .. "/auth/index.html", headers = { authorization = "Basic " .. socket.code.base64("luasocket:password") } @@ -193,7 +193,7 @@ check_request(request, expect, ignore) io.write("testing automatic basic auth: ") request = { - url = "http://luasocket:password@" .. HOST .. prefix .. "/auth/index.html" + url = "http://luasocket:password@" .. host .. prefix .. "/auth/index.html" } expect = { code = 200, @@ -207,7 +207,7 @@ check_request(request, expect, ignore) io.write("testing auth info overriding: ") request = { - url = "http://really:wrong@" .. HOST .. prefix .. "/auth/index.html", + url = "http://really:wrong@" .. host .. prefix .. "/auth/index.html", user = "luasocket", password = "password" } @@ -223,7 +223,7 @@ check_request(request, expect, ignore) io.write("testing cgi output retrieval (probably chunked...): ") request = { - url = "http://" .. HOST .. cgiprefix .. "/cat-index-html" + url = "http://" .. host .. cgiprefix .. "/cat-index-html" } expect = { body = index, @@ -237,7 +237,7 @@ check_request(request, expect, ignore) io.write("testing redirect loop: ") request = { - url = "http://" .. HOST .. cgiprefix .. "/redirect-loop" + url = "http://" .. host .. cgiprefix .. "/redirect-loop" } expect = { code = 302 @@ -251,7 +251,7 @@ check_request(request, expect, ignore) io.write("testing post method: ") request = { - url = "http://" .. HOST .. cgiprefix .. "/cat", + url = "http://" .. host .. cgiprefix .. "/cat", method = "POST", body = index } @@ -267,7 +267,7 @@ check_request(request, expect, ignore) io.write("testing wrong scheme: ") request = { - url = "wrong://" .. HOST .. cgiprefix .. "/cat", + url = "wrong://" .. host .. cgiprefix .. "/cat", method = "GET" } expect = { @@ -279,24 +279,24 @@ check_request(request, expect, ignore) local body io.write("testing simple get function: ") -body = socket.http.get("http://" .. HOST .. prefix .. "/index.html") +body = socket.http.get("http://" .. host .. prefix .. "/index.html") check(body == index) io.write("testing simple get function with table args: ") body = socket.http.get { - url = "http://really:wrong@" .. HOST .. prefix .. "/auth/index.html", + url = "http://really:wrong@" .. host .. prefix .. "/auth/index.html", user = "luasocket", password = "password" } check(body == index) io.write("testing simple post function: ") -body = socket.http.post("http://" .. HOST .. cgiprefix .. "/cat", index) +body = socket.http.post("http://" .. host .. cgiprefix .. "/cat", index) check(body == index) io.write("testing simple post function with table args: ") body = socket.http.post { - url = "http://" .. HOST .. cgiprefix .. "/cat", + url = "http://" .. host .. cgiprefix .. "/cat", body = index } check(body == index) From c1ef3e7103cc652d2004ef1ddc9409b946207f33 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 28 Mar 2003 23:29:45 +0000 Subject: [PATCH 120/483] Released luasocket-1.5-work! --- README | 46 ++++++++++------------------------------------ makefile.dist | 37 +++++++++++++++++++------------------ src/luasocket.c | 2 +- src/select.c | 2 +- 4 files changed, 31 insertions(+), 56 deletions(-) diff --git a/README b/README index c30799c..091ce6f 100644 --- a/README +++ b/README @@ -1,38 +1,12 @@ -This directory contains the implementation of the protocols FTP, HTTP -and SMTP, the URL parsing and composition module and the Concat and Code -auxiliary modules. The files provided are: +This release is work in progress. It has been tested on WinXP, Mac OS X, +SunOS and Linux. The most important change is a major rewrite of the C code +that attempts to make the library extensible. Also, all functions were +moved to the 'socket' namespace. A few changes are expected for the final +version, mostly in order to make the API more uniform. - http.lua -- HTTP protocol implementation +In this version, all Lua code has been built into the binary. For that, you +will need a working versions of luac and bin2c, both available with your +Lua distribution. Check makefile for details. -The module http.lua provides general HTTP client support. The -implementation conforms to the HTTP/1.1 standard, RFC 2068. - - smtp.lua -- SMTP protocol implementation - -The module smtp.lua provides functionality to send e-mail messages to a -SMTP mail server. The implementation conforms to RFC 821. - - ftp.lua -- FTP protocol implementation - -The module ftp.lua provides functions to download and upload files from -and to FTP servers. The implementation conforms to RFC 959. - - url.lua -- URL parsing and composition - -The module url.lua provides routines to split a URL into its components -and to compose a base URL and relative URL into an absolute URL. - - code.lua -- some coding conversion routines - -The code.lua module provides base64, hexa and escaped encoding and -decoding. The module is used for the HTTP Basic Authentication Scheme, -and URL protection, conforming to RFC 2045. - - concat.lua -- fast concatenation library - -The module concat.lua implements, completely in Lua, a set of functions -that greatly improves the performance of repeated concatenations of Lua -strings. The algorithm was inventd by Roberto Ierusalimschy. - -These modules are part of the LuaSocket library and are supported. -Please send any comments to diego@tecgraf.puc-rio.br. +Have fun, +Diego Nehab. diff --git a/makefile.dist b/makefile.dist index 2154eb9..90ef7c2 100644 --- a/makefile.dist +++ b/makefile.dist @@ -2,31 +2,32 @@ # Distribution makefile #-------------------------------------------------------------------------- -DIST = luasocket-1.4 +DIST = luasocket-1.5-work -SRC = ~diego/tec/luasocket +LUA = concat.lua code.lua url.lua http.lua smtp.lua ftp.lua lsselect.lua \ + cl-compat.lua + +TESTS = testclnt.lua testsrvr.lua testcmd.lua codetest.lua \ + urltest.lua concattest.lua + +EXAMPLES = check-links.lua daytimeclnt.lua dict.lua echoclnt.lua \ + echosrvr.lua get.lua listener.lua talker.lua tinyirc.lua tftpclnt.lua dist: - mkdir -p $(DIST)/lua mkdir -p $(DIST)/examples - mkdir -p $(DIST)/html - mkdir -p $(DIST)/test + mkdir -p $(DIST)/tests cp -vf *.c $(DIST) cp -vf *.h $(DIST) + cp -vf $(LUA) $(DIST) cp -vf makefile $(DIST) cp -vf README $(DIST) - cp -vf lua/*.lua $(DIST)/lua - cp -vf lua/README $(DIST)/lua - cp -vf examples/*.lua $(DIST)/examples - cp -vf examples/README $(DIST)/examples - cp -vf html/*.html $(DIST)/html - cp -vf html/*.png $(DIST)/html - cp -vf test/testclnt.lua $(DIST)/test - cp -vf test/testsrvr.lua $(DIST)/test - cp -vf test/testcmd.lua $(DIST)/test - cp -vf test/codetest.lua $(DIST)/test - cp -vf test/concattest.lua $(DIST)/test - cp -vf test/urltest.lua $(DIST)/test - cp -vf test/README $(DIST)/test + cp -vf $(EXAMPLES) $(DIST)/examples + cp -vf README.examples $(DIST)/examples/README + cp -vf $(TESTS) $(DIST)/tests + cp -vf README.tests $(DIST)/tests/README tar -zcvf $(DIST).tar.gz $(DIST) zip -r $(DIST).zip $(DIST) + + +clean: + \rm -rf $(DIST) $(DIST).tar.gz $(DIST).zip diff --git a/src/luasocket.c b/src/luasocket.c index 358b25e..bcc705f 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -56,7 +56,7 @@ LUASOCKET_API int lua_socketlibopen(lua_State *L) buf_open(L); tcps_open(L); udp_open(L); -#if LUASOCKET_DEBUG +#ifdef LUASOCKET_DOFILE lua_dofile(L, "concat.lua"); lua_dofile(L, "code.lua"); lua_dofile(L, "url.lua"); diff --git a/src/select.c b/src/select.c index 0c14ba6..9f56b47 100644 --- a/src/select.c +++ b/src/select.c @@ -41,7 +41,7 @@ void select_open(lua_State *L) { /* push select auxiliar lua function and register * select_lua_select with it as an upvalue */ -#ifdef LUASOCKET_DEBUG +#ifdef LUASOCKET_DOFILE lua_dofile(L, "lsselect.lua"); #else #include "lsselect.loh" From 0f6c8d50a99997ac7829864b1c93362b50f1bbf3 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Sun, 25 May 2003 01:54:13 +0000 Subject: [PATCH 121/483] Porting to LUA 5.0 final --- NEW | 25 ++- etc/tftp.lua | 131 ++++++++++++ samples/daytimeclnt.lua | 3 +- samples/talker.lua | 12 +- src/auxiliar.c | 113 ++++++++++ src/auxiliar.h | 26 +++ src/buffer.c | 275 ++++++++---------------- src/buffer.h | 26 +-- src/ftp.lua | 57 +++-- src/http.lua | 35 ++- src/inet.c | 170 ++++++--------- src/inet.h | 36 ++-- src/io.c | 8 + src/io.h | 34 +++ src/luasocket.c | 68 +++--- src/luasocket.h | 8 +- src/mbox.lua | 20 +- src/smtp.lua | 39 ++-- src/tcp.c | 222 +++++++++++++++++++ src/tcp.h | 20 ++ src/timeout.c | 116 +++++----- src/timeout.h | 32 +-- src/udp.c | 440 +++++++++++++++++--------------------- src/udp.h | 27 +-- src/{unix.c => usocket.c} | 235 +++++++++++--------- src/{unix.h => usocket.h} | 18 +- test/ftptest.lua | 6 +- test/httptest.lua | 15 +- test/smtptest.lua | 10 +- test/testclnt.lua | 430 ++++++++++++++++++------------------- test/testsrvr.lua | 3 +- test/tftptest.lua | 7 +- 32 files changed, 1539 insertions(+), 1128 deletions(-) create mode 100644 etc/tftp.lua create mode 100644 src/auxiliar.c create mode 100644 src/auxiliar.h create mode 100644 src/io.c create mode 100644 src/io.h create mode 100644 src/tcp.c create mode 100644 src/tcp.h rename src/{unix.c => usocket.c} (60%) rename src/{unix.h => usocket.h} (74%) diff --git a/NEW b/NEW index 749641a..879f12c 100644 --- a/NEW +++ b/NEW @@ -1,5 +1,20 @@ -Socket structures are independent -UDPBUFFERSIZE is now internal -Better treatment of closed connections: test!!! -HTTP post now deals with 1xx codes -connect, bind etc only try first address returned by resolver +All functions provided by the library are in the namespace "socket". +Functions such as send/receive/timeout/close etc do not exist in the +namespace. They are now only available as methods of the appropriate +objects. + +Object has been changed to become more uniform. First create an object for +a given domain/family and protocol. Then connect or bind if needed. Then +use IO functions. + +All functions return a non-nil value as first return value if successful. +All functions return nil followed by error message in case of error. +WARNING: The send function was affected. + +Better error messages and parameter checking. + +UDP connected udp sockets can break association with peer by calling +setpeername with address "*". + +socket.sleep and socket.time are now part of the library and are +supported. diff --git a/etc/tftp.lua b/etc/tftp.lua new file mode 100644 index 0000000..a0db68e --- /dev/null +++ b/etc/tftp.lua @@ -0,0 +1,131 @@ +----------------------------------------------------------------------------- +-- TFTP support for the Lua language +-- LuaSocket 1.5 toolkit. +-- Author: Diego Nehab +-- Conforming to: RFC 783, LTN7 +-- RCS ID: $Id$ +----------------------------------------------------------------------------- + +local Public, Private = {}, {} +local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace +socket.tftp = Public -- create tftp sub namespace + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +local char = string.char +local byte = string.byte + +Public.PORT = 69 +Private.OP_RRQ = 1 +Private.OP_WRQ = 2 +Private.OP_DATA = 3 +Private.OP_ACK = 4 +Private.OP_ERROR = 5 +Private.OP_INV = {"RRQ", "WRQ", "DATA", "ACK", "ERROR"} + +----------------------------------------------------------------------------- +-- Packet creation functions +----------------------------------------------------------------------------- +function Private.RRQ(source, mode) + return char(0, Private.OP_RRQ) .. source .. char(0) .. mode .. char(0) +end + +function Private.WRQ(source, mode) + return char(0, Private.OP_RRQ) .. source .. char(0) .. mode .. char(0) +end + +function Private.ACK(block) + local low, high + low = math.mod(block, 256) + high = (block - low)/256 + return char(0, Private.OP_ACK, high, low) +end + +function Private.get_OP(dgram) + local op = byte(dgram, 1)*256 + byte(dgram, 2) + return op +end + +----------------------------------------------------------------------------- +-- Packet analysis functions +----------------------------------------------------------------------------- +function Private.split_DATA(dgram) + local block = byte(dgram, 3)*256 + byte(dgram, 4) + local data = string.sub(dgram, 5) + return block, data +end + +function Private.get_ERROR(dgram) + local code = byte(dgram, 3)*256 + byte(dgram, 4) + local msg + _,_, msg = string.find(dgram, "(.*)\000", 5) + return string.format("error code %d: %s", code, msg) +end + +----------------------------------------------------------------------------- +-- Downloads and returns a file pointed to by url +----------------------------------------------------------------------------- +function Public.get(url) + local parsed = socket.url.parse(url, { + host = "", + port = Public.PORT, + path ="/", + scheme = "tftp" + }) + if parsed.scheme ~= "tftp" then + return nil, string.format("unknown scheme '%s'", parsed.scheme) + end + local retries, dgram, sent, datahost, dataport, code + local cat = socket.concat.create() + local last = 0 + local udp, err = socket.udp() + if not udp then return nil, err end + -- convert from name to ip if needed + parsed.host = socket.toip(parsed.host) + udp:timeout(1) + -- first packet gives data host/port to be used for data transfers + retries = 0 + repeat + sent, err = udp:sendto(Private.RRQ(parsed.path, "octet"), + parsed.host, parsed.port) + if err then return nil, err end + dgram, datahost, dataport = udp:receivefrom() + retries = retries + 1 + until dgram or datahost ~= "timeout" or retries > 5 + if not dgram then return nil, datahost end + -- associate socket with data host/port + udp:setpeername(datahost, dataport) + -- process all data packets + while 1 do + -- decode packet + code = Private.get_OP(dgram) + if code == Private.OP_ERROR then + return nil, Private.get_ERROR(dgram) + end + if code ~= Private.OP_DATA then + return nil, "unhandled opcode " .. code + end + -- get data packet parts + local block, data = Private.split_DATA(dgram) + -- if not repeated, write + if block == last+1 then + cat:addstring(data) + last = block + end + -- last packet brings less than 512 bytes of data + if string.len(data) < 512 then + sent, err = udp:send(Private.ACK(block)) + return cat:getresult() + end + -- get the next packet + retries = 0 + repeat + sent, err = udp:send(Private.ACK(last)) + if err then return err end + dgram, err = udp:receive() + retries = retries + 1 + until dgram or err ~= "timeout" or retries > 5 + if not dgram then return err end + end +end diff --git a/samples/daytimeclnt.lua b/samples/daytimeclnt.lua index 000dfd5..4debc81 100644 --- a/samples/daytimeclnt.lua +++ b/samples/daytimeclnt.lua @@ -7,7 +7,8 @@ end host = socket.toip(host) udp = socket.udp() print("Using host '" ..host.. "' and port " ..port.. "...") -err = udp:sendto("anything", host, port) +udp:setpeername(host, port) +sent, err = udp:send("anything") if err then print(err) exit() end dgram, err = udp:receive() if not dgram then print(err) exit() end diff --git a/samples/talker.lua b/samples/talker.lua index d66cf66..688824f 100644 --- a/samples/talker.lua +++ b/samples/talker.lua @@ -5,18 +5,18 @@ if arg then port = arg[2] or port end print("Attempting connection to host '" ..host.. "' and port " ..port.. "...") -c, e = connect(host, port) +c, e = socket.connect(host, port) if not c then print(e) - exit() + os.exit() end print("Connected! Please type stuff (empty line to stop):") -l = read() +l = io.read() while l and l ~= "" and not e do - e = c:send(l, "\n") + t, e = c:send(l, "\n") if e then print(e) - exit() + os.exit() end - l = read() + l = io.read() end diff --git a/src/auxiliar.c b/src/auxiliar.c new file mode 100644 index 0000000..5e5ba1a --- /dev/null +++ b/src/auxiliar.c @@ -0,0 +1,113 @@ +/*=========================================================================*\ +* Auxiliar routines for class hierarchy manipulation +* +* RCS ID: $Id$ +\*=========================================================================*/ +#include "aux.h" + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static void *aux_getgroupudata(lua_State *L, const char *group, int objidx); + +/*=========================================================================*\ +* Exported functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a new class. A class has methods given by the func array and the +* field 'class' tells the object class. The table 'group' list the class +* groups the object belongs to. +\*-------------------------------------------------------------------------*/ +void aux_newclass(lua_State *L, const char *name, luaL_reg *func) +{ + luaL_newmetatable(L, name); + lua_pushstring(L, "__index"); + lua_newtable(L); + luaL_openlib(L, NULL, func, 0); + lua_pushstring(L, "class"); + lua_pushstring(L, name); + lua_settable(L, -3); + lua_settable(L, -3); + lua_pushstring(L, "group"); + lua_newtable(L); + lua_settable(L, -3); + lua_pop(L, 1); +} + +/*-------------------------------------------------------------------------*\ +* Add group to object list of groups. +\*-------------------------------------------------------------------------*/ +void aux_add2group(lua_State *L, const char *name, const char *group) +{ + luaL_getmetatable(L, name); + lua_pushstring(L, "group"); + lua_gettable(L, -2); + lua_pushstring(L, group); + lua_pushnumber(L, 1); + lua_settable(L, -3); + lua_pop(L, 2); +} + +/*-------------------------------------------------------------------------*\ +* Get a userdata making sure the object belongs to a given class. +\*-------------------------------------------------------------------------*/ +void *aux_checkclass(lua_State *L, const char *name, int objidx) +{ + void *data = luaL_checkudata(L, objidx, name); + if (!data) { + char msg[45]; + sprintf(msg, "%.35s expected", name); + luaL_argerror(L, objidx, msg); + } + return data; +} + +/*-------------------------------------------------------------------------*\ +* Get a userdata making sure the object belongs to a given group. +\*-------------------------------------------------------------------------*/ +void *aux_checkgroup(lua_State *L, const char *group, int objidx) +{ + void *data = aux_getgroupudata(L, group, objidx); + if (!data) { + char msg[45]; + sprintf(msg, "%.35s expected", group); + luaL_argerror(L, objidx, msg); + } + return data; +} + +/*-------------------------------------------------------------------------*\ +* Set object class. +\*-------------------------------------------------------------------------*/ +void aux_setclass(lua_State *L, const char *name, int objidx) +{ + luaL_getmetatable(L, name); + if (objidx < 0) objidx--; + lua_setmetatable(L, objidx); +} + +/*=========================================================================*\ +* Internal functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Get a userdata if object belongs to a given group. +\*-------------------------------------------------------------------------*/ +static void *aux_getgroupudata(lua_State *L, const char *group, int objidx) +{ + if (!lua_getmetatable(L, objidx)) return NULL; + lua_pushstring(L, "group"); + lua_gettable(L, -2); + if (lua_isnil(L, -1)) { + lua_pop(L, 2); + return NULL; + } + lua_pushstring(L, group); + lua_gettable(L, -2); + if (lua_isnil(L, -1)) { + lua_pop(L, 3); + return NULL; + } + lua_pop(L, 3); + return lua_touserdata(L, objidx); +} + diff --git a/src/auxiliar.h b/src/auxiliar.h new file mode 100644 index 0000000..2681a84 --- /dev/null +++ b/src/auxiliar.h @@ -0,0 +1,26 @@ +/*=========================================================================*\ +* Auxiliar routines for class hierarchy manipulation +* +* RCS ID: $Id$ +\*=========================================================================*/ +#ifndef AUX_H +#define AUX_H + +#include +#include + +void aux_newclass(lua_State *L, const char *name, luaL_reg *func); +void aux_add2group(lua_State *L, const char *name, const char *group); +void *aux_checkclass(lua_State *L, const char *name, int objidx); +void *aux_checkgroup(lua_State *L, const char *group, int objidx); +void aux_setclass(lua_State *L, const char *name, int objidx); + +/* 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 + +#endif diff --git a/src/buffer.c b/src/buffer.c index 73df8b3..c5ef66c 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -1,28 +1,24 @@ /*=========================================================================*\ * Buffered input/output routines -* Lua methods: -* send: unbuffered send using C base_send -* receive: buffered read using C base_receive * * RCS ID: $Id$ \*=========================================================================*/ #include #include -#include "lsbuf.h" +#include "error.h" +#include "aux.h" +#include "buf.h" /*=========================================================================*\ -* Internal function prototypes. +* Internal function prototypes \*=========================================================================*/ -static int sendraw(lua_State *L, p_buf buf, cchar *data, size_t len, - size_t *done); static int recvraw(lua_State *L, p_buf buf, size_t wanted); -static int recvdosline(lua_State *L, p_buf buf); -static int recvunixline(lua_State *L, p_buf buf); +static int recvline(lua_State *L, p_buf buf); static int recvall(lua_State *L, p_buf buf); - -static int buf_contents(lua_State *L, p_buf buf, cchar **data, size_t *len); -static void buf_skip(lua_State *L, p_buf buf, size_t len); +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); /*=========================================================================*\ * Exported functions @@ -37,98 +33,69 @@ void buf_open(lua_State *L) /*-------------------------------------------------------------------------*\ * Initializes C structure -* Input -* buf: buffer structure to initialize -* base: socket object to associate with buffer structure \*-------------------------------------------------------------------------*/ -void buf_init(lua_State *L, p_buf buf, p_base base) +void buf_init(p_buf buf, p_io io, p_tm tm) { - (void) L; - buf->buf_first = buf->buf_last = 0; - buf->buf_base = base; + buf->first = buf->last = 0; + buf->io = io; + buf->tm = tm; } /*-------------------------------------------------------------------------*\ * Send data through buffered object -* Input -* buf: buffer structure to be used -* Lua Input: self, a_1 [, a_2, a_3 ... a_n] -* self: socket object -* a_i: strings to be sent. -* Lua Returns -* On success: nil, followed by the total number of bytes sent -* On error: error message \*-------------------------------------------------------------------------*/ -int buf_send(lua_State *L, p_buf buf) +int buf_meth_send(lua_State *L, p_buf buf) { int top = lua_gettop(L); size_t total = 0; - int err = PRIV_DONE; - int arg; - p_base base = buf->buf_base; - tm_markstart(&base->base_tm); + int arg, err = IO_DONE; + p_tm tm = buf->tm; + tm_markstart(tm); for (arg = 2; arg <= top; arg++) { /* first arg is socket object */ - size_t done, len; - cchar *data = luaL_optlstring(L, arg, NULL, &len); - if (!data || err != PRIV_DONE) break; - err = sendraw(L, buf, data, len, &done); - total += done; + size_t sent, count; + const char *data = luaL_optlstring(L, arg, NULL, &count); + if (!data || err != IO_DONE) break; + err = sendraw(buf, data, count, &sent); + total += sent; } - priv_pusherror(L, err); lua_pushnumber(L, total); + error_push(L, err); #ifdef LUASOCKET_DEBUG /* push time elapsed during operation as the last return value */ - lua_pushnumber(L, tm_getelapsed(&base->base_tm)/1000.0); + lua_pushnumber(L, (tm_gettime() - tm_getstart(tm))/1000.0); #endif return lua_gettop(L) - top; } /*-------------------------------------------------------------------------*\ * Receive data from a buffered object -* Input -* buf: buffer structure to be used -* Lua Input: self [pat_1, pat_2 ... pat_n] -* self: socket object -* pat_i: may be one of the following -* "*l": reads a text line, defined as a string of caracters terminates -* by a LF character, preceded or not by a CR character. This is -* the default pattern -* "*lu": reads a text line, terminanted by a CR character only. (Unix mode) -* "*a": reads until connection closed -* number: reads 'number' characters from the socket object -* Lua Returns -* On success: one string for each pattern -* On error: all strings for which there was no error, followed by one -* nil value for the remaining strings, followed by an error code \*-------------------------------------------------------------------------*/ -int buf_receive(lua_State *L, p_buf buf) +int buf_meth_receive(lua_State *L, p_buf buf) { int top = lua_gettop(L); - int arg, err = PRIV_DONE; - p_base base = buf->buf_base; - tm_markstart(&base->base_tm); + int arg, err = IO_DONE; + p_tm tm = buf->tm; + tm_markstart(tm); /* push default pattern if need be */ if (top < 2) { lua_pushstring(L, "*l"); top++; } - /* make sure we have enough stack space */ + /* make sure we have enough stack space for all returns */ luaL_checkstack(L, top+LUA_MINSTACK, "too many arguments"); /* receive all patterns */ - for (arg = 2; arg <= top && err == PRIV_DONE; arg++) { + for (arg = 2; arg <= top && err == IO_DONE; arg++) { if (!lua_isnumber(L, arg)) { - static cchar *patternnames[] = {"*l", "*lu", "*a", "*w", NULL}; - cchar *pattern = luaL_optstring(L, arg, NULL); + static const char *patternnames[] = {"*l", "*a", NULL}; + const char *pattern = lua_isnil(L, arg) ? + "*l" : luaL_checkstring(L, arg); /* get next pattern */ switch (luaL_findstring(pattern, patternnames)) { - case 0: /* DOS line pattern */ - err = recvdosline(L, buf); break; - case 1: /* Unix line pattern */ - err = recvunixline(L, buf); break; - case 2: /* Until closed pattern */ - err = recvall(L, buf); break; - case 3: /* Word pattern */ - luaL_argcheck(L, 0, arg, "word patterns are deprecated"); + case 0: /* line pattern */ + err = recvline(L, buf); break; + case 1: /* until closed pattern */ + err = recvall(L, buf); + if (err == IO_CLOSED) err = IO_DONE; break; default: /* else it is an error */ luaL_argcheck(L, 0, arg, "invalid receive pattern"); @@ -140,25 +107,20 @@ int buf_receive(lua_State *L, p_buf buf) /* push nil for each pattern after an error */ for ( ; arg <= top; arg++) lua_pushnil(L); /* last return is an error code */ - priv_pusherror(L, err); + error_push(L, err); #ifdef LUASOCKET_DEBUG /* push time elapsed during operation as the last return value */ - lua_pushnumber(L, tm_getelapsed(&base->base_tm)/1000.0); + lua_pushnumber(L, (tm_gettime() - tm_getstart(tm))/1000.0); #endif return lua_gettop(L) - top; } /*-------------------------------------------------------------------------*\ * Determines if there is any data in the read buffer -* Input -* buf: buffer structure to be used -* Returns -* 1 if empty, 0 if there is data \*-------------------------------------------------------------------------*/ -int buf_isempty(lua_State *L, p_buf buf) +int buf_isempty(p_buf buf) { - (void) L; - return buf->buf_first >= buf->buf_last; + return buf->first >= buf->last; } /*=========================================================================*\ @@ -166,24 +128,16 @@ int buf_isempty(lua_State *L, p_buf buf) \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Sends a raw block of data through a buffered object. -* Input -* buf: buffer structure to be used -* data: data to be sent -* len: number of bytes to send -* Output -* sent: number of bytes sent -* Returns -* operation error code. \*-------------------------------------------------------------------------*/ -static int sendraw(lua_State *L, p_buf buf, cchar *data, size_t len, - size_t *sent) +static int sendraw(p_buf buf, const char *data, size_t count, size_t *sent) { - p_base base = buf->buf_base; + p_io io = buf->io; + p_tm tm = buf->tm; size_t total = 0; - int err = PRIV_DONE; - while (total < len && err == PRIV_DONE) { + int err = IO_DONE; + while (total < count && err == IO_DONE) { size_t done; - err = base->base_send(L, base, data + total, len - total, &done); + err = io->send(io->ctx, data+total, count-total, &done, tm_get(tm)); total += done; } *sent = total; @@ -192,25 +146,21 @@ static int sendraw(lua_State *L, p_buf buf, cchar *data, size_t len, /*-------------------------------------------------------------------------*\ * Reads a raw block of data from a buffered object. -* Input -* buf: buffer structure -* wanted: number of bytes to be read -* Returns -* operation error code. \*-------------------------------------------------------------------------*/ -static int recvraw(lua_State *L, p_buf buf, size_t wanted) +static +int recvraw(lua_State *L, p_buf buf, size_t wanted) { - int err = PRIV_DONE; + int err = IO_DONE; size_t total = 0; luaL_Buffer b; luaL_buffinit(L, &b); - while (total < wanted && err == PRIV_DONE) { - size_t len; cchar *data; - err = buf_contents(L, buf, &data, &len); - len = MIN(len, wanted - total); - luaL_addlstring(&b, data, len); - buf_skip(L, buf, len); - total += len; + while (total < wanted && err == IO_DONE) { + size_t count; const char *data; + err = buf_get(buf, &data, &count); + count = MIN(count, wanted - total); + luaL_addlstring(&b, data, count); + buf_skip(buf, count); + total += count; } luaL_pushresult(&b); return err; @@ -218,21 +168,18 @@ static int recvraw(lua_State *L, p_buf buf, size_t wanted) /*-------------------------------------------------------------------------*\ * Reads everything until the connection is closed -* Input -* buf: buffer structure -* Result -* operation error code. \*-------------------------------------------------------------------------*/ -static int recvall(lua_State *L, p_buf buf) +static +int recvall(lua_State *L, p_buf buf) { - int err = PRIV_DONE; + int err = IO_DONE; luaL_Buffer b; luaL_buffinit(L, &b); - while (err == PRIV_DONE) { - cchar *data; size_t len; - err = buf_contents(L, buf, &data, &len); - luaL_addlstring(&b, data, len); - buf_skip(L, buf, len); + while (err == IO_DONE) { + const char *data; size_t count; + err = buf_get(buf, &data, &count); + luaL_addlstring(&b, data, count); + buf_skip(buf, count); } luaL_pushresult(&b); return err; @@ -241,61 +188,27 @@ static int recvall(lua_State *L, p_buf buf) /*-------------------------------------------------------------------------*\ * Reads a line terminated by a CR LF pair or just by a LF. The CR and LF * are not returned by the function and are discarded from the buffer. -* Input -* buf: buffer structure -* Result -* operation error code. PRIV_DONE, PRIV_TIMEOUT or PRIV_CLOSED \*-------------------------------------------------------------------------*/ -static int recvdosline(lua_State *L, p_buf buf) +static +int recvline(lua_State *L, p_buf buf) { int err = 0; luaL_Buffer b; luaL_buffinit(L, &b); - while (err == PRIV_DONE) { - size_t len, pos; cchar *data; - err = buf_contents(L, buf, &data, &len); + while (err == IO_DONE) { + size_t count, pos; const char *data; + err = buf_get(buf, &data, &count); pos = 0; - while (pos < len && data[pos] != '\n') { + while (pos < count && data[pos] != '\n') { /* we ignore all \r's */ if (data[pos] != '\r') luaL_putchar(&b, data[pos]); pos++; } - if (pos < len) { /* found '\n' */ - buf_skip(L, buf, pos+1); /* skip '\n' too */ + if (pos < count) { /* found '\n' */ + buf_skip(buf, pos+1); /* skip '\n' too */ break; /* we are done */ } else /* reached the end of the buffer */ - buf_skip(L, buf, pos); - } - luaL_pushresult(&b); - return err; -} - -/*-------------------------------------------------------------------------*\ -* Reads a line terminated by a LF character, which is not returned by -* the function, and is skipped in the buffer. -* Input -* buf: buffer structure -* Returns -* operation error code. PRIV_DONE, PRIV_TIMEOUT or PRIV_CLOSED -\*-------------------------------------------------------------------------*/ -static int recvunixline(lua_State *L, p_buf buf) -{ - int err = PRIV_DONE; - luaL_Buffer b; - luaL_buffinit(L, &b); - while (err == 0) { - size_t pos, len; cchar *data; - err = buf_contents(L, buf, &data, &len); - pos = 0; - while (pos < len && data[pos] != '\n') { - luaL_putchar(&b, data[pos]); - pos++; - } - if (pos < len) { /* found '\n' */ - buf_skip(L, buf, pos+1); /* skip '\n' too */ - break; /* we are done */ - } else /* reached the end of the buffer */ - buf_skip(L, buf, pos); + buf_skip(buf, pos); } luaL_pushresult(&b); return err; @@ -303,38 +216,32 @@ static int recvunixline(lua_State *L, p_buf buf) /*-------------------------------------------------------------------------*\ * Skips a given number of bytes in read buffer -* Input -* buf: buffer structure -* len: number of bytes to skip \*-------------------------------------------------------------------------*/ -static void buf_skip(lua_State *L, p_buf buf, size_t len) +static +void buf_skip(p_buf buf, size_t count) { - buf->buf_first += len; - if (buf_isempty(L, buf)) buf->buf_first = buf->buf_last = 0; + buf->first += count; + if (buf_isempty(buf)) + buf->first = buf->last = 0; } /*-------------------------------------------------------------------------*\ * Return any data available in buffer, or get more data from transport layer * if buffer is empty. -* Input -* buf: buffer structure -* Output -* data: pointer to buffer start -* len: buffer buffer length -* Returns -* PRIV_DONE, PRIV_CLOSED, PRIV_TIMEOUT ... \*-------------------------------------------------------------------------*/ -static int buf_contents(lua_State *L, p_buf buf, cchar **data, size_t *len) +static +int buf_get(p_buf buf, const char **data, size_t *count) { - int err = PRIV_DONE; - p_base base = buf->buf_base; - if (buf_isempty(L, buf)) { - size_t done; - err = base->base_receive(L, base, buf->buf_data, BUF_SIZE, &done); - buf->buf_first = 0; - buf->buf_last = done; + int err = IO_DONE; + p_io io = buf->io; + p_tm tm = buf->tm; + if (buf_isempty(buf)) { + size_t got; + err = io->recv(io->ctx, buf->data, BUF_SIZE, &got, tm_get(tm)); + buf->first = 0; + buf->last = got; } - *len = buf->buf_last - buf->buf_first; - *data = buf->buf_data + buf->buf_first; + *count = buf->last - buf->first; + *data = buf->data + buf->first; return err; } diff --git a/src/buffer.h b/src/buffer.h index 4943e3b..3ffc145 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -3,11 +3,12 @@ * * RCS ID: $Id$ \*=========================================================================*/ -#ifndef BUF_H_ -#define BUF_H_ +#ifndef BUF_H +#define BUF_H #include -#include "lsbase.h" +#include "io.h" +#include "tm.h" /* buffer size in bytes */ #define BUF_SIZE 8192 @@ -15,10 +16,11 @@ /*-------------------------------------------------------------------------*\ * Buffer control structure \*-------------------------------------------------------------------------*/ -typedef struct t_buf_tag { - size_t buf_first, buf_last; - char buf_data[BUF_SIZE]; - p_base buf_base; +typedef struct t_buf_ { + p_io io; /* IO driver used for this buffer */ + p_tm tm; /* timeout management for this buffer */ + size_t first, last; /* index of first and last bytes of stored data */ + char data[BUF_SIZE]; /* storage space for buffer data */ } t_buf; typedef t_buf *p_buf; @@ -26,9 +28,9 @@ typedef t_buf *p_buf; * Exported functions \*-------------------------------------------------------------------------*/ void buf_open(lua_State *L); -void buf_init(lua_State *L, p_buf buf, p_base base); -int buf_send(lua_State *L, p_buf buf); -int buf_receive(lua_State *L, p_buf buf); -int buf_isempty(lua_State *L, p_buf buf); +void buf_init(p_buf buf, p_io io, p_tm tm); +int buf_meth_send(lua_State *L, p_buf buf); +int buf_meth_receive(lua_State *L, p_buf buf); +int buf_isempty(p_buf buf); -#endif /* BUF_H_ */ +#endif /* BUF_H */ diff --git a/src/ftp.lua b/src/ftp.lua index 4017eb5..c48f2c7 100644 --- a/src/ftp.lua +++ b/src/ftp.lua @@ -7,7 +7,8 @@ ----------------------------------------------------------------------------- local Public, Private = {}, {} -socket.ftp = Public +local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace +socket.ftp = Public -- create ftp sub namespace ----------------------------------------------------------------------------- -- Program constants @@ -22,6 +23,33 @@ Public.EMAIL = "anonymous@anonymous.org" -- block size used in transfers Public.BLOCKSIZE = 8192 +----------------------------------------------------------------------------- +-- Tries to get a pattern from the server and closes socket on error +-- sock: socket connected to the server +-- pattern: pattern to receive +-- Returns +-- received pattern on success +-- nil followed by error message on error +----------------------------------------------------------------------------- +function Private.try_receive(sock, pattern) + local data, err = sock:receive(pattern) + if not data then sock:close() end + return data, err +end + +----------------------------------------------------------------------------- +-- Tries to send data to the server and closes socket on error +-- sock: socket connected to the server +-- data: data to send +-- Returns +-- err: error message if any, nil if successfull +----------------------------------------------------------------------------- +function Private.try_send(sock, data) + local sent, err = sock:send(data) + if not sent then sock:close() end + return err +end + ----------------------------------------------------------------------------- -- Tries to send DOS mode lines. Closes socket on error. -- Input @@ -31,24 +59,7 @@ Public.BLOCKSIZE = 8192 -- err: message in case of error, nil if successfull ----------------------------------------------------------------------------- function Private.try_sendline(sock, line) - local err = sock:send(line .. "\r\n") - if err then sock:close() end - return err -end - ------------------------------------------------------------------------------ --- Tries to get a pattern from the server and closes socket on error --- sock: socket connected to the server --- ...: pattern to receive --- Returns --- ...: received pattern --- err: error message if any ------------------------------------------------------------------------------ -function Private.try_receive(...) - local sock = arg[1] - local data, err = sock.receive(unpack(arg)) - if err then sock:close() end - return data, err + return Private.try_send(sock, line .. "\r\n") end ----------------------------------------------------------------------------- @@ -307,20 +318,20 @@ end -- nil if successfull, or an error message in case of error ----------------------------------------------------------------------------- function Private.send_indirect(data, send_cb, chunk, size) - local sent, err - sent = 0 + local total, sent, err + total = 0 while 1 do if type(chunk) ~= "string" or type(size) ~= "number" then data:close() if not chunk and type(size) == "string" then return size else return "invalid callback return" end end - err = data:send(chunk) + sent, err = data:send(chunk) if err then data:close() return err end - sent = sent + string.len(chunk) + total = total + sent if sent >= size then break end chunk, size = send_cb() end diff --git a/src/http.lua b/src/http.lua index 59645ee..d531a2f 100644 --- a/src/http.lua +++ b/src/http.lua @@ -7,7 +7,8 @@ ----------------------------------------------------------------------------- local Public, Private = {}, {} -socket.http = Public +local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace +socket.http = Public -- create http sub namespace ----------------------------------------------------------------------------- -- Program constants @@ -24,19 +25,15 @@ Public.BLOCKSIZE = 8192 ----------------------------------------------------------------------------- -- Tries to get a pattern from the server and closes socket on error -- sock: socket connected to the server --- ...: pattern to receive +-- pattern: pattern to receive -- Returns --- ...: received pattern --- err: error message if any +-- received pattern on success +-- nil followed by error message on error ----------------------------------------------------------------------------- -function Private.try_receive(...) - local sock = arg[1] - local data, err = sock.receive(unpack(arg)) - if err then - sock:close() - return nil, err - end - return data +function Private.try_receive(sock, pattern) + local data, err = sock:receive(pattern) + if not data then sock:close() end + return data, err end ----------------------------------------------------------------------------- @@ -47,8 +44,8 @@ end -- err: error message if any, nil if successfull ----------------------------------------------------------------------------- function Private.try_send(sock, data) - local err = sock:send(data) - if err then sock:close() end + local sent, err = sock:send(data) + if not sent then sock:close() end return err end @@ -285,21 +282,21 @@ end -- nil if successfull, or an error message in case of error ----------------------------------------------------------------------------- function Private.send_indirect(data, send_cb, chunk, size) - local sent, err - sent = 0 + local total, sent, err + total = 0 while 1 do if type(chunk) ~= "string" or type(size) ~= "number" then data:close() if not chunk and type(size) == "string" then return size else return "invalid callback return" end end - err = data:send(chunk) + sent, err = data:send(chunk) if err then data:close() return err end - sent = sent + string.len(chunk) - if sent >= size then break end + total = total + sent + if total >= size then break end chunk, size = send_cb() end end diff --git a/src/inet.c b/src/inet.c index 341c60e..f20762f 100644 --- a/src/inet.c +++ b/src/inet.c @@ -1,12 +1,5 @@ /*=========================================================================*\ -* Internet domain class: inherits from the Socket class, and implement -* a few methods shared by all internet related objects -* Lua methods: -* getpeername: gets socket peer ip address and port -* getsockname: gets local socket ip address and port -* Global Lua fuctions: -* toip: gets resolver info on host name -* tohostname: gets resolver info on dotted-quad +* Internet domain functions * * RCS ID: $Id$ \*=========================================================================*/ @@ -15,23 +8,27 @@ #include #include -#include "lsinet.h" -#include "lssock.h" -#include "lscompat.h" +#include "luasocket.h" +#include "inet.h" /*=========================================================================*\ * Internal function prototypes. \*=========================================================================*/ -static int inet_lua_toip(lua_State *L); -static int inet_lua_tohostname(lua_State *L); -static int inet_lua_getpeername(lua_State *L); -static int inet_lua_getsockname(lua_State *L); +static int inet_global_toip(lua_State *L); +static int inet_global_tohostname(lua_State *L); + static void inet_pushresolved(lua_State *L, struct hostent *hp); -#ifdef COMPAT_INETATON -static int inet_aton(cchar *cp, struct in_addr *inp); +#ifdef INET_ATON +static int inet_aton(const char *cp, struct in_addr *inp); #endif +static luaL_reg func[] = { + { "toip", inet_global_toip }, + { "tohostname", inet_global_tohostname }, + { NULL, NULL} +}; + /*=========================================================================*\ * Exported functions \*=========================================================================*/ @@ -40,39 +37,7 @@ static int inet_aton(cchar *cp, struct in_addr *inp); \*-------------------------------------------------------------------------*/ void inet_open(lua_State *L) { - lua_pushcfunction(L, inet_lua_toip); - priv_newglobal(L, "toip"); - lua_pushcfunction(L, inet_lua_tohostname); - priv_newglobal(L, "tohostname"); - priv_newglobalmethod(L, "getsockname"); - priv_newglobalmethod(L, "getpeername"); -} - -/*-------------------------------------------------------------------------*\ -* Hook lua methods to methods table. -* Input -* lsclass: class name -\*-------------------------------------------------------------------------*/ -void inet_inherit(lua_State *L, cchar *lsclass) -{ - unsigned int i; - static struct luaL_reg funcs[] = { - {"getsockname", inet_lua_getsockname}, - {"getpeername", inet_lua_getpeername}, - }; - sock_inherit(L, lsclass); - for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { - lua_pushcfunction(L, funcs[i].func); - priv_setmethod(L, lsclass, funcs[i].name); - } -} - -/*-------------------------------------------------------------------------*\ -* Constructs the object -\*-------------------------------------------------------------------------*/ -void inet_construct(lua_State *L, p_inet inet) -{ - sock_construct(L, (p_sock) inet); + luaL_openlib(L, LUASOCKET_LIBNAME, func, 0); } /*=========================================================================*\ @@ -87,17 +52,18 @@ void inet_construct(lua_State *L, p_inet inet) * On success: first IP address followed by a resolved table * On error: nil, followed by an error message \*-------------------------------------------------------------------------*/ -static int inet_lua_toip(lua_State *L) +static int inet_global_toip(lua_State *L) { - cchar *address = luaL_checkstring(L, 1); + const char *address = luaL_checkstring(L, 1); struct in_addr addr; struct hostent *hp; if (inet_aton(address, &addr)) hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET); - else hp = gethostbyname(address); + else + hp = gethostbyname(address); if (!hp) { lua_pushnil(L); - lua_pushstring(L, compat_hoststrerror()); + lua_pushstring(L, sock_hoststrerror()); return 2; } addr = *((struct in_addr *) hp->h_addr); @@ -115,17 +81,18 @@ static int inet_lua_toip(lua_State *L) * On success: canonic name followed by a resolved table * On error: nil, followed by an error message \*-------------------------------------------------------------------------*/ -static int inet_lua_tohostname(lua_State *L) +static int inet_global_tohostname(lua_State *L) { - cchar *address = luaL_checkstring(L, 1); + const char *address = luaL_checkstring(L, 1); struct in_addr addr; struct hostent *hp; if (inet_aton(address, &addr)) hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET); - else hp = gethostbyname(address); + else + hp = gethostbyname(address); if (!hp) { lua_pushnil(L); - lua_pushstring(L, compat_hoststrerror()); + lua_pushstring(L, sock_hoststrerror()); return 2; } lua_pushstring(L, hp->h_name); @@ -138,18 +105,17 @@ static int inet_lua_tohostname(lua_State *L) \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Retrieves socket peer name -* Lua Input: sock +* Input: * sock: socket * Lua Returns * On success: ip address and port of peer * On error: nil \*-------------------------------------------------------------------------*/ -static int inet_lua_getpeername(lua_State *L) +int inet_meth_getpeername(lua_State *L, p_sock ps) { - p_sock sock = (p_sock) lua_touserdata(L, 1); struct sockaddr_in peer; size_t peer_len = sizeof(peer); - if (getpeername(sock->fd, (SA *) &peer, &peer_len) < 0) { + if (getpeername(*ps, (SA *) &peer, &peer_len) < 0) { lua_pushnil(L); return 1; } @@ -160,18 +126,17 @@ static int inet_lua_getpeername(lua_State *L) /*-------------------------------------------------------------------------*\ * Retrieves socket local name -* Lua Input: sock +* Input: * sock: socket * Lua Returns * On success: local ip address and port * On error: nil \*-------------------------------------------------------------------------*/ -static int inet_lua_getsockname(lua_State *L) +int inet_meth_getsockname(lua_State *L, p_sock ps) { - p_sock sock = (p_sock) lua_touserdata(L, 1); struct sockaddr_in local; size_t local_len = sizeof(local); - if (getsockname(sock->fd, (SA *) &local, &local_len) < 0) { + if (getsockname(*ps, (SA *) &local, &local_len) < 0) { lua_pushnil(L); return 1; } @@ -222,47 +187,53 @@ static void inet_pushresolved(lua_State *L, struct hostent *hp) } /*-------------------------------------------------------------------------*\ -* Tries to create a TCP socket and connect to remote address (address, port) +* Tries to connect to remote address (address, port) * Input -* client: socket structure to be used +* ps: pointer to socket * address: host name or ip address * port: port number to bind to * Returns * NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ -cchar *inet_tryconnect(p_inet inet, cchar *address, ushort port) +const char *inet_tryconnect(p_sock ps, const char *address, ushort port) { struct sockaddr_in remote; memset(&remote, 0, sizeof(remote)); remote.sin_family = AF_INET; remote.sin_port = htons(port); - if (!strlen(address) || !inet_aton(address, &remote.sin_addr)) { - struct hostent *hp = gethostbyname(address); - struct in_addr **addr; - if (!hp) return compat_hoststrerror(); - addr = (struct in_addr **) hp->h_addr_list; - memcpy(&remote.sin_addr, *addr, sizeof(struct in_addr)); - } - compat_setblocking(inet->fd); - if (compat_connect(inet->fd, (SA *) &remote, sizeof(remote)) < 0) { - const char *err = compat_connectstrerror(); - compat_close(inet->fd); - inet->fd = COMPAT_INVALIDFD; + if (strcmp(address, "*")) { + if (!strlen(address) || !inet_aton(address, &remote.sin_addr)) { + struct hostent *hp = gethostbyname(address); + struct in_addr **addr; + remote.sin_family = AF_INET; + if (!hp) return sock_hoststrerror(); + addr = (struct in_addr **) hp->h_addr_list; + memcpy(&remote.sin_addr, *addr, sizeof(struct in_addr)); + } + } else remote.sin_family = AF_UNSPEC; + sock_setblocking(ps); + const char *err = sock_connect(ps, (SA *) &remote, sizeof(remote)); + if (err) { + sock_destroy(ps); + *ps = SOCK_INVALID; return err; - } - compat_setnonblocking(inet->fd); - return NULL; + } else { + sock_setnonblocking(ps); + return NULL; + } } /*-------------------------------------------------------------------------*\ -* Tries to create a TCP socket and bind it to (address, port) +* Tries to bind socket to (address, port) * Input +* sock: pointer to socket * address: host name or ip address * port: port number to bind to * Returns * NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ -cchar *inet_trybind(p_inet inet, cchar *address, ushort port) +const char *inet_trybind(p_sock ps, const char *address, ushort port, + int backlog) { struct sockaddr_in local; memset(&local, 0, sizeof(local)); @@ -274,34 +245,33 @@ cchar *inet_trybind(p_inet inet, cchar *address, ushort port) (!strlen(address) || !inet_aton(address, &local.sin_addr))) { struct hostent *hp = gethostbyname(address); struct in_addr **addr; - if (!hp) return compat_hoststrerror(); + if (!hp) return sock_hoststrerror(); addr = (struct in_addr **) hp->h_addr_list; memcpy(&local.sin_addr, *addr, sizeof(struct in_addr)); } - compat_setblocking(inet->fd); - if (compat_bind(inet->fd, (SA *) &local, sizeof(local)) < 0) { - const char *err = compat_bindstrerror(); - compat_close(inet->fd); - inet->fd = COMPAT_INVALIDFD; + sock_setblocking(ps); + const char *err = sock_bind(ps, (SA *) &local, sizeof(local)); + if (err) { + sock_destroy(ps); + *ps = SOCK_INVALID; return err; + } else { + sock_setnonblocking(ps); + if (backlog > 0) sock_listen(ps, backlog); + return NULL; } - compat_setnonblocking(inet->fd); - return NULL; } /*-------------------------------------------------------------------------*\ * Tries to create a new inet socket * Input -* udp: udp structure +* sock: pointer to socket * Returns * NULL if successfull, error message on error \*-------------------------------------------------------------------------*/ -cchar *inet_trysocket(p_inet inet, int type) +const char *inet_trycreate(p_sock ps, int type) { - if (inet->fd != COMPAT_INVALIDFD) compat_close(inet->fd); - inet->fd = compat_socket(AF_INET, type, 0); - if (inet->fd == COMPAT_INVALIDFD) return compat_socketstrerror(); - else return NULL; + return sock_create(ps, AF_INET, type, 0); } /*-------------------------------------------------------------------------*\ diff --git a/src/inet.h b/src/inet.h index 93fcedf..bcefc5b 100644 --- a/src/inet.h +++ b/src/inet.h @@ -1,38 +1,26 @@ /*=========================================================================*\ -* Internet domain class: inherits from the Socket class, and implement -* a few methods shared by all internet related objects +* Internet domain functions * * RCS ID: $Id$ \*=========================================================================*/ -#ifndef INET_H_ -#define INET_H_ +#ifndef INET_H +#define INET_H #include -#include "lssock.h" - -/* class name */ -#define INET_CLASS "luasocket(inet)" - -/*-------------------------------------------------------------------------*\ -* Socket fields -\*-------------------------------------------------------------------------*/ -#define INET_FIELDS SOCK_FIELDS - -/*-------------------------------------------------------------------------*\ -* Socket structure -\*-------------------------------------------------------------------------*/ -typedef t_sock t_inet; -typedef t_inet *p_inet; +#include "sock.h" /*-------------------------------------------------------------------------*\ * Exported functions \*-------------------------------------------------------------------------*/ void inet_open(lua_State *L); -void inet_construct(lua_State *L, p_inet inet); -void inet_inherit(lua_State *L, cchar *lsclass); -cchar *inet_tryconnect(p_sock sock, cchar *address, ushort); -cchar *inet_trybind(p_sock sock, cchar *address, ushort); -cchar *inet_trysocket(p_inet inet, int type); +const char *inet_tryconnect(p_sock ps, const char *address, + unsigned short port); +const char *inet_trybind(p_sock ps, const char *address, + unsigned short port, int backlog); +const char *inet_trycreate(p_sock ps, int type); + +int inet_meth_getpeername(lua_State *L, p_sock ps); +int inet_meth_getsockname(lua_State *L, p_sock ps); #endif /* INET_H_ */ diff --git a/src/io.c b/src/io.c new file mode 100644 index 0000000..902124a --- /dev/null +++ b/src/io.c @@ -0,0 +1,8 @@ +#include "io.h" + +void io_init(p_io io, p_send send, p_recv recv, void *ctx) +{ + io->send = send; + io->recv = recv; + io->ctx = ctx; +} diff --git a/src/io.h b/src/io.h new file mode 100644 index 0000000..b5b7f1d --- /dev/null +++ b/src/io.h @@ -0,0 +1,34 @@ +#ifndef IO_H +#define IO_H + +#include "error.h" + +/* interface to send function */ +typedef int (*p_send) ( + void *ctx, /* context needed by send */ + const char *data, /* pointer to buffer with data to send */ + size_t count, /* number of bytes to send from buffer */ + size_t *sent, /* number of bytes sent uppon return */ + int timeout /* number of miliseconds left for transmission */ +); + +/* interface to recv function */ +typedef int (*p_recv) ( + void *ctx, /* context needed by recv */ + char *data, /* pointer to buffer where data will be writen */ + size_t count, /* number of bytes to receive into buffer */ + size_t *got, /* number of bytes received uppon return */ + int timeout /* number of miliseconds left for transmission */ +); + +/* IO driver definition */ +typedef struct t_io_ { + void *ctx; /* context needed by send/recv */ + p_send send; /* send function pointer */ + p_recv recv; /* receive function pointer */ +} t_io; +typedef t_io *p_io; + +void io_init(p_io io, p_send send, p_recv recv, void *ctx); + +#endif /* IO_H */ diff --git a/src/luasocket.c b/src/luasocket.c index bcc705f..53f8c21 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -23,18 +23,13 @@ * LuaSocket includes \*=========================================================================*/ #include "luasocket.h" -#include "lspriv.h" -#include "lsselect.h" -#include "lscompat.h" -#include "lsbase.h" -#include "lstm.h" -#include "lsbuf.h" -#include "lssock.h" -#include "lsinet.h" -#include "lstcpc.h" -#include "lstcps.h" -#include "lstcps.h" -#include "lsudp.h" + +#include "tm.h" +#include "buf.h" +#include "sock.h" +#include "inet.h" +#include "tcp.h" +#include "udp.h" /*=========================================================================*\ * Exported functions @@ -42,34 +37,29 @@ /*-------------------------------------------------------------------------*\ * Initializes all library modules. \*-------------------------------------------------------------------------*/ -LUASOCKET_API int lua_socketlibopen(lua_State *L) +LUASOCKET_API int luaopen_socketlib(lua_State *L) { - compat_open(L); - priv_open(L); - select_open(L); - base_open(L); - tm_open(L); - fd_open(L); - sock_open(L); - inet_open(L); - tcpc_open(L); - buf_open(L); - tcps_open(L); - udp_open(L); -#ifdef LUASOCKET_DOFILE - lua_dofile(L, "concat.lua"); - lua_dofile(L, "code.lua"); - lua_dofile(L, "url.lua"); - lua_dofile(L, "http.lua"); - lua_dofile(L, "smtp.lua"); - lua_dofile(L, "ftp.lua"); -#else -#include "concat.loh" -#include "code.loh" -#include "url.loh" -#include "http.loh" -#include "smtp.loh" -#include "ftp.loh" + /* create namespace table */ + lua_pushstring(L, LUASOCKET_LIBNAME); + lua_newtable(L); +#ifdef LUASOCKET_DEBUG + lua_pushstring(L, "debug"); + lua_pushnumber(L, 1); + lua_settable(L, -3); #endif + lua_settable(L, LUA_GLOBALSINDEX); + /* make sure modules know what is our namespace */ + lua_pushstring(L, "LUASOCKET_LIBNAME"); + lua_pushstring(L, LUASOCKET_LIBNAME); + lua_settable(L, LUA_GLOBALSINDEX); + /* initialize all modules */ + sock_open(L); + tm_open(L); + buf_open(L); + inet_open(L); + tcp_open(L); + udp_open(L); + /* load all Lua code */ + lua_dofile(L, "luasocket.lua"); return 0; } diff --git a/src/luasocket.h b/src/luasocket.h index fd22606..6c25af2 100644 --- a/src/luasocket.h +++ b/src/luasocket.h @@ -5,8 +5,8 @@ * * RCS ID: $Id$ \*=========================================================================*/ -#ifndef _LUASOCKET_H_ -#define _LUASOCKET_H_ +#ifndef LUASOCKET_H +#define LUASOCKET_H /*-------------------------------------------------------------------------*\ * Current luasocket version @@ -28,6 +28,6 @@ /*-------------------------------------------------------------------------*\ * Initializes the library. \*-------------------------------------------------------------------------*/ -LUASOCKET_API int lua_socketlibopen(lua_State *L); +LUASOCKET_API int luaopen_socketlib(lua_State *L); -#endif /* _LUASOCKET_H_ */ +#endif /* LUASOCKET_H */ diff --git a/src/mbox.lua b/src/mbox.lua index 4a72331..f52719b 100644 --- a/src/mbox.lua +++ b/src/mbox.lua @@ -5,10 +5,10 @@ mbox = Public function Public.split_message(message_s) local message = {} message_s = string.gsub(message_s, "\r\n", "\n") - string.gsub(message_s, "^(.-\n)\n", function (h) %message.headers = h end) - string.gsub(message_s, "^.-\n\n(.*)", function (b) %message.body = b end) + string.gsub(message_s, "^(.-\n)\n", function (h) message.headers = h end) + string.gsub(message_s, "^.-\n\n(.*)", function (b) message.body = b end) if not message.body then - string.gsub(message_s, "^\n(.*)", function (b) %message.body = b end) + string.gsub(message_s, "^\n(.*)", function (b) message.body = b end) end if not message.headers and not message.body then message.headers = message_s @@ -20,7 +20,7 @@ function Public.split_headers(headers_s) local headers = {} headers_s = string.gsub(headers_s, "\r\n", "\n") headers_s = string.gsub(headers_s, "\n[ ]+", " ") - string.gsub("\n" .. headers_s, "\n([^\n]+)", function (h) table.insert(%headers, h) end) + string.gsub("\n" .. headers_s, "\n([^\n]+)", function (h) table.insert(headers, h) end) return headers end @@ -32,10 +32,10 @@ function Public.parse_header(header_s) end function Public.parse_headers(headers_s) - local headers_t = %Public.split_headers(headers_s) + local headers_t = Public.split_headers(headers_s) local headers = {} for i = 1, table.getn(headers_t) do - local name, value = %Public.parse_header(headers_t[i]) + local name, value = Public.parse_header(headers_t[i]) if name then name = string.lower(name) if headers[name] then @@ -73,16 +73,16 @@ function Public.split_mbox(mbox_s) end function Public.parse(mbox_s) - local mbox = %Public.split_mbox(mbox_s) + local mbox = Public.split_mbox(mbox_s) for i = 1, table.getn(mbox) do - mbox[i] = %Public.parse_message(mbox[i]) + mbox[i] = Public.parse_message(mbox[i]) end return mbox end function Public.parse_message(message_s) local message = {} - message.headers, message.body = %Public.split_message(message_s) - message.headers = %Public.parse_headers(message.headers) + message.headers, message.body = Public.split_message(message_s) + message.headers = Public.parse_headers(message.headers) return message end diff --git a/src/smtp.lua b/src/smtp.lua index 0ba2b0f..604f79b 100644 --- a/src/smtp.lua +++ b/src/smtp.lua @@ -7,7 +7,8 @@ ----------------------------------------------------------------------------- local Public, Private = {}, {} -socket.smtp = Public +local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace +socket.smtp = Public -- create smtp sub namespace ----------------------------------------------------------------------------- -- Program constants @@ -23,32 +24,30 @@ Public.DOMAIN = os.getenv("SERVER_NAME") or "localhost" Public.SERVER = "localhost" ----------------------------------------------------------------------------- --- Tries to send data through socket. Closes socket on error. --- Input --- sock: server socket --- data: string to be sent +-- Tries to get a pattern from the server and closes socket on error +-- sock: socket connected to the server +-- pattern: pattern to receive -- Returns --- err: message in case of error, nil if successfull +-- received pattern on success +-- nil followed by error message on error ----------------------------------------------------------------------------- -function Private.try_send(sock, data) - local err = sock:send(data) - if err then sock:close() end - return err +function Private.try_receive(sock, pattern) + local data, err = sock:receive(pattern) + if not data then sock:close() end + return data, err end ----------------------------------------------------------------------------- --- Tries to get a pattern from the server and closes socket on error --- sock: socket opened to the server --- ...: pattern to receive +-- Tries to send data to the server and closes socket on error +-- sock: socket connected to the server +-- data: data to send -- Returns --- ...: received pattern --- err: error message if any +-- err: error message if any, nil if successfull ----------------------------------------------------------------------------- -function Private.try_receive(...) - local sock = arg[1] - local data, err = sock.receive(unpack(arg)) - if err then sock:close() end - return data, err +function Private.try_send(sock, data) + local sent, err = sock:send(data) + if not sent then sock:close() end + return err end ----------------------------------------------------------------------------- diff --git a/src/tcp.c b/src/tcp.c new file mode 100644 index 0000000..db6a38e --- /dev/null +++ b/src/tcp.c @@ -0,0 +1,222 @@ +/*=========================================================================*\ +* TCP object +* +* RCS ID: $Id$ +\*=========================================================================*/ +#include + +#include +#include + +#include "luasocket.h" + +#include "aux.h" +#include "inet.h" +#include "tcp.h" + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int tcp_global_create(lua_State *L); +static int tcp_meth_connect(lua_State *L); +static int tcp_meth_bind(lua_State *L); +static int tcp_meth_send(lua_State *L); +static int tcp_meth_getsockname(lua_State *L); +static int tcp_meth_getpeername(lua_State *L); +static int tcp_meth_receive(lua_State *L); +static int tcp_meth_accept(lua_State *L); +static int tcp_meth_close(lua_State *L); +static int tcp_meth_timeout(lua_State *L); + +/* tcp object methods */ +static luaL_reg tcp[] = { + {"connect", tcp_meth_connect}, + {"send", tcp_meth_send}, + {"receive", tcp_meth_receive}, + {"bind", tcp_meth_bind}, + {"accept", tcp_meth_accept}, + {"setpeername", tcp_meth_connect}, + {"setsockname", tcp_meth_bind}, + {"getpeername", tcp_meth_getpeername}, + {"getsockname", tcp_meth_getsockname}, + {"timeout", tcp_meth_timeout}, + {"close", tcp_meth_close}, + {NULL, NULL} +}; + +/* functions in library namespace */ +static luaL_reg func[] = { + {"tcp", tcp_global_create}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +void tcp_open(lua_State *L) +{ + /* create classes */ + aux_newclass(L, "tcp{master}", tcp); + aux_newclass(L, "tcp{client}", tcp); + aux_newclass(L, "tcp{server}", tcp); + /* create class groups */ + aux_add2group(L, "tcp{client}", "tcp{client, server}"); + aux_add2group(L, "tcp{server}", "tcp{client, server}"); + aux_add2group(L, "tcp{master}", "tcp{any}"); + aux_add2group(L, "tcp{client}", "tcp{any}"); + aux_add2group(L, "tcp{server}", "tcp{any}"); + /* define library functions */ + luaL_openlib(L, LUASOCKET_LIBNAME, func, 0); + lua_pop(L, 1); +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Just call buffered IO methods +\*-------------------------------------------------------------------------*/ +static int tcp_meth_send(lua_State *L) +{ + p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{client}", 1); + return buf_meth_send(L, &tcp->buf); +} + +static int tcp_meth_receive(lua_State *L) +{ + p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{client}", 1); + return buf_meth_receive(L, &tcp->buf); +} + +/*-------------------------------------------------------------------------*\ +* Just call inet methods +\*-------------------------------------------------------------------------*/ +static int tcp_meth_getpeername(lua_State *L) +{ + p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{client}", 1); + return inet_meth_getpeername(L, &tcp->sock); +} + +static int tcp_meth_getsockname(lua_State *L) +{ + p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{client, server}", 1); + return inet_meth_getsockname(L, &tcp->sock); +} + +/*-------------------------------------------------------------------------*\ +* Just call tm methods +\*-------------------------------------------------------------------------*/ +static int tcp_meth_timeout(lua_State *L) +{ + p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{any}", 1); + return tm_meth_timeout(L, &tcp->tm); +} + +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int tcp_meth_close(lua_State *L) +{ + p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{any}", 1); + sock_destroy(&tcp->sock); + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Turns a master tcp object into a client object. +\*-------------------------------------------------------------------------*/ +static int tcp_meth_connect(lua_State *L) +{ + p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{master}", 1); + const char *address = luaL_checkstring(L, 2); + unsigned short port = (ushort) luaL_checknumber(L, 3); + const char *err = inet_tryconnect(&tcp->sock, address, port); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + /* turn master object into a client object */ + aux_setclass(L, "tcp{client}", 1); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Turns a master object into a server object +\*-------------------------------------------------------------------------*/ +static int tcp_meth_bind(lua_State *L) +{ + p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{master}", 1); + const char *address = luaL_checkstring(L, 2); + unsigned short port = (ushort) luaL_checknumber(L, 3); + int backlog = (int) luaL_optnumber(L, 4, 1); + const char *err = inet_trybind(&tcp->sock, address, port, backlog); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + /* turn master object into a server object */ + aux_setclass(L, "tcp{server}", 1); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Waits for and returns a client object attempting connection to the +* server object +\*-------------------------------------------------------------------------*/ +static int tcp_meth_accept(lua_State *L) +{ + struct sockaddr_in addr; + size_t addr_len = sizeof(addr); + p_tcp server = (p_tcp) aux_checkclass(L, "tcp{server}", 1); + p_tm tm = &server->tm; + p_tcp client = lua_newuserdata(L, sizeof(t_tcp)); + tm_markstart(tm); + aux_setclass(L, "tcp{client}", -1); + for ( ;; ) { + sock_accept(&server->sock, &client->sock, + (SA *) &addr, &addr_len, tm_get(tm)); + if (client->sock == SOCK_INVALID) { + if (tm_get(tm) == 0) { + lua_pushnil(L); + error_push(L, IO_TIMEOUT); + return 2; + } + } else break; + } + /* initialize remaining structure fields */ + io_init(&client->io, (p_send) sock_send, (p_recv) sock_recv, &client->sock); + tm_init(&client->tm, -1, -1); + buf_init(&client->buf, &client->io, &client->tm); + return 1; +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a master tcp object +\*-------------------------------------------------------------------------*/ +int tcp_global_create(lua_State *L) +{ + /* allocate tcp object */ + p_tcp tcp = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); + /* set its type as master object */ + aux_setclass(L, "tcp{master}", -1); + /* try to allocate a system socket */ + const char *err = inet_trycreate(&tcp->sock, SOCK_STREAM); + if (err) { /* get rid of object on stack and push error */ + lua_pop(L, 1); + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + /* initialize remaining structure fields */ + io_init(&tcp->io, (p_send) sock_send, (p_recv) sock_recv, &tcp->sock); + tm_init(&tcp->tm, -1, -1); + buf_init(&tcp->buf, &tcp->io, &tcp->tm); + return 1; +} diff --git a/src/tcp.h b/src/tcp.h new file mode 100644 index 0000000..d4cc65c --- /dev/null +++ b/src/tcp.h @@ -0,0 +1,20 @@ +#ifndef TCP_H +#define TCP_H + +#include + +#include "buf.h" +#include "tm.h" +#include "sock.h" + +typedef struct t_tcp_ { + t_sock sock; + t_io io; + t_buf buf; + t_tm tm; +} t_tcp; +typedef t_tcp *p_tcp; + +void tcp_open(lua_State *L); + +#endif diff --git a/src/timeout.c b/src/timeout.c index 5549c89..17878aa 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -1,18 +1,19 @@ /*=========================================================================*\ * Timeout management functions * Global Lua functions: -* _sleep: (debug mode only) -* _time: (debug mode only) +* _sleep +* _time * * RCS ID: $Id$ \*=========================================================================*/ +#include + #include #include -#include "lspriv.h" -#include "lstm.h" - -#include +#include "luasocket.h" +#include "aux.h" +#include "tm.h" #ifdef WIN32 #include @@ -28,78 +29,69 @@ static int tm_lua_time(lua_State *L); static int tm_lua_sleep(lua_State *L); +static luaL_reg func[] = { + { "time", tm_lua_time }, + { "sleep", tm_lua_sleep }, + { NULL, NULL } +}; + /*=========================================================================*\ * Exported functions. \*=========================================================================*/ /*-------------------------------------------------------------------------*\ -* Sets timeout limits -* Input -* tm: timeout control structure -* mode: block or return timeout -* value: timeout value in miliseconds +* Initialize structure \*-------------------------------------------------------------------------*/ -void tm_set(p_tm tm, int tm_block, int tm_return) +void tm_init(p_tm tm, int block, int total) { - tm->tm_block = tm_block; - tm->tm_return = tm_return; + tm->block = block; + tm->total = total; } /*-------------------------------------------------------------------------*\ -* Returns timeout limits -* Input -* tm: timeout control structure -* mode: block or return timeout -* value: timeout value in miliseconds +* Set and get timeout limits \*-------------------------------------------------------------------------*/ -void tm_get(p_tm tm, int *tm_block, int *tm_return) -{ - if (tm_block) *tm_block = tm->tm_block; - if (tm_return) *tm_return = tm->tm_return; -} +void tm_setblock(p_tm tm, int block) +{ tm->block = block; } +void tm_settotal(p_tm tm, int total) +{ tm->total = total; } +int tm_getblock(p_tm tm) +{ return tm->block; } +int tm_gettotal(p_tm tm) +{ return tm->total; } +int tm_getstart(p_tm tm) +{ return tm->start; } /*-------------------------------------------------------------------------*\ -* Determines how much time we have left for the current io operation -* an IO write operation. +* Determines how much time we have left for the current operation * Input * tm: timeout control structure * Returns * the number of ms left or -1 if there is no time limit \*-------------------------------------------------------------------------*/ -int tm_getremaining(p_tm tm) +int tm_get(p_tm tm) { /* no timeout */ - if (tm->tm_block < 0 && tm->tm_return < 0) + if (tm->block < 0 && tm->total < 0) return -1; /* there is no block timeout, we use the return timeout */ - else if (tm->tm_block < 0) - return MAX(tm->tm_return - tm_gettime() + tm->tm_start, 0); + else if (tm->block < 0) + return MAX(tm->total - tm_gettime() + tm->start, 0); /* there is no return timeout, we use the block timeout */ - else if (tm->tm_return < 0) - return tm->tm_block; + else if (tm->total < 0) + return tm->block; /* both timeouts are specified */ - else return MIN(tm->tm_block, - MAX(tm->tm_return - tm_gettime() + tm->tm_start, 0)); + else return MIN(tm->block, + MAX(tm->total - tm_gettime() + tm->start, 0)); } /*-------------------------------------------------------------------------*\ -* Marks the operation start time in sock structure +* Marks the operation start time in structure * Input * tm: timeout control structure \*-------------------------------------------------------------------------*/ void tm_markstart(p_tm tm) { - tm->tm_start = tm_gettime(); - tm->tm_end = tm->tm_start; -} - -/*-------------------------------------------------------------------------*\ -* Returns the length of the operation in ms -* Input -* tm: timeout control structure -\*-------------------------------------------------------------------------*/ -int tm_getelapsed(p_tm tm) -{ - return tm->tm_end - tm->tm_start; + tm->start = tm_gettime(); } /*-------------------------------------------------------------------------*\ @@ -125,11 +117,31 @@ int tm_gettime(void) \*-------------------------------------------------------------------------*/ void tm_open(lua_State *L) { - (void) L; - lua_pushcfunction(L, tm_lua_time); - priv_newglobal(L, "_time"); - lua_pushcfunction(L, tm_lua_sleep); - priv_newglobal(L, "_sleep"); + luaL_openlib(L, LUASOCKET_LIBNAME, func, 0); +} + +/*-------------------------------------------------------------------------*\ +* Sets timeout values for IO operations +* Lua Input: base, time [, mode] +* time: time out value in seconds +* mode: "b" for block timeout, "t" for total timeout. (default: b) +\*-------------------------------------------------------------------------*/ +int tm_meth_timeout(lua_State *L, p_tm tm) +{ + int ms = lua_isnil(L, 2) ? -1 : (int) (luaL_checknumber(L, 2)*1000.0); + const char *mode = luaL_optstring(L, 3, "b"); + switch (*mode) { + case 'b': + tm_setblock(tm, ms); + break; + case 'r': case 't': + tm_settotal(tm, ms); + break; + default: + luaL_argcheck(L, 0, 3, "invalid timeout mode"); + break; + } + return 0; } /*=========================================================================*\ diff --git a/src/timeout.h b/src/timeout.h index 1dc0a5a..43476cb 100644 --- a/src/timeout.h +++ b/src/timeout.h @@ -3,23 +3,29 @@ * * RCS ID: $Id$ \*=========================================================================*/ -#ifndef _TM_H -#define _TM_H +#ifndef TM_H +#define TM_H -typedef struct t_tm_tag { - int tm_return; - int tm_block; - int tm_start; - int tm_end; +#include + +/* timeout control structure */ +typedef struct t_tm_ { + int total; /* total number of miliseconds for operation */ + int block; /* maximum time for blocking calls */ + int start; /* time of start of operation */ } t_tm; typedef t_tm *p_tm; -void tm_set(p_tm tm, int tm_block, int tm_return); -int tm_getremaining(p_tm tm); -int tm_getelapsed(p_tm tm); -int tm_gettime(void); -void tm_get(p_tm tm, int *tm_block, int *tm_return); -void tm_markstart(p_tm tm); void tm_open(lua_State *L); +void tm_init(p_tm tm, int block, int total); +void tm_setblock(p_tm tm, int block); +void tm_settotal(p_tm tm, int total); +int tm_getblock(p_tm tm); +int tm_gettotal(p_tm tm); +void tm_markstart(p_tm tm); +int tm_getstart(p_tm tm); +int tm_get(p_tm tm); +int tm_gettime(void); +int tm_meth_timeout(lua_State *L, p_tm tm); #endif diff --git a/src/udp.c b/src/udp.c index 361816c..1701d1b 100644 --- a/src/udp.c +++ b/src/udp.c @@ -1,299 +1,263 @@ /*=========================================================================*\ -* UDP class: inherits from Socked and Internet domain classes and provides -* all the functionality for UDP objects. -* Lua methods: -* send: using compat module -* sendto: using compat module -* receive: using compat module -* receivefrom: using compat module -* setpeername: using internet module -* setsockname: using internet module -* Global Lua functions: -* udp: creates the udp object +* UDP object * * RCS ID: $Id$ \*=========================================================================*/ -#include +#include #include #include -#include "lsinet.h" -#include "lsudp.h" -#include "lscompat.h" -#include "lsselect.h" +#include "luasocket.h" + +#include "aux.h" +#include "inet.h" +#include "udp.h" /*=========================================================================*\ -* Internal function prototypes. +* Internal function prototypes \*=========================================================================*/ -static int udp_lua_send(lua_State *L); -static int udp_lua_sendto(lua_State *L); -static int udp_lua_receive(lua_State *L); -static int udp_lua_receivefrom(lua_State *L); -static int udp_lua_setpeername(lua_State *L); -static int udp_lua_setsockname(lua_State *L); +static int udp_global_create(lua_State *L); +static int udp_meth_send(lua_State *L); +static int udp_meth_sendto(lua_State *L); +static int udp_meth_receive(lua_State *L); +static int udp_meth_receivefrom(lua_State *L); +static int udp_meth_getsockname(lua_State *L); +static int udp_meth_getpeername(lua_State *L); +static int udp_meth_setsockname(lua_State *L); +static int udp_meth_setpeername(lua_State *L); +static int udp_meth_close(lua_State *L); +static int udp_meth_timeout(lua_State *L); -static int udp_global_udp(lua_State *L); - -static struct luaL_reg funcs[] = { - {"send", udp_lua_send}, - {"sendto", udp_lua_sendto}, - {"receive", udp_lua_receive}, - {"receivefrom", udp_lua_receivefrom}, - {"setpeername", udp_lua_setpeername}, - {"setsockname", udp_lua_setsockname}, +/* udp object methods */ +static luaL_reg udp[] = { + {"setpeername", udp_meth_setpeername}, + {"setsockname", udp_meth_setsockname}, + {"getsockname", udp_meth_getsockname}, + {"getpeername", udp_meth_getpeername}, + {"send", udp_meth_send}, + {"sendto", udp_meth_sendto}, + {"receive", udp_meth_receive}, + {"receivefrom", udp_meth_receivefrom}, + {"timeout", udp_meth_timeout}, + {"close", udp_meth_close}, + {NULL, NULL} +}; + +/* functions in library namespace */ +static luaL_reg func[] = { + {"udp", udp_global_create}, + {NULL, NULL} }; -/*=========================================================================*\ -* Exported functions -\*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ void udp_open(lua_State *L) { - unsigned int i; - priv_newclass(L, UDP_CLASS); - udp_inherit(L, UDP_CLASS); - /* declare global functions */ - lua_pushcfunction(L, udp_global_udp); - priv_newglobal(L, "udp"); - for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) - priv_newglobalmethod(L, funcs[i].name); - /* make class selectable */ - select_addclass(L, UDP_CLASS); -} - -/*-------------------------------------------------------------------------*\ -* Hook object methods to methods table. -\*-------------------------------------------------------------------------*/ -void udp_inherit(lua_State *L, cchar *lsclass) -{ - unsigned int i; - inet_inherit(L, lsclass); - for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { - lua_pushcfunction(L, funcs[i].func); - priv_setmethod(L, lsclass, funcs[i].name); - } -} - -/*-------------------------------------------------------------------------*\ -* Initializes socket structure -\*-------------------------------------------------------------------------*/ -void udp_construct(lua_State *L, p_udp udp) -{ - inet_construct(L, (p_inet) udp); - udp->udp_connected = 0; -} - -/*-------------------------------------------------------------------------*\ -* Creates a socket structure and initializes it. A socket object is -* left in the Lua stack. -* Returns -* pointer to allocated structure -\*-------------------------------------------------------------------------*/ -p_udp udp_push(lua_State *L) -{ - p_udp udp = (p_udp) lua_newuserdata(L, sizeof(t_udp)); - priv_setclass(L, UDP_CLASS); - udp_construct(L, udp); - return udp; + /* create classes */ + aux_newclass(L, "udp{connected}", udp); + aux_newclass(L, "udp{unconnected}", udp); + /* create class groups */ + aux_add2group(L, "udp{connected}", "udp{any}"); + aux_add2group(L, "udp{unconnected}", "udp{any}"); + /* define library functions */ + luaL_openlib(L, LUASOCKET_LIBNAME, func, 0); + lua_pop(L, 1); } /*=========================================================================*\ -* Socket table constructors +* Lua methods \*=========================================================================*/ /*-------------------------------------------------------------------------*\ -* Creates a udp socket object and returns it to the Lua script. -* Lua Input: [options] -* options: socket options table -* Lua Returns -* On success: udp socket -* On error: nil, followed by an error message +* Send data through connected udp socket \*-------------------------------------------------------------------------*/ -static int udp_global_udp(lua_State *L) +static int udp_meth_send(lua_State *L) { - int oldtop = lua_gettop(L); - p_udp udp = udp_push(L); - cchar *err = inet_trysocket((p_inet) udp, SOCK_DGRAM); - if (err) { - lua_pushnil(L); - lua_pushstring(L, err); - return 2; - } - if (oldtop < 1) return 1; - err = compat_trysetoptions(L, udp->fd); - if (err) { - lua_pushnil(L); - lua_pushstring(L, err); - return 2; - } - return 1; -} - -/*=========================================================================*\ -* Socket table methods -\*=========================================================================*/ -/*-------------------------------------------------------------------------*\ -* Receives data from a UDP socket -* Lua Input: sock [, wanted] -* sock: client socket created by the connect function -* wanted: the number of bytes expected (default: LUASOCKET_UDPBUFFERSIZE) -* Lua Returns -* On success: datagram received -* On error: nil, followed by an error message -\*-------------------------------------------------------------------------*/ -static int udp_lua_receive(lua_State *L) -{ - p_udp udp = (p_udp) lua_touserdata(L, 1); - char buffer[UDP_DATAGRAMSIZE]; - size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buffer)); + p_udp udp = (p_udp) aux_checkclass(L, "udp{connected}", 1); + p_tm tm = &udp->tm; + size_t count, sent = 0; int err; - p_tm tm = &udp->base_tm; - wanted = MIN(wanted, sizeof(buffer)); + const char *data = luaL_checklstring(L, 2, &count); tm_markstart(tm); - err = compat_recv(udp->fd, buffer, wanted, &got, tm_getremaining(tm)); - if (err == PRIV_CLOSED) err = PRIV_REFUSED; - if (err != PRIV_DONE) lua_pushnil(L); - else lua_pushlstring(L, buffer, got); - priv_pusherror(L, err); + err = sock_send(&udp->sock, data, count, &sent, tm_get(tm)); + if (err == IO_DONE) lua_pushnumber(L, sent); + else lua_pushnil(L); + error_push(L, err); return 2; } /*-------------------------------------------------------------------------*\ -* Receives a datagram from a UDP socket -* Lua Input: sock [, wanted] -* sock: client socket created by the connect function -* wanted: the number of bytes expected (default: LUASOCKET_UDPBUFFERSIZE) -* Lua Returns -* On success: datagram received, ip and port of sender -* On error: nil, followed by an error message +* Send data through unconnected udp socket \*-------------------------------------------------------------------------*/ -static int udp_lua_receivefrom(lua_State *L) +static int udp_meth_sendto(lua_State *L) { - p_udp udp = (p_udp) lua_touserdata(L, 1); - p_tm tm = &udp->base_tm; - struct sockaddr_in peer; - size_t peer_len = sizeof(peer); - char buffer[UDP_DATAGRAMSIZE]; - size_t wanted = (size_t) luaL_optnumber(L, 2, sizeof(buffer)); - size_t got; + p_udp udp = (p_udp) aux_checkclass(L, "udp{unconnected}", 1); + size_t count, sent = 0; + const char *data = luaL_checklstring(L, 2, &count); + const char *ip = luaL_checkstring(L, 3); + ushort port = (ushort) luaL_checknumber(L, 4); + p_tm tm = &udp->tm; + struct sockaddr_in addr; int err; - if (udp->udp_connected) luaL_error(L, "receivefrom on connected socket"); + memset(&addr, 0, sizeof(addr)); + if (!inet_aton(ip, &addr.sin_addr)) + luaL_argerror(L, 3, "invalid ip address"); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); tm_markstart(tm); - wanted = MIN(wanted, sizeof(buffer)); - err = compat_recvfrom(udp->fd, buffer, wanted, &got, tm_getremaining(tm), - (SA *) &peer, &peer_len); - if (err == PRIV_CLOSED) err = PRIV_REFUSED; - if (err == PRIV_DONE) { + err = sock_sendto(&udp->sock, data, count, &sent, + (SA *) &addr, sizeof(addr), tm_get(tm)); + if (err == IO_DONE) lua_pushnumber(L, sent); + else lua_pushnil(L); + error_push(L, err == IO_CLOSED ? IO_REFUSED : err); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Receives data from a UDP socket +\*-------------------------------------------------------------------------*/ +static int udp_meth_receive(lua_State *L) +{ + p_udp udp = (p_udp) aux_checkgroup(L, "udp{any}", 1); + char buffer[UDP_DATAGRAMSIZE]; + size_t got, count = (size_t) luaL_optnumber(L, 2, sizeof(buffer)); + int err; + p_tm tm = &udp->tm; + count = MIN(count, sizeof(buffer)); + tm_markstart(tm); + err = sock_recv(&udp->sock, buffer, count, &got, tm_get(tm)); + if (err == IO_DONE) lua_pushlstring(L, buffer, got); + else lua_pushnil(L); + error_push(L, err); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Receives data and sender from a UDP socket +\*-------------------------------------------------------------------------*/ +static int udp_meth_receivefrom(lua_State *L) +{ + p_udp udp = (p_udp) aux_checkclass(L, "udp{unconnected}", 1); + struct sockaddr_in addr; + size_t addr_len = sizeof(addr); + char buffer[UDP_DATAGRAMSIZE]; + size_t got, count = (size_t) luaL_optnumber(L, 2, sizeof(buffer)); + int err; + p_tm tm = &udp->tm; + tm_markstart(tm); + count = MIN(count, sizeof(buffer)); + err = sock_recvfrom(&udp->sock, buffer, count, &got, + (SA *) &addr, &addr_len, tm_get(tm)); + if (err == IO_DONE) { lua_pushlstring(L, buffer, got); - lua_pushstring(L, inet_ntoa(peer.sin_addr)); - lua_pushnumber(L, ntohs(peer.sin_port)); + lua_pushstring(L, inet_ntoa(addr.sin_addr)); + lua_pushnumber(L, ntohs(addr.sin_port)); return 3; } else { lua_pushnil(L); - priv_pusherror(L, err); + error_push(L, err); return 2; } } /*-------------------------------------------------------------------------*\ -* Send data through a connected UDP socket -* Lua Input: sock, data -* sock: udp socket -* data: data to be sent -* Lua Returns -* On success: nil, followed by the total number of bytes sent -* On error: error message +* Just call inet methods \*-------------------------------------------------------------------------*/ -static int udp_lua_send(lua_State *L) +static int udp_meth_getpeername(lua_State *L) { - p_udp udp = (p_udp) lua_touserdata(L, 1); - p_tm tm = &udp->base_tm; - size_t wanted, sent = 0; - int err; - cchar *data = luaL_checklstring(L, 2, &wanted); - if (!udp->udp_connected) luaL_error(L, "send on unconnected socket"); - tm_markstart(tm); - err = compat_send(udp->fd, data, wanted, &sent, tm_getremaining(tm)); - priv_pusherror(L, err == PRIV_CLOSED ? PRIV_REFUSED : err); - lua_pushnumber(L, sent); - return 2; + p_udp udp = (p_udp) aux_checkclass(L, "udp{connected}", 1); + return inet_meth_getpeername(L, &udp->sock); +} + +static int udp_meth_getsockname(lua_State *L) +{ + p_udp udp = (p_udp) aux_checkgroup(L, "udp{any}", 1); + return inet_meth_getsockname(L, &udp->sock); } /*-------------------------------------------------------------------------*\ -* Send data through a unconnected UDP socket -* Lua Input: sock, data, ip, port -* sock: udp socket -* data: data to be sent -* ip: ip address of target -* port: port in target -* Lua Returns -* On success: nil, followed by the total number of bytes sent -* On error: error message +* Just call tm methods \*-------------------------------------------------------------------------*/ -static int udp_lua_sendto(lua_State *L) +static int udp_meth_timeout(lua_State *L) { - p_udp udp = (p_udp) lua_touserdata(L, 1); - size_t wanted, sent = 0; - cchar *data = luaL_checklstring(L, 2, &wanted); - cchar *ip = luaL_checkstring(L, 3); - ushort port = (ushort) luaL_checknumber(L, 4); - p_tm tm = &udp->base_tm; - struct sockaddr_in peer; - int err; - if (udp->udp_connected) luaL_error(L, "sendto on connected socket"); - memset(&peer, 0, sizeof(peer)); - if (!inet_aton(ip, &peer.sin_addr)) luaL_error(L, "invalid ip address"); - peer.sin_family = AF_INET; - peer.sin_port = htons(port); - tm_markstart(tm); - err = compat_sendto(udp->fd, data, wanted, &sent, tm_getremaining(tm), - (SA *) &peer, sizeof(peer)); - priv_pusherror(L, err == PRIV_CLOSED ? PRIV_REFUSED : err); - lua_pushnumber(L, sent); - return 2; + p_udp udp = (p_udp) aux_checkgroup(L, "udp{any}", 1); + return tm_meth_timeout(L, &udp->tm); } /*-------------------------------------------------------------------------*\ -* Associates a local address to an UDP socket -* Lua Input: address, port -* address: host name or ip address to bind to -* port: port to bind to -* Lua Returns -* On success: nil -* On error: error message +* Turns a master udp object into a client object. \*-------------------------------------------------------------------------*/ -static int udp_lua_setsockname(lua_State * L) +static int udp_meth_setpeername(lua_State *L) { - p_udp udp = (p_udp) lua_touserdata(L, 1); - cchar *address = luaL_checkstring(L, 2); - ushort port = (ushort) luaL_checknumber(L, 3); - cchar *err = inet_trybind((p_inet) udp, address, port); - if (err) lua_pushstring(L, err); - else lua_pushnil(L); - return 1; -} - -/*-------------------------------------------------------------------------*\ -* Sets a peer for a UDP socket -* Lua Input: address, port -* address: remote host name -* port: remote host port -* Lua Returns -* On success: nil -* On error: error message -\*-------------------------------------------------------------------------*/ -static int udp_lua_setpeername(lua_State *L) -{ - p_udp udp = (p_udp) lua_touserdata(L, 1); - cchar *address = luaL_checkstring(L, 2); - ushort port = (ushort) luaL_checknumber(L, 3); - cchar *err = inet_tryconnect((p_inet) udp, address, port); - if (!err) { - udp->udp_connected = 1; + p_udp udp = (p_udp) aux_checkclass(L, "udp{unconnected}", 1); + const char *address = luaL_checkstring(L, 2); + int connecting = strcmp(address, "*"); + unsigned short port = connecting ? + (ushort) luaL_checknumber(L, 3) : (ushort) luaL_optnumber(L, 3, 0); + const char *err = inet_tryconnect(&udp->sock, address, port); + if (err) { lua_pushnil(L); - } else lua_pushstring(L, err); + lua_pushstring(L, err); + return 2; + } + /* change class to connected or unconnected depending on address */ + if (connecting) aux_setclass(L, "udp{connected}", 1); + else aux_setclass(L, "udp{unconnected}", 1); + lua_pushnumber(L, 1); return 1; } +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int udp_meth_close(lua_State *L) +{ + p_udp udp = (p_udp) aux_checkgroup(L, "udp{any}", 1); + sock_destroy(&udp->sock); + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Turns a master object into a server object +\*-------------------------------------------------------------------------*/ +static int udp_meth_setsockname(lua_State *L) +{ + p_udp udp = (p_udp) aux_checkclass(L, "udp{master}", 1); + const char *address = luaL_checkstring(L, 2); + unsigned short port = (ushort) luaL_checknumber(L, 3); + const char *err = inet_trybind(&udp->sock, address, port, -1); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a master udp object +\*-------------------------------------------------------------------------*/ +int udp_global_create(lua_State *L) +{ + /* allocate udp object */ + p_udp udp = (p_udp) lua_newuserdata(L, sizeof(t_udp)); + /* set its type as master object */ + aux_setclass(L, "udp{unconnected}", -1); + /* try to allocate a system socket */ + const char *err = inet_trycreate(&udp->sock, SOCK_DGRAM); + if (err) { + /* get rid of object on stack and push error */ + lua_pop(L, 1); + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + /* initialize timeout management */ + tm_init(&udp->tm, -1, -1); + return 1; +} diff --git a/src/udp.h b/src/udp.h index 928a99f..4ba53e6 100644 --- a/src/udp.h +++ b/src/udp.h @@ -1,30 +1,19 @@ -/*=========================================================================*\ -* UDP class: inherits from Socked and Internet domain classes and provides -* all the functionality for UDP objects. -* -* RCS ID: $Id$ -\*=========================================================================*/ -#ifndef UDP_H_ -#define UDP_H_ +#ifndef UDP_H +#define UDP_H -#include "lsinet.h" +#include -#define UDP_CLASS "luasocket(UDP socket)" +#include "tm.h" +#include "sock.h" #define UDP_DATAGRAMSIZE 576 -#define UDP_FIELDS \ - INET_FIELDS; \ - int udp_connected - -typedef struct t_udp_tag { - UDP_FIELDS; +typedef struct t_udp_ { + t_sock sock; + t_tm tm; } t_udp; typedef t_udp *p_udp; -void udp_inherit(lua_State *L, cchar *lsclass); -void udp_construct(lua_State *L, p_udp udp); void udp_open(lua_State *L); -p_udp udp_push(lua_State *L); #endif diff --git a/src/unix.c b/src/usocket.c similarity index 60% rename from src/unix.c rename to src/usocket.c index 23984b0..b4b8d5a 100644 --- a/src/unix.c +++ b/src/usocket.c @@ -1,5 +1,5 @@ /*=========================================================================*\ -* Network compatibilization module: Unix version +* Socket compatibilization module for Unix * * RCS ID: $Id$ \*=========================================================================*/ @@ -7,20 +7,20 @@ #include #include -#include "lscompat.h" +#include "sock.h" /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ -static cchar *try_setoption(lua_State *L, COMPAT_FD sock); -static cchar *try_setbooloption(lua_State *L, COMPAT_FD sock, int name); +static const char *try_setoption(lua_State *L, p_sock ps); +static const char *try_setbooloption(lua_State *L, p_sock ps, int name); /*=========================================================================*\ * Exported functions. \*=========================================================================*/ -int compat_open(lua_State *L) +int sock_open(lua_State *L) { - /* Instals a handler to ignore sigpipe. */ + /* instals a handler to ignore sigpipe. */ struct sigaction new; memset(&new, 0, sizeof(new)); new.sa_handler = SIG_IGN; @@ -28,143 +28,178 @@ int compat_open(lua_State *L) return 1; } -COMPAT_FD compat_accept(COMPAT_FD s, struct sockaddr *addr, - size_t *len, int deadline) +void sock_destroy(p_sock ps) { - struct timeval tv; - fd_set fds; - tv.tv_sec = deadline / 1000; - tv.tv_usec = (deadline % 1000) * 1000; - FD_ZERO(&fds); - FD_SET(s, &fds); - select(s+1, &fds, NULL, NULL, deadline >= 0 ? &tv : NULL); - return accept(s, addr, len); + close(*ps); } -int compat_send(COMPAT_FD c, cchar *data, size_t count, size_t *sent, - int deadline) +const char *sock_create(p_sock ps, int domain, int type, int protocol) { + t_sock sock = socket(domain, type, protocol); + if (sock == SOCK_INVALID) return sock_createstrerror(); + *ps = sock; + sock_setnonblocking(ps); + sock_setreuseaddr(ps); + return NULL; +} + +const char *sock_connect(p_sock ps, SA *addr, size_t addr_len) +{ + if (connect(*ps, addr, addr_len) < 0) return sock_connectstrerror(); + else return NULL; +} + +const char *sock_bind(p_sock ps, SA *addr, size_t addr_len) +{ + if (bind(*ps, addr, addr_len) < 0) return sock_bindstrerror(); + else return NULL; +} + +void sock_listen(p_sock ps, int backlog) +{ + listen(*ps, backlog); +} + +void sock_accept(p_sock ps, p_sock pa, SA *addr, size_t *addr_len, int timeout) +{ + t_sock sock = *ps; + struct timeval tv; + fd_set fds; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + FD_ZERO(&fds); + FD_SET(sock, &fds); + select(sock+1, &fds, NULL, NULL, timeout >= 0 ? &tv : NULL); + *pa = accept(sock, addr, addr_len); +} + +int sock_send(p_sock ps, const char *data, size_t count, size_t *sent, + int timeout) +{ + t_sock sock = *ps; struct timeval tv; fd_set fds; ssize_t put = 0; int err; int ret; - tv.tv_sec = deadline / 1000; - tv.tv_usec = (deadline % 1000) * 1000; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&fds); - FD_SET(c, &fds); - ret = select(c+1, NULL, &fds, NULL, deadline >= 0 ? &tv : NULL); + FD_SET(sock, &fds); + ret = select(sock+1, NULL, &fds, NULL, timeout >= 0 ? &tv : NULL); if (ret > 0) { - put = write(c, data, count); + put = write(sock, data, count); if (put <= 0) { - err = PRIV_CLOSED; + err = IO_CLOSED; #ifdef __CYGWIN__ /* this is for CYGWIN, which is like Unix but has Win32 bugs */ - if (errno == EWOULDBLOCK) err = PRIV_DONE; + if (errno == EWOULDBLOCK) err = IO_DONE; #endif *sent = 0; } else { *sent = put; - err = PRIV_DONE; + err = IO_DONE; } return err; } else { *sent = 0; - return PRIV_TIMEOUT; + return IO_TIMEOUT; } } -int compat_sendto(COMPAT_FD c, cchar *data, size_t count, size_t *sent, - int deadline, SA *addr, size_t len) +int sock_sendto(p_sock ps, const char *data, size_t count, size_t *sent, + SA *addr, size_t addr_len, int timeout) { + t_sock sock = *ps; struct timeval tv; fd_set fds; ssize_t put = 0; int err; int ret; - tv.tv_sec = deadline / 1000; - tv.tv_usec = (deadline % 1000) * 1000; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&fds); - FD_SET(c, &fds); - ret = select(c+1, NULL, &fds, NULL, deadline >= 0 ? &tv : NULL); + FD_SET(sock, &fds); + ret = select(sock+1, NULL, &fds, NULL, timeout >= 0 ? &tv : NULL); if (ret > 0) { - put = sendto(c, data, count, 0, addr, len); + put = sendto(sock, data, count, 0, addr, addr_len); if (put <= 0) { - err = PRIV_CLOSED; + err = IO_CLOSED; #ifdef __CYGWIN__ /* this is for CYGWIN, which is like Unix but has Win32 bugs */ - if (sent < 0 && errno == EWOULDBLOCK) err = PRIV_DONE; + if (sent < 0 && errno == EWOULDBLOCK) err = IO_DONE; #endif *sent = 0; } else { *sent = put; - err = PRIV_DONE; + err = IO_DONE; } return err; } else { *sent = 0; - return PRIV_TIMEOUT; + return IO_TIMEOUT; } } -int compat_recv(COMPAT_FD c, char *data, size_t count, size_t *got, - int deadline) +int sock_recv(p_sock ps, char *data, size_t count, size_t *got, int timeout) { + t_sock sock = *ps; struct timeval tv; fd_set fds; int ret; ssize_t taken = 0; - tv.tv_sec = deadline / 1000; - tv.tv_usec = (deadline % 1000) * 1000; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&fds); - FD_SET(c, &fds); - ret = select(c+1, &fds, NULL, NULL, deadline >= 0 ? &tv : NULL); + FD_SET(sock, &fds); + ret = select(sock+1, &fds, NULL, NULL, timeout >= 0 ? &tv : NULL); if (ret > 0) { - taken = read(c, data, count); + taken = read(sock, data, count); if (taken <= 0) { *got = 0; - return PRIV_CLOSED; + return IO_CLOSED; } else { *got = taken; - return PRIV_DONE; + return IO_DONE; } } else { *got = 0; - return PRIV_TIMEOUT; + return IO_TIMEOUT; } } -int compat_recvfrom(COMPAT_FD c, char *data, size_t count, size_t *got, - int deadline, SA *addr, size_t *len) +int sock_recvfrom(p_sock ps, char *data, size_t count, size_t *got, + SA *addr, size_t *addr_len, int timeout) { + t_sock sock = *ps; struct timeval tv; fd_set fds; int ret; ssize_t taken = 0; - tv.tv_sec = deadline / 1000; - tv.tv_usec = (deadline % 1000) * 1000; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&fds); - FD_SET(c, &fds); - ret = select(c+1, &fds, NULL, NULL, deadline >= 0 ? &tv : NULL); + FD_SET(sock, &fds); + ret = select(sock+1, &fds, NULL, NULL, timeout >= 0 ? &tv : NULL); if (ret > 0) { - taken = recvfrom(c, data, count, 0, addr, len); + taken = recvfrom(sock, data, count, 0, addr, addr_len); if (taken <= 0) { *got = 0; - return PRIV_CLOSED; + return IO_CLOSED; } else { *got = taken; - return PRIV_DONE; + return IO_DONE; } } else { *got = 0; - return PRIV_TIMEOUT; + return IO_TIMEOUT; } } /*-------------------------------------------------------------------------*\ * Returns a string describing the last host manipulation error. \*-------------------------------------------------------------------------*/ -const char *compat_hoststrerror(void) +const char *sock_hoststrerror(void) { switch (h_errno) { case HOST_NOT_FOUND: return "host not found"; @@ -178,7 +213,7 @@ const char *compat_hoststrerror(void) /*-------------------------------------------------------------------------*\ * Returns a string describing the last socket manipulation error. \*-------------------------------------------------------------------------*/ -const char *compat_socketstrerror(void) +const char *sock_createstrerror(void) { switch (errno) { case EACCES: return "access denied"; @@ -192,7 +227,7 @@ const char *compat_socketstrerror(void) /*-------------------------------------------------------------------------*\ * Returns a string describing the last bind command error. \*-------------------------------------------------------------------------*/ -const char *compat_bindstrerror(void) +const char *sock_bindstrerror(void) { switch (errno) { case EBADF: return "invalid descriptor"; @@ -209,7 +244,7 @@ const char *compat_bindstrerror(void) /*-------------------------------------------------------------------------*\ * Returns a string describing the last connect error. \*-------------------------------------------------------------------------*/ -const char *compat_connectstrerror(void) +const char *sock_connectstrerror(void) { switch (errno) { case EBADF: return "invalid descriptor"; @@ -229,40 +264,30 @@ const char *compat_connectstrerror(void) * Input * sock: socket descriptor \*-------------------------------------------------------------------------*/ -void compat_setreuseaddr(COMPAT_FD sock) +void sock_setreuseaddr(p_sock ps) { int val = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)); -} - -COMPAT_FD compat_socket(int domain, int type, int protocol) -{ - COMPAT_FD sock = socket(domain, type, protocol); - if (sock != COMPAT_INVALIDFD) { - compat_setnonblocking(sock); - compat_setreuseaddr(sock); - } - return sock; + setsockopt(*ps, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)); } /*-------------------------------------------------------------------------*\ * Put socket into blocking mode. \*-------------------------------------------------------------------------*/ -void compat_setblocking(COMPAT_FD sock) +void sock_setblocking(p_sock ps) { - int flags = fcntl(sock, F_GETFL, 0); + int flags = fcntl(*ps, F_GETFL, 0); flags &= (~(O_NONBLOCK)); - fcntl(sock, F_SETFL, flags); + fcntl(*ps, F_SETFL, flags); } /*-------------------------------------------------------------------------*\ * Put socket into non-blocking mode. \*-------------------------------------------------------------------------*/ -void compat_setnonblocking(COMPAT_FD sock) +void sock_setnonblocking(p_sock ps) { - int flags = fcntl(sock, F_GETFL, 0); + int flags = fcntl(*ps, F_GETFL, 0); flags |= O_NONBLOCK; - fcntl(sock, F_SETFL, flags); + fcntl(*ps, F_SETFL, flags); } /*-------------------------------------------------------------------------*\ @@ -273,54 +298,50 @@ void compat_setnonblocking(COMPAT_FD sock) * Returns * NULL if successfull, error message on error \*-------------------------------------------------------------------------*/ -cchar *compat_trysetoptions(lua_State *L, COMPAT_FD sock) +const char *sock_trysetoptions(lua_State *L, p_sock ps) { if (!lua_istable(L, 1)) luaL_argerror(L, 1, "invalid options table"); lua_pushnil(L); while (lua_next(L, 1)) { - cchar *err = try_setoption(L, sock); + const char *err = try_setoption(L, ps); lua_pop(L, 1); if (err) return err; } return NULL; } -/*=========================================================================*\ -* Internal functions. -\*=========================================================================*/ -static cchar *try_setbooloption(lua_State *L, COMPAT_FD sock, int name) -{ - int bool, res; - if (!lua_isnumber(L, -1)) luaL_error(L, "invalid option value"); - bool = (int) lua_tonumber(L, -1); - res = setsockopt(sock, SOL_SOCKET, name, (char *) &bool, sizeof(bool)); - if (res < 0) return "error setting option"; - else return NULL; -} - - /*-------------------------------------------------------------------------*\ * Set socket options from a table on top of Lua stack. -* Supports SO_KEEPALIVE, SO_DONTROUTE, SO_BROADCAST, and SO_LINGER options. +* Supports SO_KEEPALIVE, SO_DONTROUTE, and SO_BROADCAST options. * Input -* L: Lua state to use -* sock: socket descriptor +* sock: socket * Returns * 1 if successful, 0 otherwise \*-------------------------------------------------------------------------*/ -static cchar *try_setoption(lua_State *L, COMPAT_FD sock) +static const char *try_setoption(lua_State *L, p_sock ps) { - static cchar *options[] = { - "SO_KEEPALIVE", "SO_DONTROUTE", "SO_BROADCAST", "SO_LINGER", NULL + static const char *options[] = { + "SO_KEEPALIVE", "SO_DONTROUTE", "SO_BROADCAST", NULL }; - cchar *option = lua_tostring(L, -2); + const char *option = lua_tostring(L, -2); if (!lua_isstring(L, -2)) return "invalid option"; switch (luaL_findstring(option, options)) { - case 0: return try_setbooloption(L, sock, SO_KEEPALIVE); - case 1: return try_setbooloption(L, sock, SO_DONTROUTE); - case 2: return try_setbooloption(L, sock, SO_BROADCAST); - case 3: return "SO_LINGER is deprecated"; + case 0: return try_setbooloption(L, ps, SO_KEEPALIVE); + case 1: return try_setbooloption(L, ps, SO_DONTROUTE); + case 2: return try_setbooloption(L, ps, SO_BROADCAST); default: return "unsupported option"; } } +/*=========================================================================*\ +* Internal functions. +\*=========================================================================*/ +static const char *try_setbooloption(lua_State *L, p_sock ps, int name) +{ + int bool, res; + if (!lua_isnumber(L, -1)) luaL_error(L, "invalid option value"); + bool = (int) lua_tonumber(L, -1); + res = setsockopt(*ps, SOL_SOCKET, name, (char *) &bool, sizeof(bool)); + if (res < 0) return "error setting option"; + else return NULL; +} diff --git a/src/unix.h b/src/usocket.h similarity index 74% rename from src/unix.h rename to src/usocket.h index 863e478..f124bce 100644 --- a/src/unix.h +++ b/src/usocket.h @@ -1,10 +1,10 @@ /*=========================================================================*\ -* Network compatibilization module: Unix version +* Socket compatibilization module for Unix * * RCS ID: $Id$ \*=========================================================================*/ -#ifndef UNIX_H_ -#define UNIX_H_ +#ifndef UNIX_H +#define UNIX_H /*=========================================================================*\ * BSD include files @@ -31,13 +31,9 @@ #include #include -#define COMPAT_FD int -#define COMPAT_INVALIDFD (-1) +typedef int t_sock; +typedef t_sock *p_sock; -#define compat_bind bind -#define compat_connect connect -#define compat_listen listen -#define compat_close close -#define compat_select select +#define SOCK_INVALID (-1) -#endif /* UNIX_H_ */ +#endif /* UNIX_H */ diff --git a/test/ftptest.lua b/test/ftptest.lua index ee3af91..6ba61a4 100644 --- a/test/ftptest.lua +++ b/test/ftptest.lua @@ -1,5 +1,3 @@ -dofile("noglobals.lua") - local similar = function(s1, s2) return string.lower(string.gsub(s1, "%s", "")) == @@ -34,7 +32,7 @@ end local index, err, saved, back, expected -local t = socket._time() +local t = socket.time() index = readfile("test/index.html") @@ -112,4 +110,4 @@ back, err = socket.ftp.get("ftp://localhost/index.wrong.html;type=a") check(err, err) print("passed all tests") -print(string.format("done in %.2fs", socket._time() - t)) +print(string.format("done in %.2fs", socket.time() - t)) diff --git a/test/httptest.lua b/test/httptest.lua index 1eb4b6a..030974c 100644 --- a/test/httptest.lua +++ b/test/httptest.lua @@ -3,9 +3,6 @@ -- needs ScriptAlias from /home/c/diego/tec/luasocket/test/cgi -- to /luasocket-test-cgi -- needs AllowOverride AuthConfig on /home/c/diego/tec/luasocket/test/auth - -dofile("noglobals.lua") - local similar = function(s1, s2) return string.lower(string.gsub(s1 or "", "%s", "")) == string.lower(string.gsub(s2 or "", "%s", "")) @@ -27,27 +24,27 @@ end local check = function (v, e) if v then print("ok") - else %fail(e) end + else fail(e) end end local check_request = function(request, expect, ignore) local response = socket.http.request(request) for i,v in response do if not ignore[i] then - if v ~= expect[i] then %fail(i .. " differs!") end + if v ~= expect[i] then fail(i .. " differs!") end end end for i,v in expect do if not ignore[i] then - if v ~= response[i] then %fail(i .. " differs!") end + if v ~= response[i] then fail(i .. " differs!") end end end print("ok") end -local request, response, ignore, expect, index, prefix, cgiprefix +local host, request, response, ignore, expect, index, prefix, cgiprefix -local t = socket._time() +local t = socket.time() host = host or "localhost" prefix = prefix or "/luasocket" @@ -310,4 +307,4 @@ check(response and response.headers) print("passed all tests") -print(string.format("done in %.2fs", socket._time() - t)) +print(string.format("done in %.2fs", socket.time() - t)) diff --git a/test/smtptest.lua b/test/smtptest.lua index 27ba400..09bf634 100644 --- a/test/smtptest.lua +++ b/test/smtptest.lua @@ -11,7 +11,7 @@ local files = { "/var/spool/mail/luasock3", } -local t = socket._time() +local t = socket.time() local err dofile("mbox.lua") @@ -106,7 +106,7 @@ local insert = function(sent, message) end local mark = function() - local time = socket._time() + local time = socket.time() return { time = time } end @@ -116,11 +116,11 @@ local wait = function(sentinel, n) while 1 do local mbox = parse(get()) if n == table.getn(mbox) then break end - if socket._time() - sentinel.time > 50 then + if socket.time() - sentinel.time > 50 then to = 1 break end - socket._sleep(1) + socket.sleep(1) io.write(".") io.stdout:flush() end @@ -256,4 +256,4 @@ for i = 1, table.getn(mbox) do end print("passed all tests") -print(string.format("done in %.2fs", socket._time() - t)) +print(string.format("done in %.2fs", socket.time() - t)) diff --git a/test/testclnt.lua b/test/testclnt.lua index 3e80a36..b2b4b18 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -43,7 +43,7 @@ function check_timeout(tm, sl, elapsed, err, opp, mode, alldone) else pass("proper timeout") end end else - if mode == "return" then + if mode == "total" then if elapsed > tm then if err ~= "timeout" then fail("should have timed out") else pass("proper timeout") end @@ -66,17 +66,17 @@ function check_timeout(tm, sl, elapsed, err, opp, mode, alldone) end end +if not socket.debug then + fail("Please define LUASOCKET_DEBUG and recompile LuaSocket") +end + io.write("----------------------------------------------\n", "LuaSocket Test Procedures\n", "----------------------------------------------\n") -if not socket._time or not socket._sleep then - fail("not compiled with _DEBUG") -end +start = socket.time() -start = socket._time() - -function tcpreconnect() +function reconnect() io.write("attempting data connection... ") if data then data:close() end remote [[ @@ -87,109 +87,85 @@ function tcpreconnect() if not data then fail(err) else pass("connected!") end end -reconnect = tcpreconnect pass("attempting control connection...") control, err = socket.connect(host, port) if err then fail(err) else pass("connected!") end ------------------------------------------------------------------------- -test("bugs") - -io.write("empty host connect: ") -function empty_connect() - if data then data:close() data = nil end - remote [[ - if data then data:close() data = nil end - data = server:accept() - ]] - data, err = socket.connect("", port) - if not data then - pass("ok") - data = socket.connect(host, port) - else fail("should not have connected!") end -end - -empty_connect() - -io.write("active close: ") -function active_close() - reconnect() - if socket._isclosed(data) then fail("should not be closed") end - data:close() - if not socket._isclosed(data) then fail("should be closed") end - data = nil - local udp = socket.udp() - if socket._isclosed(udp) then fail("should not be closed") end - udp:close() - if not socket._isclosed(udp) then fail("should be closed") end - pass("ok") -end - -active_close() - ------------------------------------------------------------------------ test("method registration") function test_methods(sock, methods) for _, v in methods do if type(sock[v]) ~= "function" then - fail(type(sock) .. " method " .. v .. "not registered") + fail(sock.class .. " method '" .. v .. "' not registered") end end - pass(type(sock) .. " methods are ok") + pass(sock.class .. " methods are ok") end -test_methods(control, { - "close", - "timeout", - "send", - "receive", +test_methods(socket.tcp(), { + "connect", + "send", + "receive", + "bind", + "accept", + "setpeername", + "setsockname", "getpeername", - "getsockname" + "getsockname", + "timeout", + "close", }) -if udpsocket then - test_methods(socket.udp(), { - "close", - "timeout", - "send", - "sendto", - "receive", - "receivefrom", - "getpeername", - "getsockname", - "setsockname", - "setpeername" - }) -end - -test_methods(socket.bind("*", 0), { - "close", +test_methods(socket.udp(), { + "getpeername", + "getsockname", + "setsockname", + "setpeername", + "send", + "sendto", + "receive", + "receivefrom", "timeout", - "accept" + "close", }) ------------------------------------------------------------------------ -test("select function") -function test_selectbugs() - local r, s, e = socket.select(nil, nil, 0.1) - assert(type(r) == "table" and type(s) == "table" and e == "timeout") - pass("both nil: ok") - local udp = socket.udp() - udp:close() - r, s, e = socket.select({ udp }, { udp }, 0.1) - assert(type(r) == "table" and type(s) == "table" and e == "timeout") - pass("closed sockets: ok") - e = pcall(socket.select, "wrong", 1, 0.1) - assert(e == false) - e = pcall(socket.select, {}, 1, 0.1) - assert(e == false) - pass("invalid input: ok") +test("mixed patterns") + +function test_mixed(len) + reconnect() + local inter = math.ceil(len/4) + local p1 = "unix " .. string.rep("x", inter) .. "line\n" + local p2 = "dos " .. string.rep("y", inter) .. "line\r\n" + local p3 = "raw " .. string.rep("z", inter) .. "bytes" + local p4 = "end" .. string.rep("w", inter) .. "bytes" + local bp1, bp2, bp3, bp4 + pass(len .. " byte(s) patterns") +remote (string.format("str = data:receive(%d)", + string.len(p1)+string.len(p2)+string.len(p3)+string.len(p4))) + sent, err = data:send(p1, p2, p3, p4) + if err then fail(err) end +remote "data:send(str); data:close()" + bp1, bp2, bp3, bp4, err = data:receive("*l", "*l", string.len(p3), "*a") + if err then fail(err) end + if bp1.."\n" == p1 and bp2.."\r\n" == p2 and bp3 == p3 and bp4 == p4 then + pass("patterns match") + else fail("patterns don't match") end end -test_selectbugs() + +test_mixed(1) +test_mixed(17) +test_mixed(200) +test_mixed(4091) +test_mixed(80199) +test_mixed(4091) +test_mixed(200) +test_mixed(17) +test_mixed(1) ------------------------------------------------------------------------ test("character line") @@ -202,7 +178,7 @@ function test_asciiline(len) str = str .. str10 pass(len .. " byte(s) line") remote "str = data:receive()" - err = data:send(str, "\n") + sent, err = data:send(str, "\n") if err then fail(err) end remote "data:send(str, '\\n')" back, err = data:receive() @@ -230,7 +206,7 @@ function test_rawline(len) str = str .. str10 pass(len .. " byte(s) line") remote "str = data:receive()" - err = data:send(str, "\n") + sent, err = data:send(str, "\n") if err then fail(err) end remote "data:send(str, '\\n')" back, err = data:receive() @@ -262,9 +238,9 @@ function test_raw(len) s2 = string.rep("y", len-half) pass(len .. " byte(s) block") remote (string.format("str = data:receive(%d)", len)) - err = data:send(s1) + sent, err = data:send(s1) if err then fail(err) end - err = data:send(s2) + sent, err = data:send(s2) if err then fail(err) end remote "data:send(str)" back, err = data:receive(len) @@ -304,39 +280,139 @@ test_raw(17) test_raw(1) ------------------------------------------------------------------------ -test("mixed patterns") -reconnect() +test("total timeout on receive") +function test_totaltimeoutreceive(len, tm, sl) + local str, err, total + reconnect() + pass("%d bytes, %ds total timeout, %ds pause", len, tm, sl) + remote (string.format ([[ + data:timeout(%d) + str = string.rep('a', %d) + data:send(str) + print('server: sleeping for %ds') + socket.sleep(%d) + print('server: woke up') + data:send(str) + ]], 2*tm, len, sl, sl)) + data:timeout(tm, "total") + str, err, elapsed = data:receive(2*len) + check_timeout(tm, sl, elapsed, err, "receive", "total", + string.len(str) == 2*len) +end +test_totaltimeoutreceive(800091, 1, 3) +test_totaltimeoutreceive(800091, 2, 3) +test_totaltimeoutreceive(800091, 3, 2) +test_totaltimeoutreceive(800091, 3, 1) -function test_mixed(len) - local inter = math.floor(len/3) - local p1 = "unix " .. string.rep("x", inter) .. "line\n" - local p2 = "dos " .. string.rep("y", inter) .. "line\r\n" - local p3 = "raw " .. string.rep("z", inter) .. "bytes" - local bp1, bp2, bp3 - pass(len .. " byte(s) patterns") -remote (string.format("str = data:receive(%d)", - string.len(p1)+string.len(p2)+string.len(p3))) - err = data:send(p1, p2, p3) - if err then fail(err) end -remote "data:send(str)" - bp1, bp2, bp3, err = data:receive("*lu", "*l", string.len(p3)) - if err then fail(err) end - if bp1.."\n" == p1 and bp2.."\r\n" == p2 and bp3 == p3 then - pass("patterns match") - else fail("patterns don't match") end +------------------------------------------------------------------------ +test("total timeout on send") +function test_totaltimeoutsend(len, tm, sl) + local str, err, total + reconnect() + pass("%d bytes, %ds total timeout, %ds pause", len, tm, sl) + remote (string.format ([[ + data:timeout(%d) + str = data:receive(%d) + print('server: sleeping for %ds') + socket.sleep(%d) + print('server: woke up') + str = data:receive(%d) + ]], 2*tm, len, sl, sl, len)) + data:timeout(tm, "total") + str = string.rep("a", 2*len) + total, err, elapsed = data:send(str) + check_timeout(tm, sl, elapsed, err, "send", "total", + total == 2*len) +end +test_totaltimeoutsend(800091, 1, 3) +test_totaltimeoutsend(800091, 2, 3) +test_totaltimeoutsend(800091, 3, 2) +test_totaltimeoutsend(800091, 3, 1) + +------------------------------------------------------------------------ +test("blocking timeout on receive") +function test_blockingtimeoutreceive(len, tm, sl) + local str, err, total + reconnect() + pass("%d bytes, %ds blocking timeout, %ds pause", len, tm, sl) + remote (string.format ([[ + data:timeout(%d) + str = string.rep('a', %d) + data:send(str) + print('server: sleeping for %ds') + socket.sleep(%d) + print('server: woke up') + data:send(str) + ]], 2*tm, len, sl, sl)) + data:timeout(tm) + str, err, elapsed = data:receive(2*len) + check_timeout(tm, sl, elapsed, err, "receive", "blocking", + string.len(str) == 2*len) +end +test_blockingtimeoutreceive(800091, 1, 3) +test_blockingtimeoutreceive(800091, 2, 3) +test_blockingtimeoutreceive(800091, 3, 2) +test_blockingtimeoutreceive(800091, 3, 1) + +------------------------------------------------------------------------ +test("blocking timeout on send") +function test_blockingtimeoutsend(len, tm, sl) + local str, err, total + reconnect() + pass("%d bytes, %ds blocking timeout, %ds pause", len, tm, sl) + remote (string.format ([[ + data:timeout(%d) + str = data:receive(%d) + print('server: sleeping for %ds') + socket.sleep(%d) + print('server: woke up') + str = data:receive(%d) + ]], 2*tm, len, sl, sl, len)) + data:timeout(tm) + str = string.rep("a", 2*len) + total, err, elapsed = data:send(str) + check_timeout(tm, sl, elapsed, err, "send", "blocking", + total == 2*len) +end +test_blockingtimeoutsend(800091, 1, 3) +test_blockingtimeoutsend(800091, 2, 3) +test_blockingtimeoutsend(800091, 3, 2) +test_blockingtimeoutsend(800091, 3, 1) + +------------------------------------------------------------------------ +test("bugs") + +io.write("empty host connect: ") +function empty_connect() + if data then data:close() data = nil end + remote [[ + if data then data:close() data = nil end + data = server:accept() + ]] + data, err = socket.connect("", port) + if not data then + pass("ok") + data = socket.connect(host, port) + else fail("should not have connected!") end end -test_mixed(1) -test_mixed(17) -test_mixed(200) -test_mixed(4091) -test_mixed(80199) -test_mixed(800000) -test_mixed(80199) -test_mixed(4091) -test_mixed(200) -test_mixed(17) -test_mixed(1) +empty_connect() + +-- io.write("active close: ") +function active_close() + reconnect() + if socket._isclosed(data) then fail("should not be closed") end + data:close() + if not socket._isclosed(data) then fail("should be closed") end + data = nil + local udp = socket.udp() + if socket._isclosed(udp) then fail("should not be closed") end + udp:close() + if not socket._isclosed(udp) then fail("should be closed") end + pass("ok") +end + +-- active_close() ------------------------------------------------------------------------ test("closed connection detection") @@ -363,7 +439,7 @@ function test_closed() data:close() data = nil ]] - err, total = data:send(string.rep("ugauga", 100000)) + total, err = data:send(string.rep("ugauga", 100000)) if not err then pass("failed: output buffer is at least %d bytes long!", total) elseif err ~= "closed" then @@ -376,106 +452,26 @@ end test_closed() ------------------------------------------------------------------------ -test("return timeout on receive") -function test_blockingtimeoutreceive(len, tm, sl) - local str, err, total - reconnect() - pass("%d bytes, %ds return timeout, %ds pause", len, tm, sl) - remote (string.format ([[ - data:timeout(%d) - str = string.rep('a', %d) - data:send(str) - print('server: sleeping for %ds') - socket._sleep(%d) - print('server: woke up') - data:send(str) - ]], 2*tm, len, sl, sl)) - data:timeout(tm, "return") - str, err, elapsed = data:receive(2*len) - check_timeout(tm, sl, elapsed, err, "receive", "return", - string.len(str) == 2*len) +test("select function") +function test_selectbugs() + local r, s, e = socket.select(nil, nil, 0.1) + assert(type(r) == "table" and type(s) == "table" and e == "timeout") + pass("both nil: ok") + local udp = socket.udp() + udp:close() + r, s, e = socket.select({ udp }, { udp }, 0.1) + assert(type(r) == "table" and type(s) == "table" and e == "timeout") + pass("closed sockets: ok") + e = pcall(socket.select, "wrong", 1, 0.1) + assert(e == false) + e = pcall(socket.select, {}, 1, 0.1) + assert(e == false) + pass("invalid input: ok") end -test_blockingtimeoutreceive(800091, 1, 3) -test_blockingtimeoutreceive(800091, 2, 3) -test_blockingtimeoutreceive(800091, 3, 2) -test_blockingtimeoutreceive(800091, 3, 1) ------------------------------------------------------------------------- -test("return timeout on send") -function test_returntimeoutsend(len, tm, sl) - local str, err, total - reconnect() - pass("%d bytes, %ds return timeout, %ds pause", len, tm, sl) - remote (string.format ([[ - data:timeout(%d) - str = data:receive(%d) - print('server: sleeping for %ds') - socket._sleep(%d) - print('server: woke up') - str = data:receive(%d) - ]], 2*tm, len, sl, sl, len)) - data:timeout(tm, "return") - str = string.rep("a", 2*len) - err, total, elapsed = data:send(str) - check_timeout(tm, sl, elapsed, err, "send", "return", - total == 2*len) -end -test_returntimeoutsend(800091, 1, 3) -test_returntimeoutsend(800091, 2, 3) -test_returntimeoutsend(800091, 3, 2) -test_returntimeoutsend(800091, 3, 1) +-- test_selectbugs() ------------------------------------------------------------------------- -test("blocking timeout on receive") -function test_blockingtimeoutreceive(len, tm, sl) - local str, err, total - reconnect() - pass("%d bytes, %ds blocking timeout, %ds pause", len, tm, sl) - remote (string.format ([[ - data:timeout(%d) - str = string.rep('a', %d) - data:send(str) - print('server: sleeping for %ds') - socket._sleep(%d) - print('server: woke up') - data:send(str) - ]], 2*tm, len, sl, sl)) - data:timeout(tm) - str, err, elapsed = data:receive(2*len) - check_timeout(tm, sl, elapsed, err, "receive", "blocking", - string.len(str) == 2*len) -end -test_blockingtimeoutreceive(800091, 1, 3) -test_blockingtimeoutreceive(800091, 2, 3) -test_blockingtimeoutreceive(800091, 3, 2) -test_blockingtimeoutreceive(800091, 3, 1) ------------------------------------------------------------------------- -test("blocking timeout on send") -function test_blockingtimeoutsend(len, tm, sl) - local str, err, total - reconnect() - pass("%d bytes, %ds blocking timeout, %ds pause", len, tm, sl) - remote (string.format ([[ - data:timeout(%d) - str = data:receive(%d) - print('server: sleeping for %ds') - socket._sleep(%d) - print('server: woke up') - str = data:receive(%d) - ]], 2*tm, len, sl, sl, len)) - data:timeout(tm) - str = string.rep("a", 2*len) - err, total, elapsed = data:send(str) - check_timeout(tm, sl, elapsed, err, "send", "blocking", - total == 2*len) -end -test_blockingtimeoutsend(800091, 1, 3) -test_blockingtimeoutsend(800091, 2, 3) -test_blockingtimeoutsend(800091, 3, 2) -test_blockingtimeoutsend(800091, 3, 1) - ------------------------------------------------------------------------- -test(string.format("done in %.2fs", socket._time() - start)) +test(string.format("done in %.2fs", socket.time() - start)) diff --git a/test/testsrvr.lua b/test/testsrvr.lua index fb77ea5..3c40840 100644 --- a/test/testsrvr.lua +++ b/test/testsrvr.lua @@ -13,12 +13,13 @@ while 1 do print("server: closing connection...") break end - error = control:send("\n") + sent, error = control:send("\n") if error then control:close() print("server: closing connection...") break end + print(command); (loadstring(command))() end end diff --git a/test/tftptest.lua b/test/tftptest.lua index a435ad4..a478ed8 100644 --- a/test/tftptest.lua +++ b/test/tftptest.lua @@ -1,5 +1,5 @@ -- load tftpclnt.lua -dofile("tftpclnt.lua") +dofile("tftp.lua") -- needs tftp server running on localhost, with root pointing to -- a directory with index.html in it @@ -13,11 +13,8 @@ function readfile(file) end host = host or "localhost" -print("downloading") -err = tftp_get(host, 69, "index.html", "index.got") +retrieved, err = socket.tftp.get("tftp://" .. host .."/index.html") assert(not err, err) original = readfile("test/index.html") -retrieved = readfile("index.got") -os.remove("index.got") assert(original == retrieved, "files differ!") print("passed") From b2724ad2d1cc3768a04270ed3f8014ec65ad133b Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Sun, 25 May 2003 01:56:46 +0000 Subject: [PATCH 122/483] New sock.h has nothing to do with class sock... --- src/socket.h | 61 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/src/socket.h b/src/socket.h index 9972639..f8c5d8f 100644 --- a/src/socket.h +++ b/src/socket.h @@ -1,24 +1,57 @@ /*=========================================================================*\ -* Socket class: inherits from the File Descriptor class and is here just -* for extensibility in the future +* Socket compatibilization module * -* RCS ID: $id$ +* RCS ID: $Id$ \*=========================================================================*/ -#ifndef SOCK_H_ -#define SOCK_H_ +#ifndef SOCK_H +#define SOCK_H #include -#include "lsfd.h" +#include "error.h" -#define SOCK_CLASS "luasocket(sock)" +/*=========================================================================*\ +* Platform specific compatibilization +\*=========================================================================*/ +#ifdef WIN32 +#include "sockwin32.h" +#else +#include "sockunix.h" +#endif -#define SOCK_FIELDS FD_FIELDS +/* we are lazy... */ +typedef struct sockaddr SA; -typedef t_fd t_sock; -typedef t_sock *p_sock; +/*=========================================================================*\ +* Functions bellow implement a comfortable platform independent +* interface to sockets +\*=========================================================================*/ +int sock_open(lua_State *L); -void sock_open(lua_State *L); -void sock_construct(lua_State *L, p_sock sock); -void sock_inherit(lua_State *L, cchar *lsclass); +const char *sock_create(p_sock ps, int domain, int type, int protocol); +void sock_destroy(p_sock ps); +void sock_accept(p_sock ps, p_sock pa, SA *addr, size_t *addr_len, int timeout); +const char *sock_connect(p_sock ps, SA *addr, size_t addr_len); +const char *sock_bind(p_sock ps, SA *addr, size_t addr_len); +void sock_listen(p_sock ps, int backlog); -#endif /* SOCK_H_ */ +int sock_send(p_sock ps, const char *data, size_t count, + size_t *sent, int timeout); +int sock_recv(p_sock ps, char *data, size_t count, + size_t *got, int timeout); +int sock_sendto(p_sock ps, const char *data, size_t count, + size_t *sent, SA *addr, size_t addr_len, int timeout); +int sock_recvfrom(p_sock ps, char *data, size_t count, + size_t *got, SA *addr, size_t *addr_len, int timeout); + +void sock_setnonblocking(p_sock ps); +void sock_setblocking(p_sock ps); +void sock_setreuseaddr(p_sock ps); + +const char *sock_hoststrerror(void); +const char *sock_createstrerror(void); +const char *sock_bindstrerror(void); +const char *sock_connectstrerror(void); + +const char *sock_trysetoptions(lua_State *L, p_sock ps); + +#endif /* SOCK_H */ From 58bdb658aaa1c30a8f3bed46eef880d308fae582 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 9 Jun 2003 18:23:40 +0000 Subject: [PATCH 123/483] Select re-implemented in a nicer way. Few changes in internal class and group registration. Lua modules are compiled and built into library. Dynamic library tested in Linux and Mac OS X. --- etc/check-links.lua | 2 + etc/dict.lua | 2 + etc/get.lua | 2 + makefile.dist | 79 +++++++++++++++-- samples/daytimeclnt.lua | 6 ++ samples/echoclnt.lua | 6 ++ samples/echosrvr.lua | 6 ++ samples/listener.lua | 4 +- samples/talker.lua | 6 ++ samples/tinyirc.lua | 90 ++++++++++--------- src/auxiliar.c | 77 +++++++++++----- src/auxiliar.h | 2 + src/buffer.c | 4 +- src/buffer.h | 2 +- src/inet.c | 5 +- src/inet.h | 2 +- src/io.h | 12 ++- src/luasocket.c | 31 +++++-- src/select.c | 191 +++++++++++++++++----------------------- src/select.h | 7 +- src/socket.h | 23 +++-- src/tcp.c | 97 ++++++++++++-------- src/tcp.h | 6 +- src/timeout.c | 5 +- src/udp.c | 98 +++++++++++++-------- src/udp.h | 4 +- src/usocket.c | 119 ++++--------------------- src/usocket.h | 5 ++ 28 files changed, 496 insertions(+), 397 deletions(-) diff --git a/etc/check-links.lua b/etc/check-links.lua index 705c0ce..4c96fdc 100644 --- a/etc/check-links.lua +++ b/etc/check-links.lua @@ -1,6 +1,8 @@ ----------------------------------------------------------------------------- -- Little program that checks links in HTML files -- LuaSocket 1.5 sample files. +-- Author: Diego Nehab +-- RCS ID: $Id$ ----------------------------------------------------------------------------- socket.http.TIMEOUT = 10 diff --git a/etc/dict.lua b/etc/dict.lua index 6790cab..89bdb4f 100644 --- a/etc/dict.lua +++ b/etc/dict.lua @@ -1,6 +1,8 @@ ----------------------------------------------------------------------------- -- Little program to download DICT word definitions -- LuaSocket 1.5 sample files +-- Author: Diego Nehab +-- RCS ID: $Id$ ----------------------------------------------------------------------------- function get_status(sock, valid) local line, err = sock:receive() diff --git a/etc/get.lua b/etc/get.lua index af46c16..e972d16 100644 --- a/etc/get.lua +++ b/etc/get.lua @@ -1,6 +1,8 @@ ----------------------------------------------------------------------------- -- Little program to download files from URLs -- LuaSocket 1.5 sample files +-- Author: Diego Nehab +-- RCS ID: $Id$ ----------------------------------------------------------------------------- -- formats a number of seconds into human readable form function nicetime(s) diff --git a/makefile.dist b/makefile.dist index 90ef7c2..e493305 100644 --- a/makefile.dist +++ b/makefile.dist @@ -2,22 +2,81 @@ # Distribution makefile #-------------------------------------------------------------------------- -DIST = luasocket-1.5-work +DIST = luasocket-1.5-alpha -LUA = concat.lua code.lua url.lua http.lua smtp.lua ftp.lua lsselect.lua \ - cl-compat.lua +LUA = \ + concat.lua \ + code.lua \ + url.lua \ + http.lua \ + smtp.lua \ + ftp.lua \ + select.lua \ + luasocket.lua -TESTS = testclnt.lua testsrvr.lua testcmd.lua codetest.lua \ - urltest.lua concattest.lua +TESTS = \ + testclnt.lua \ + testsrvr.lua \ + testcmd.lua \ + codetest.lua \ + urltest.lua \ + concattest.lua \ + ftptest.lua \ + httptest.lua \ + smtptest.lua \ + mbox.lua \ + udptest.lua -EXAMPLES = check-links.lua daytimeclnt.lua dict.lua echoclnt.lua \ - echosrvr.lua get.lua listener.lua talker.lua tinyirc.lua tftpclnt.lua +EXAMPLES = \ + check-links.lua \ + daytimeclnt.lua \ + echoclnt.lua \ + echosrvr.lua \ + get.lua \ + listener.lua \ + talker.lua \ + tinyirc.lua + +ETC = \ + cl-compat.lua \ + tftp.lua \ + dict.lua + +MAIN = \ + auxiliar.c \ + auxiliar.h \ + buffer.c \ + buffer.h \ + error.c \ + error.h \ + inet.c \ + inet.h \ + io.c \ + io.h \ + lua.c \ + luasocket.c \ + luasocket.h \ + makefile \ + select.c \ + select.h \ + socket.h \ + tcp.c \ + tcp.h \ + timeout.c \ + timeout.h \ + udp.c \ + udp.h \ + usocket.c \ + usocket.h \ + wsocket.c \ + wsocket.h \ dist: mkdir -p $(DIST)/examples mkdir -p $(DIST)/tests - cp -vf *.c $(DIST) - cp -vf *.h $(DIST) + mkdir -p $(DIST)/etc + mkdir -p $(DIST)/lua + cp -vf $(MAIN) $(DIST) cp -vf $(LUA) $(DIST) cp -vf makefile $(DIST) cp -vf README $(DIST) @@ -25,6 +84,8 @@ dist: cp -vf README.examples $(DIST)/examples/README cp -vf $(TESTS) $(DIST)/tests cp -vf README.tests $(DIST)/tests/README + cp -vf $(ETC) $(DIST)/etc + cp -vf README.etc $(DIST)/etc/README tar -zcvf $(DIST).tar.gz $(DIST) zip -r $(DIST).zip $(DIST) diff --git a/samples/daytimeclnt.lua b/samples/daytimeclnt.lua index 4debc81..85ddca1 100644 --- a/samples/daytimeclnt.lua +++ b/samples/daytimeclnt.lua @@ -1,3 +1,9 @@ +----------------------------------------------------------------------------- +-- UDP sample: daytime protocol client +-- LuaSocket 1.5 sample files. +-- Author: Diego Nehab +-- RCS ID: $Id$ +----------------------------------------------------------------------------- host = host or "127.0.0.1" port = port or 13 if arg then diff --git a/samples/echoclnt.lua b/samples/echoclnt.lua index cd8b450..bca0b4d 100644 --- a/samples/echoclnt.lua +++ b/samples/echoclnt.lua @@ -1,3 +1,9 @@ +----------------------------------------------------------------------------- +-- UDP sample: echo protocol client +-- LuaSocket 1.5 sample files +-- Author: Diego Nehab +-- RCS ID: $Id$ +----------------------------------------------------------------------------- host = host or "localhost" port = port or 7 if arg then diff --git a/samples/echosrvr.lua b/samples/echosrvr.lua index 6117557..18bd84e 100644 --- a/samples/echosrvr.lua +++ b/samples/echosrvr.lua @@ -1,3 +1,9 @@ +----------------------------------------------------------------------------- +-- UDP sample: echo protocol server +-- LuaSocket 1.5 sample files +-- Author: Diego Nehab +-- RCS ID: $Id$ +----------------------------------------------------------------------------- host = host or "127.0.0.1" port = port or 7 if arg then diff --git a/samples/listener.lua b/samples/listener.lua index c035ab2..4846419 100644 --- a/samples/listener.lua +++ b/samples/listener.lua @@ -1,6 +1,8 @@ ----------------------------------------------------------------------------- --- Little program to dump lines received at a given port +-- TCP sample: Little program to dump lines received at a given port -- LuaSocket 1.5 sample files +-- Author: Diego Nehab +-- RCS ID: $Id$ ----------------------------------------------------------------------------- host = host or "*" port = port or 8080 diff --git a/samples/talker.lua b/samples/talker.lua index 688824f..c7a239a 100644 --- a/samples/talker.lua +++ b/samples/talker.lua @@ -1,3 +1,9 @@ +----------------------------------------------------------------------------- +-- TCP sample: Little program to send text lines to a given host/port +-- LuaSocket 1.5 sample files +-- Author: Diego Nehab +-- RCS ID: $Id$ +----------------------------------------------------------------------------- host = host or "localhost" port = port or 8080 if arg then diff --git a/samples/tinyirc.lua b/samples/tinyirc.lua index d3e56e7..0b20303 100644 --- a/samples/tinyirc.lua +++ b/samples/tinyirc.lua @@ -1,16 +1,9 @@ -function set_add(set, sock) - table.insert(set, sock) -end - -function set_remove(set, sock) - for i = 1, table.getn(set) do - if set[i] == sock then - table.remove(set, i) - break - end - end -end - +----------------------------------------------------------------------------- +-- Select sample: simple text line server +-- LuaSocket 1.5 sample files. +-- Author: Diego Nehab +-- RCS ID: $Id$ +----------------------------------------------------------------------------- host = host or "*" port1 = port1 or 8080 port2 = port2 or 8081 @@ -21,49 +14,62 @@ if arg then end server1, error = socket.bind(host, port1) -if not server1 then print(error) exit() end +assert(server1, error) server1:timeout(1) server2, error = socket.bind(host, port2) -if not server2 then print(error) exit() end +assert(server2, error) server2:timeout(1) -sock_set = {server1, server2} +function newset() + local reverse = {} + local set = {} + setmetatable(set, { __index = { + insert = function(set, value) + table.insert(set, value) + reverse[value] = table.getn(set) + end, + remove = function(set, value) + table.remove(set, reverse[value]) + reverse[value] = nil + end, + id = function(set, value) + return reverse[value] + end + }}) + return set +end -sock_id = {} -sock_id[server1] = 1 -sock_id[server2] = 2 -next_id = 3 +sockets = newset() + +sockets:insert(server1) +sockets:insert(server2) while 1 do - local readable, _, error = socket.select(sock_set, nil) - for _, sock in readable do - -- is it a server socket - if sock_id[sock] < 3 then - local incomming = sock:accept() - if incomming then - incomming:timeout(1) - sock_id[incomming] = next_id - set_add(sock_set, incomming) - io.write("Added client id ", next_id, ". ", - table.getn(sock_set)-2, " total.\n") - next_id = next_id + 1 + local readable, _, error = socket.select(sockets, nil) + for _, input in readable do + -- is it a server socket? + local id = sockets:id(input) + if input == server1 or input == server2 then + local new = input:accept() + if new then + new:timeout(1) + sockets:insert(new) + io.write("Server ", id, " got client ", sockets:id(new), "\n") end -- it is a client socket else - local line, error = sock:receive() - local id = sock_id[sock] + local line, error = input:receive() if error then - sock:close() - set_remove(sock_set, sock) - io.write("Removed client number ", id, ". ", - getn(sock_set)-2, " total.\n") + input:close() + io.write("Removing client ", id, "\n") + sockets:remove(input) else io.write("Broadcasting line '", id, "> ", line, "'.\n") - __, writable, error = socket.select(nil, sock_set, 1) + __, writable, error = socket.select(nil, sockets, 1) if not error then - for ___, outgoing in writable do - io.write("Sending to client ", sock_id[outgoing], "\n") - outgoing:send(id, "> ", line, "\r\n") + for ___, output in writable do + io.write("Sending to client ", sockets:id(output), "\n") + output:send(id, "> ", line, "\r\n") end else io.write("No one ready to listen!!!\n") end end diff --git a/src/auxiliar.c b/src/auxiliar.c index 5e5ba1a..96138f1 100644 --- a/src/auxiliar.c +++ b/src/auxiliar.c @@ -3,12 +3,7 @@ * * RCS ID: $Id$ \*=========================================================================*/ -#include "aux.h" - -/*=========================================================================*\ -* Internal function prototypes -\*=========================================================================*/ -static void *aux_getgroupudata(lua_State *L, const char *group, int objidx); +#include "auxiliar.h" /*=========================================================================*\ * Exported functions @@ -20,18 +15,19 @@ static void *aux_getgroupudata(lua_State *L, const char *group, int objidx); \*-------------------------------------------------------------------------*/ void aux_newclass(lua_State *L, const char *name, luaL_reg *func) { - luaL_newmetatable(L, name); + lua_pushstring(L, name); + lua_newtable(L); lua_pushstring(L, "__index"); lua_newtable(L); luaL_openlib(L, NULL, func, 0); lua_pushstring(L, "class"); lua_pushstring(L, name); - lua_settable(L, -3); - lua_settable(L, -3); + lua_rawset(L, -3); lua_pushstring(L, "group"); lua_newtable(L); - lua_settable(L, -3); - lua_pop(L, 1); + lua_rawset(L, -3); + lua_rawset(L, -3); + lua_rawset(L, LUA_REGISTRYINDEX); } /*-------------------------------------------------------------------------*\ @@ -39,13 +35,16 @@ void aux_newclass(lua_State *L, const char *name, luaL_reg *func) \*-------------------------------------------------------------------------*/ void aux_add2group(lua_State *L, const char *name, const char *group) { - luaL_getmetatable(L, name); + lua_pushstring(L, name); + lua_rawget(L, LUA_REGISTRYINDEX); + lua_pushstring(L, "__index"); + lua_rawget(L, -2); lua_pushstring(L, "group"); - lua_gettable(L, -2); + lua_rawget(L, -2); lua_pushstring(L, group); lua_pushnumber(L, 1); - lua_settable(L, -3); - lua_pop(L, 2); + lua_rawset(L, -3); + lua_pop(L, 3); } /*-------------------------------------------------------------------------*\ @@ -53,7 +52,7 @@ void aux_add2group(lua_State *L, const char *name, const char *group) \*-------------------------------------------------------------------------*/ void *aux_checkclass(lua_State *L, const char *name, int objidx) { - void *data = luaL_checkudata(L, objidx, name); + void *data = aux_getclassudata(L, name, objidx); if (!data) { char msg[45]; sprintf(msg, "%.35s expected", name); @@ -81,7 +80,8 @@ void *aux_checkgroup(lua_State *L, const char *group, int objidx) \*-------------------------------------------------------------------------*/ void aux_setclass(lua_State *L, const char *name, int objidx) { - luaL_getmetatable(L, name); + lua_pushstring(L, name); + lua_rawget(L, LUA_REGISTRYINDEX); if (objidx < 0) objidx--; lua_setmetatable(L, objidx); } @@ -92,17 +92,47 @@ void aux_setclass(lua_State *L, const char *name, int objidx) /*-------------------------------------------------------------------------*\ * Get a userdata if object belongs to a given group. \*-------------------------------------------------------------------------*/ -static void *aux_getgroupudata(lua_State *L, const char *group, int objidx) +void *aux_getgroupudata(lua_State *L, const char *group, int objidx) { - if (!lua_getmetatable(L, objidx)) return NULL; - lua_pushstring(L, "group"); - lua_gettable(L, -2); - if (lua_isnil(L, -1)) { + if (!lua_getmetatable(L, objidx)) + return NULL; + lua_pushstring(L, "__index"); + lua_rawget(L, -2); + if (!lua_istable(L, -1)) { lua_pop(L, 2); return NULL; } + lua_pushstring(L, "group"); + lua_rawget(L, -2); + if (!lua_istable(L, -1)) { + lua_pop(L, 3); + return NULL; + } lua_pushstring(L, group); - lua_gettable(L, -2); + lua_rawget(L, -2); + if (lua_isnil(L, -1)) { + lua_pop(L, 4); + return NULL; + } + lua_pop(L, 4); + return lua_touserdata(L, objidx); +} + +/*-------------------------------------------------------------------------*\ +* Get a userdata if object belongs to a given class. +\*-------------------------------------------------------------------------*/ +void *aux_getclassudata(lua_State *L, const char *group, int objidx) +{ + if (!lua_getmetatable(L, objidx)) + return NULL; + lua_pushstring(L, "__index"); + lua_rawget(L, -2); + if (!lua_istable(L, -1)) { + lua_pop(L, 2); + return NULL; + } + lua_pushstring(L, "class"); + lua_rawget(L, -2); if (lua_isnil(L, -1)) { lua_pop(L, 3); return NULL; @@ -110,4 +140,3 @@ static void *aux_getgroupudata(lua_State *L, const char *group, int objidx) lua_pop(L, 3); return lua_touserdata(L, objidx); } - diff --git a/src/auxiliar.h b/src/auxiliar.h index 2681a84..66be31d 100644 --- a/src/auxiliar.h +++ b/src/auxiliar.h @@ -13,6 +13,8 @@ void aux_newclass(lua_State *L, const char *name, luaL_reg *func); void aux_add2group(lua_State *L, const char *name, const char *group); void *aux_checkclass(lua_State *L, const char *name, int objidx); void *aux_checkgroup(lua_State *L, const char *group, int objidx); +void *aux_getclassudata(lua_State *L, const char *group, int objidx); +void *aux_getgroupudata(lua_State *L, const char *group, int objidx); void aux_setclass(lua_State *L, const char *name, int objidx); /* min and max macros */ diff --git a/src/buffer.c b/src/buffer.c index c5ef66c..ab059bb 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -7,8 +7,8 @@ #include #include "error.h" -#include "aux.h" -#include "buf.h" +#include "auxiliar.h" +#include "buffer.h" /*=========================================================================*\ * Internal function prototypes diff --git a/src/buffer.h b/src/buffer.h index 3ffc145..1502ef0 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -8,7 +8,7 @@ #include #include "io.h" -#include "tm.h" +#include "timeout.h" /* buffer size in bytes */ #define BUF_SIZE 8192 diff --git a/src/inet.c b/src/inet.c index f20762f..60106f2 100644 --- a/src/inet.c +++ b/src/inet.c @@ -38,6 +38,7 @@ static luaL_reg func[] = { void inet_open(lua_State *L) { luaL_openlib(L, LUASOCKET_LIBNAME, func, 0); + lua_pop(L, 1); } /*=========================================================================*\ @@ -114,7 +115,7 @@ static int inet_global_tohostname(lua_State *L) int inet_meth_getpeername(lua_State *L, p_sock ps) { struct sockaddr_in peer; - size_t peer_len = sizeof(peer); + socklen_t peer_len = sizeof(peer); if (getpeername(*ps, (SA *) &peer, &peer_len) < 0) { lua_pushnil(L); return 1; @@ -135,7 +136,7 @@ int inet_meth_getpeername(lua_State *L, p_sock ps) int inet_meth_getsockname(lua_State *L, p_sock ps) { struct sockaddr_in local; - size_t local_len = sizeof(local); + socklen_t local_len = sizeof(local); if (getsockname(*ps, (SA *) &local, &local_len) < 0) { lua_pushnil(L); return 1; diff --git a/src/inet.h b/src/inet.h index bcefc5b..b8d8f19 100644 --- a/src/inet.h +++ b/src/inet.h @@ -7,7 +7,7 @@ #define INET_H #include -#include "sock.h" +#include "socket.h" /*-------------------------------------------------------------------------*\ * Exported functions diff --git a/src/io.h b/src/io.h index b5b7f1d..30d445b 100644 --- a/src/io.h +++ b/src/io.h @@ -1,7 +1,17 @@ #ifndef IO_H #define IO_H -#include "error.h" +#include + +/* IO error codes */ +enum { + IO_DONE, /* operation completed successfully */ + IO_TIMEOUT, /* operation timed out */ + IO_CLOSED, /* the connection has been closed */ + IO_ERROR, /* something wrong... */ + IO_REFUSED, /* transfer has been refused */ + IO_LIMITED /* maximum number of bytes reached */ +}; /* interface to send function */ typedef int (*p_send) ( diff --git a/src/luasocket.c b/src/luasocket.c index 53f8c21..5541d7f 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -24,12 +24,13 @@ \*=========================================================================*/ #include "luasocket.h" -#include "tm.h" -#include "buf.h" -#include "sock.h" +#include "timeout.h" +#include "buffer.h" +#include "socket.h" #include "inet.h" #include "tcp.h" #include "udp.h" +#include "select.h" /*=========================================================================*\ * Exported functions @@ -39,6 +40,7 @@ \*-------------------------------------------------------------------------*/ LUASOCKET_API int luaopen_socketlib(lua_State *L) { + if (!sock_open()) return 0; /* create namespace table */ lua_pushstring(L, LUASOCKET_LIBNAME); lua_newtable(L); @@ -53,13 +55,28 @@ LUASOCKET_API int luaopen_socketlib(lua_State *L) lua_pushstring(L, LUASOCKET_LIBNAME); lua_settable(L, LUA_GLOBALSINDEX); /* initialize all modules */ - sock_open(L); tm_open(L); buf_open(L); inet_open(L); tcp_open(L); udp_open(L); - /* load all Lua code */ - lua_dofile(L, "luasocket.lua"); - return 0; + select_open(L); +#ifdef LUASOCKET_COMPILED +#include "auxiliar.lch" +#include "concat.lch" +#include "code.lch" +#include "url.lch" +#include "smtp.lch" +#include "ftp.lch" +#include "http.lch" +#else + lua_dofile(L, "auxiliar.lua"); + lua_dofile(L, "concat.lua"); + lua_dofile(L, "code.lua"); + lua_dofile(L, "url.lua"); + lua_dofile(L, "smtp.lua"); + lua_dofile(L, "ftp.lua"); + lua_dofile(L, "http.lua"); +#endif + return 1; } diff --git a/src/select.c b/src/select.c index 9f56b47..3cabbd1 100644 --- a/src/select.c +++ b/src/select.c @@ -1,154 +1,129 @@ /*=========================================================================*\ * Select implementation -* Global Lua fuctions: -* select: waits until socket ready * RCS ID: $Id$ \*=========================================================================*/ #include + #include #include #include "luasocket.h" -#include "lspriv.h" -#include "lsselect.h" -#include "lsfd.h" +#include "socket.h" +#include "auxiliar.h" +#include "select.h" -/* auxiliar functions */ -static int local_select(lua_State *L); -static int local_getfd(lua_State *L); -static int local_pending(lua_State *L); -static int local_FD_SET(lua_State *L); -static int local_FD_ISSET(lua_State *L); +static int meth_set(lua_State *L); +static int meth_isset(lua_State *L); +static int c_select(lua_State *L); +static int global_select(lua_State *L); +static void check_obj_tab(lua_State *L, int tabidx); -static int select_lua_select(lua_State *L); +/* fd_set object methods */ +static luaL_reg set[] = { + {"set", meth_set}, + {"isset", meth_isset}, + {NULL, NULL} +}; -/*-------------------------------------------------------------------------*\ -* Marks type as selectable -* Input -* name: type name -\*-------------------------------------------------------------------------*/ -void select_addclass(lua_State *L, cchar *lsclass) -{ - lua_pushstring(L, "luasocket(select)"); - lua_gettable(L, LUA_REGISTRYINDEX); - lua_pushstring(L, lsclass); - lua_pushnumber(L, 1); - lua_settable(L, -3); - lua_pop(L, 1); -} +/* functions in library namespace */ +static luaL_reg func[] = { + {"select", global_select}, + {NULL, NULL} +}; void select_open(lua_State *L) { - /* push select auxiliar lua function and register - * select_lua_select with it as an upvalue */ -#ifdef LUASOCKET_DOFILE - lua_dofile(L, "lsselect.lua"); + /* 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 -#include "lsselect.loh" + lua_dofile(L, "select.lua"); #endif - lua_getglobal(L, LUASOCKET_LIBNAME); - lua_pushstring(L, "_select"); - lua_gettable(L, -2); - lua_pushcclosure(L, select_lua_select, 1); - priv_newglobal(L, "select"); + luaL_openlib(L, LUASOCKET_LIBNAME, func, 1); lua_pop(L, 1); - /* create luasocket(select) table */ - lua_pushstring(L, "luasocket(select)"); - lua_newtable(L); - lua_settable(L, LUA_REGISTRYINDEX); + aux_newclass(L, "select{fd_set}", set); } /*-------------------------------------------------------------------------*\ * Waits for a set of sockets until a condition is met or timeout. -* Lua Input: {input}, {output} [, timeout] -* {input}: table of sockets to be tested for input -* {output}: table of sockets to be tested for output -* timeout: maximum amount of time to wait for condition, in seconds -* Lua Returns: {input}, {output}, err -* {input}: table with sockets ready for input -* {output}: table with sockets ready for output -* err: "timeout" or nil \*-------------------------------------------------------------------------*/ -static int select_lua_select(lua_State *L) +static int global_select(lua_State *L) { - fd_set read, write; - FD_ZERO(&read); - FD_ZERO(&write); - /* push select lua auxiliar function */ - lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, 1); + fd_set *read_fd_set, *write_fd_set; /* make sure we have enough arguments (nil is the default) */ - lua_settop(L, 4); - /* pass FD_SET and manipulation functions */ - lua_boxpointer(L, &read); - lua_boxpointer(L, &write); - lua_pushcfunction(L, local_FD_SET); - lua_pushcfunction(L, local_FD_ISSET); - /* pass getfd function with selectable table as upvalue */ - lua_pushstring(L, "luasocket(select)"); lua_gettable(L, LUA_REGISTRYINDEX); - lua_pushcclosure(L, local_getfd, 1); - /* pass pending function */ - lua_pushstring(L, "luasocket(select)"); lua_gettable(L, LUA_REGISTRYINDEX); - lua_pushcclosure(L, local_pending, 1); + lua_settop(L, 3); + /* check object tables */ + check_obj_tab(L, 1); + check_obj_tab(L, 2); + /* 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 = lua_newuserdata(L, sizeof(fd_set)); + FD_ZERO(read_fd_set); + aux_setclass(L, "select{fd_set}", -1); + write_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, local_select); + lua_pushcfunction(L, c_select); /* call select auxiliar lua function */ - lua_call(L, 10, 3); + lua_call(L, 6, 3); return 3; } -static int local_getfd(lua_State *L) -{ - priv_pushclass(L, 1); - lua_gettable(L, lua_upvalueindex(1)); - if (!lua_isnil(L, -1)) { - p_fd sock = (p_fd) lua_touserdata(L, 1); - lua_pushnumber(L, sock->fd); - } - return 1; -} - -static int local_pending(lua_State *L) -{ - priv_pushclass(L, 1); - lua_gettable(L, lua_upvalueindex(1)); - if (!lua_isnil(L, -1)) { - p_fd sock = (p_fd) lua_touserdata(L, 1); - if (sock->fd_pending(L, sock)) lua_pushnumber(L, 1); - else lua_pushnil(L); - } - return 1; -} - -static int local_select(lua_State *L) +static int c_select(lua_State *L) { int max_fd = (int) lua_tonumber(L, 1); - fd_set *read_set = (fd_set *) lua_touserdata(L, 2); - fd_set *write_set = (fd_set *) lua_touserdata(L, 3); - int deadline = lua_isnil(L, 4) ? -1 : (int)(lua_tonumber(L, 4) * 1000); + 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; - if (deadline >= 0) { - tv.tv_sec = deadline / 1000; - tv.tv_usec = (deadline % 1000) * 1000; - lua_pushnumber(L, select(max_fd, read_set, write_set, NULL, &tv)); - } else { - lua_pushnumber(L, select(max_fd, read_set, write_set, NULL, NULL)); - } + 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 local_FD_SET(lua_State *L) +static int meth_set(lua_State *L) { - COMPAT_FD fd = (COMPAT_FD) lua_tonumber(L, 1); - fd_set *set = (fd_set *) lua_topointer(L, 2); + 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 local_FD_ISSET(lua_State *L) +static int meth_isset(lua_State *L) { - COMPAT_FD fd = (COMPAT_FD) lua_tonumber(L, 1); - fd_set *set = (fd_set *) lua_topointer(L, 2); + 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; } + +static void check_obj_tab(lua_State *L, int tabidx) +{ + if (tabidx < 0) tabidx = lua_gettop(L) + tabidx + 1; + if (lua_istable(L, tabidx)) { + lua_pushnil(L); + while (lua_next(L, tabidx) != 0) { + if (aux_getgroupudata(L, "select{able}", -1) == NULL) { + char msg[45]; + if (lua_isnumber(L, -2)) + sprintf(msg, "table entry #%g is invalid", + lua_tonumber(L, -2)); + else + sprintf(msg, "invalid entry found in table"); + luaL_argerror(L, tabidx, msg); + } + lua_pop(L, 1); + } + } else if (!lua_isnil(L, tabidx)) + luaL_argerror(L, tabidx, "table or nil expected"); +} diff --git a/src/select.h b/src/select.h index 2b2ed19..9521fae 100644 --- a/src/select.h +++ b/src/select.h @@ -2,10 +2,9 @@ * Select implementation * RCS ID: $Id$ \*=========================================================================*/ -#ifndef SLCT_H_ -#define SLCT_H_ +#ifndef SELECT_H +#define SELECT_H -void select_addclass(lua_State *L, cchar *lsclass); void select_open(lua_State *L); -#endif +#endif /* SELECT_H */ diff --git a/src/socket.h b/src/socket.h index f8c5d8f..70ebc52 100644 --- a/src/socket.h +++ b/src/socket.h @@ -6,16 +6,15 @@ #ifndef SOCK_H #define SOCK_H -#include -#include "error.h" +#include "io.h" /*=========================================================================*\ * Platform specific compatibilization \*=========================================================================*/ #ifdef WIN32 -#include "sockwin32.h" +#include "wsocket.h" #else -#include "sockunix.h" +#include "usocket.h" #endif /* we are lazy... */ @@ -25,13 +24,13 @@ typedef struct sockaddr SA; * Functions bellow implement a comfortable platform independent * interface to sockets \*=========================================================================*/ -int sock_open(lua_State *L); - +int sock_open(void); const char *sock_create(p_sock ps, int domain, int type, int protocol); void sock_destroy(p_sock ps); -void sock_accept(p_sock ps, p_sock pa, SA *addr, size_t *addr_len, int timeout); -const char *sock_connect(p_sock ps, SA *addr, size_t addr_len); -const char *sock_bind(p_sock ps, SA *addr, size_t addr_len); +int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len, + int timeout); +const char *sock_connect(p_sock ps, SA *addr, socklen_t addr_len); +const char *sock_bind(p_sock ps, SA *addr, socklen_t addr_len); void sock_listen(p_sock ps, int backlog); int sock_send(p_sock ps, const char *data, size_t count, @@ -39,9 +38,9 @@ int sock_send(p_sock ps, const char *data, size_t count, int sock_recv(p_sock ps, char *data, size_t count, size_t *got, int timeout); int sock_sendto(p_sock ps, const char *data, size_t count, - size_t *sent, SA *addr, size_t addr_len, int timeout); + size_t *sent, SA *addr, socklen_t addr_len, int timeout); int sock_recvfrom(p_sock ps, char *data, size_t count, - size_t *got, SA *addr, size_t *addr_len, int timeout); + size_t *got, SA *addr, socklen_t *addr_len, int timeout); void sock_setnonblocking(p_sock ps); void sock_setblocking(p_sock ps); @@ -52,6 +51,4 @@ const char *sock_createstrerror(void); const char *sock_bindstrerror(void); const char *sock_connectstrerror(void); -const char *sock_trysetoptions(lua_State *L, p_sock ps); - #endif /* SOCK_H */ diff --git a/src/tcp.c b/src/tcp.c index db6a38e..74857c9 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -10,43 +10,49 @@ #include "luasocket.h" -#include "aux.h" +#include "auxiliar.h" +#include "socket.h" #include "inet.h" +#include "error.h" #include "tcp.h" /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ -static int tcp_global_create(lua_State *L); -static int tcp_meth_connect(lua_State *L); -static int tcp_meth_bind(lua_State *L); -static int tcp_meth_send(lua_State *L); -static int tcp_meth_getsockname(lua_State *L); -static int tcp_meth_getpeername(lua_State *L); -static int tcp_meth_receive(lua_State *L); -static int tcp_meth_accept(lua_State *L); -static int tcp_meth_close(lua_State *L); -static int tcp_meth_timeout(lua_State *L); +static int global_create(lua_State *L); +static int meth_connect(lua_State *L); +static int meth_bind(lua_State *L); +static int meth_send(lua_State *L); +static int meth_getsockname(lua_State *L); +static int meth_getpeername(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_accept(lua_State *L); +static int meth_close(lua_State *L); +static int meth_timeout(lua_State *L); +static int meth_fd(lua_State *L); +static int meth_dirty(lua_State *L); /* tcp object methods */ static luaL_reg tcp[] = { - {"connect", tcp_meth_connect}, - {"send", tcp_meth_send}, - {"receive", tcp_meth_receive}, - {"bind", tcp_meth_bind}, - {"accept", tcp_meth_accept}, - {"setpeername", tcp_meth_connect}, - {"setsockname", tcp_meth_bind}, - {"getpeername", tcp_meth_getpeername}, - {"getsockname", tcp_meth_getsockname}, - {"timeout", tcp_meth_timeout}, - {"close", tcp_meth_close}, + {"connect", meth_connect}, + {"send", meth_send}, + {"receive", meth_receive}, + {"bind", meth_bind}, + {"accept", meth_accept}, + {"setpeername", meth_connect}, + {"setsockname", meth_bind}, + {"getpeername", meth_getpeername}, + {"getsockname", meth_getsockname}, + {"timeout", meth_timeout}, + {"close", meth_close}, + {"fd", meth_fd}, + {"dirty", meth_dirty}, {NULL, NULL} }; /* functions in library namespace */ static luaL_reg func[] = { - {"tcp", tcp_global_create}, + {"tcp", global_create}, {NULL, NULL} }; @@ -60,11 +66,13 @@ void tcp_open(lua_State *L) aux_newclass(L, "tcp{client}", tcp); aux_newclass(L, "tcp{server}", tcp); /* create class groups */ - aux_add2group(L, "tcp{client}", "tcp{client, server}"); - aux_add2group(L, "tcp{server}", "tcp{client, server}"); aux_add2group(L, "tcp{master}", "tcp{any}"); aux_add2group(L, "tcp{client}", "tcp{any}"); aux_add2group(L, "tcp{server}", "tcp{any}"); + aux_add2group(L, "tcp{client}", "tcp{client, server}"); + aux_add2group(L, "tcp{server}", "tcp{client, server}"); + aux_add2group(L, "tcp{client}", "select{able}"); + aux_add2group(L, "tcp{server}", "select{able}"); /* define library functions */ luaL_openlib(L, LUASOCKET_LIBNAME, func, 0); lua_pop(L, 1); @@ -76,28 +84,45 @@ void tcp_open(lua_State *L) /*-------------------------------------------------------------------------*\ * Just call buffered IO methods \*-------------------------------------------------------------------------*/ -static int tcp_meth_send(lua_State *L) +static int meth_send(lua_State *L) { p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{client}", 1); return buf_meth_send(L, &tcp->buf); } -static int tcp_meth_receive(lua_State *L) +static int meth_receive(lua_State *L) { p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{client}", 1); return buf_meth_receive(L, &tcp->buf); } +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_fd(lua_State *L) +{ + p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{client, server}", 1); + lua_pushnumber(L, tcp->sock); + return 1; +} + +static int meth_dirty(lua_State *L) +{ + p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{client, server}", 1); + lua_pushboolean(L, !buf_isempty(&tcp->buf)); + return 1; +} + /*-------------------------------------------------------------------------*\ * Just call inet methods \*-------------------------------------------------------------------------*/ -static int tcp_meth_getpeername(lua_State *L) +static int meth_getpeername(lua_State *L) { p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{client}", 1); return inet_meth_getpeername(L, &tcp->sock); } -static int tcp_meth_getsockname(lua_State *L) +static int meth_getsockname(lua_State *L) { p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{client, server}", 1); return inet_meth_getsockname(L, &tcp->sock); @@ -106,7 +131,7 @@ static int tcp_meth_getsockname(lua_State *L) /*-------------------------------------------------------------------------*\ * Just call tm methods \*-------------------------------------------------------------------------*/ -static int tcp_meth_timeout(lua_State *L) +static int meth_timeout(lua_State *L) { p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{any}", 1); return tm_meth_timeout(L, &tcp->tm); @@ -115,7 +140,7 @@ static int tcp_meth_timeout(lua_State *L) /*-------------------------------------------------------------------------*\ * Closes socket used by object \*-------------------------------------------------------------------------*/ -static int tcp_meth_close(lua_State *L) +static int meth_close(lua_State *L) { p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{any}", 1); sock_destroy(&tcp->sock); @@ -125,7 +150,7 @@ static int tcp_meth_close(lua_State *L) /*-------------------------------------------------------------------------*\ * Turns a master tcp object into a client object. \*-------------------------------------------------------------------------*/ -static int tcp_meth_connect(lua_State *L) +static int meth_connect(lua_State *L) { p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{master}", 1); const char *address = luaL_checkstring(L, 2); @@ -145,7 +170,7 @@ static int tcp_meth_connect(lua_State *L) /*-------------------------------------------------------------------------*\ * Turns a master object into a server object \*-------------------------------------------------------------------------*/ -static int tcp_meth_bind(lua_State *L) +static int meth_bind(lua_State *L) { p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{master}", 1); const char *address = luaL_checkstring(L, 2); @@ -167,10 +192,10 @@ static int tcp_meth_bind(lua_State *L) * Waits for and returns a client object attempting connection to the * server object \*-------------------------------------------------------------------------*/ -static int tcp_meth_accept(lua_State *L) +static int meth_accept(lua_State *L) { struct sockaddr_in addr; - size_t addr_len = sizeof(addr); + socklen_t addr_len = sizeof(addr); p_tcp server = (p_tcp) aux_checkclass(L, "tcp{server}", 1); p_tm tm = &server->tm; p_tcp client = lua_newuserdata(L, sizeof(t_tcp)); @@ -200,7 +225,7 @@ static int tcp_meth_accept(lua_State *L) /*-------------------------------------------------------------------------*\ * Creates a master tcp object \*-------------------------------------------------------------------------*/ -int tcp_global_create(lua_State *L) +int global_create(lua_State *L) { /* allocate tcp object */ p_tcp tcp = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); diff --git a/src/tcp.h b/src/tcp.h index d4cc65c..f4319f3 100644 --- a/src/tcp.h +++ b/src/tcp.h @@ -3,9 +3,9 @@ #include -#include "buf.h" -#include "tm.h" -#include "sock.h" +#include "buffer.h" +#include "timeout.h" +#include "socket.h" typedef struct t_tcp_ { t_sock sock; diff --git a/src/timeout.c b/src/timeout.c index 17878aa..1553069 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -12,8 +12,8 @@ #include #include "luasocket.h" -#include "aux.h" -#include "tm.h" +#include "auxiliar.h" +#include "timeout.h" #ifdef WIN32 #include @@ -118,6 +118,7 @@ int tm_gettime(void) void tm_open(lua_State *L) { luaL_openlib(L, LUASOCKET_LIBNAME, func, 0); + lua_pop(L, 1); } /*-------------------------------------------------------------------------*\ diff --git a/src/udp.c b/src/udp.c index 1701d1b..bcf515b 100644 --- a/src/udp.c +++ b/src/udp.c @@ -10,43 +10,49 @@ #include "luasocket.h" -#include "aux.h" +#include "auxiliar.h" +#include "socket.h" #include "inet.h" +#include "error.h" #include "udp.h" /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ -static int udp_global_create(lua_State *L); -static int udp_meth_send(lua_State *L); -static int udp_meth_sendto(lua_State *L); -static int udp_meth_receive(lua_State *L); -static int udp_meth_receivefrom(lua_State *L); -static int udp_meth_getsockname(lua_State *L); -static int udp_meth_getpeername(lua_State *L); -static int udp_meth_setsockname(lua_State *L); -static int udp_meth_setpeername(lua_State *L); -static int udp_meth_close(lua_State *L); -static int udp_meth_timeout(lua_State *L); +static int global_create(lua_State *L); +static int meth_send(lua_State *L); +static int meth_sendto(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_receivefrom(lua_State *L); +static int meth_getsockname(lua_State *L); +static int meth_getpeername(lua_State *L); +static int meth_setsockname(lua_State *L); +static int meth_setpeername(lua_State *L); +static int meth_close(lua_State *L); +static int meth_timeout(lua_State *L); +static int meth_fd(lua_State *L); +static int meth_dirty(lua_State *L); /* udp object methods */ static luaL_reg udp[] = { - {"setpeername", udp_meth_setpeername}, - {"setsockname", udp_meth_setsockname}, - {"getsockname", udp_meth_getsockname}, - {"getpeername", udp_meth_getpeername}, - {"send", udp_meth_send}, - {"sendto", udp_meth_sendto}, - {"receive", udp_meth_receive}, - {"receivefrom", udp_meth_receivefrom}, - {"timeout", udp_meth_timeout}, - {"close", udp_meth_close}, + {"setpeername", meth_setpeername}, + {"setsockname", meth_setsockname}, + {"getsockname", meth_getsockname}, + {"getpeername", meth_getpeername}, + {"send", meth_send}, + {"sendto", meth_sendto}, + {"receive", meth_receive}, + {"receivefrom", meth_receivefrom}, + {"timeout", meth_timeout}, + {"close", meth_close}, + {"fd", meth_fd}, + {"dirty", meth_dirty}, {NULL, NULL} }; /* functions in library namespace */ static luaL_reg func[] = { - {"udp", udp_global_create}, + {"udp", global_create}, {NULL, NULL} }; @@ -59,8 +65,10 @@ void udp_open(lua_State *L) aux_newclass(L, "udp{connected}", udp); aux_newclass(L, "udp{unconnected}", udp); /* create class groups */ - aux_add2group(L, "udp{connected}", "udp{any}"); + aux_add2group(L, "udp{connected}", "udp{any}"); aux_add2group(L, "udp{unconnected}", "udp{any}"); + aux_add2group(L, "udp{connected}", "select{able}"); + aux_add2group(L, "udp{unconnected}", "select{able}"); /* define library functions */ luaL_openlib(L, LUASOCKET_LIBNAME, func, 0); lua_pop(L, 1); @@ -72,7 +80,7 @@ void udp_open(lua_State *L) /*-------------------------------------------------------------------------*\ * Send data through connected udp socket \*-------------------------------------------------------------------------*/ -static int udp_meth_send(lua_State *L) +static int meth_send(lua_State *L) { p_udp udp = (p_udp) aux_checkclass(L, "udp{connected}", 1); p_tm tm = &udp->tm; @@ -90,7 +98,7 @@ static int udp_meth_send(lua_State *L) /*-------------------------------------------------------------------------*\ * Send data through unconnected udp socket \*-------------------------------------------------------------------------*/ -static int udp_meth_sendto(lua_State *L) +static int meth_sendto(lua_State *L) { p_udp udp = (p_udp) aux_checkclass(L, "udp{unconnected}", 1); size_t count, sent = 0; @@ -117,7 +125,7 @@ static int udp_meth_sendto(lua_State *L) /*-------------------------------------------------------------------------*\ * Receives data from a UDP socket \*-------------------------------------------------------------------------*/ -static int udp_meth_receive(lua_State *L) +static int meth_receive(lua_State *L) { p_udp udp = (p_udp) aux_checkgroup(L, "udp{any}", 1); char buffer[UDP_DATAGRAMSIZE]; @@ -136,11 +144,11 @@ static int udp_meth_receive(lua_State *L) /*-------------------------------------------------------------------------*\ * Receives data and sender from a UDP socket \*-------------------------------------------------------------------------*/ -static int udp_meth_receivefrom(lua_State *L) +static int meth_receivefrom(lua_State *L) { p_udp udp = (p_udp) aux_checkclass(L, "udp{unconnected}", 1); struct sockaddr_in addr; - size_t addr_len = sizeof(addr); + socklen_t addr_len = sizeof(addr); char buffer[UDP_DATAGRAMSIZE]; size_t got, count = (size_t) luaL_optnumber(L, 2, sizeof(buffer)); int err; @@ -161,16 +169,34 @@ static int udp_meth_receivefrom(lua_State *L) } } +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_fd(lua_State *L) +{ + p_udp udp = (p_udp) aux_checkclass(L, "udp{any}", 1); + lua_pushnumber(L, udp->sock); + return 1; +} + +static int meth_dirty(lua_State *L) +{ + p_udp udp = (p_udp) aux_checkclass(L, "udp{any}", 1); + (void) udp; + lua_pushboolean(L, 0); + return 1; +} + /*-------------------------------------------------------------------------*\ * Just call inet methods \*-------------------------------------------------------------------------*/ -static int udp_meth_getpeername(lua_State *L) +static int meth_getpeername(lua_State *L) { p_udp udp = (p_udp) aux_checkclass(L, "udp{connected}", 1); return inet_meth_getpeername(L, &udp->sock); } -static int udp_meth_getsockname(lua_State *L) +static int meth_getsockname(lua_State *L) { p_udp udp = (p_udp) aux_checkgroup(L, "udp{any}", 1); return inet_meth_getsockname(L, &udp->sock); @@ -179,7 +205,7 @@ static int udp_meth_getsockname(lua_State *L) /*-------------------------------------------------------------------------*\ * Just call tm methods \*-------------------------------------------------------------------------*/ -static int udp_meth_timeout(lua_State *L) +static int meth_timeout(lua_State *L) { p_udp udp = (p_udp) aux_checkgroup(L, "udp{any}", 1); return tm_meth_timeout(L, &udp->tm); @@ -188,7 +214,7 @@ static int udp_meth_timeout(lua_State *L) /*-------------------------------------------------------------------------*\ * Turns a master udp object into a client object. \*-------------------------------------------------------------------------*/ -static int udp_meth_setpeername(lua_State *L) +static int meth_setpeername(lua_State *L) { p_udp udp = (p_udp) aux_checkclass(L, "udp{unconnected}", 1); const char *address = luaL_checkstring(L, 2); @@ -211,7 +237,7 @@ static int udp_meth_setpeername(lua_State *L) /*-------------------------------------------------------------------------*\ * Closes socket used by object \*-------------------------------------------------------------------------*/ -static int udp_meth_close(lua_State *L) +static int meth_close(lua_State *L) { p_udp udp = (p_udp) aux_checkgroup(L, "udp{any}", 1); sock_destroy(&udp->sock); @@ -221,7 +247,7 @@ static int udp_meth_close(lua_State *L) /*-------------------------------------------------------------------------*\ * Turns a master object into a server object \*-------------------------------------------------------------------------*/ -static int udp_meth_setsockname(lua_State *L) +static int meth_setsockname(lua_State *L) { p_udp udp = (p_udp) aux_checkclass(L, "udp{master}", 1); const char *address = luaL_checkstring(L, 2); @@ -242,7 +268,7 @@ static int udp_meth_setsockname(lua_State *L) /*-------------------------------------------------------------------------*\ * Creates a master udp object \*-------------------------------------------------------------------------*/ -int udp_global_create(lua_State *L) +int global_create(lua_State *L) { /* allocate udp object */ p_udp udp = (p_udp) lua_newuserdata(L, sizeof(t_udp)); diff --git a/src/udp.h b/src/udp.h index 4ba53e6..a6f17e2 100644 --- a/src/udp.h +++ b/src/udp.h @@ -3,8 +3,8 @@ #include -#include "tm.h" -#include "sock.h" +#include "timeout.h" +#include "socket.h" #define UDP_DATAGRAMSIZE 576 diff --git a/src/usocket.c b/src/usocket.c index b4b8d5a..062a0ff 100644 --- a/src/usocket.c +++ b/src/usocket.c @@ -1,24 +1,8 @@ -/*=========================================================================*\ -* Socket compatibilization module for Unix -* -* RCS ID: $Id$ -\*=========================================================================*/ -#include -#include #include -#include "sock.h" +#include "socket.h" -/*=========================================================================*\ -* Internal function prototypes -\*=========================================================================*/ -static const char *try_setoption(lua_State *L, p_sock ps); -static const char *try_setbooloption(lua_State *L, p_sock ps, int name); - -/*=========================================================================*\ -* Exported functions. -\*=========================================================================*/ -int sock_open(lua_State *L) +int sock_open(void) { /* instals a handler to ignore sigpipe. */ struct sigaction new; @@ -43,13 +27,13 @@ const char *sock_create(p_sock ps, int domain, int type, int protocol) return NULL; } -const char *sock_connect(p_sock ps, SA *addr, size_t addr_len) +const char *sock_connect(p_sock ps, SA *addr, socklen_t addr_len) { if (connect(*ps, addr, addr_len) < 0) return sock_connectstrerror(); else return NULL; } -const char *sock_bind(p_sock ps, SA *addr, size_t addr_len) +const char *sock_bind(p_sock ps, SA *addr, socklen_t addr_len) { if (bind(*ps, addr, addr_len) < 0) return sock_bindstrerror(); else return NULL; @@ -60,17 +44,25 @@ void sock_listen(p_sock ps, int backlog) listen(*ps, backlog); } -void sock_accept(p_sock ps, p_sock pa, SA *addr, size_t *addr_len, int timeout) +int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len, + int timeout) { t_sock sock = *ps; struct timeval tv; + SA dummy_addr; + socklen_t dummy_len; fd_set fds; tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&fds); FD_SET(sock, &fds); - select(sock+1, &fds, NULL, NULL, timeout >= 0 ? &tv : NULL); + if (select(sock+1, &fds, NULL, NULL, timeout >= 0 ? &tv : NULL) <= 0) + return IO_TIMEOUT; + if (!addr) addr = &dummy_addr; + if (!addr_len) addr_len = &dummy_len; *pa = accept(sock, addr, addr_len); + if (*pa == SOCK_INVALID) return IO_ERROR; + else return IO_DONE; } int sock_send(p_sock ps, const char *data, size_t count, size_t *sent, @@ -108,7 +100,7 @@ int sock_send(p_sock ps, const char *data, size_t count, size_t *sent, } int sock_sendto(p_sock ps, const char *data, size_t count, size_t *sent, - SA *addr, size_t addr_len, int timeout) + SA *addr, socklen_t addr_len, int timeout) { t_sock sock = *ps; struct timeval tv; @@ -169,7 +161,7 @@ int sock_recv(p_sock ps, char *data, size_t count, size_t *got, int timeout) } int sock_recvfrom(p_sock ps, char *data, size_t count, size_t *got, - SA *addr, size_t *addr_len, int timeout) + SA *addr, socklen_t *addr_len, int timeout) { t_sock sock = *ps; struct timeval tv; @@ -196,9 +188,6 @@ int sock_recvfrom(p_sock ps, char *data, size_t count, size_t *got, } } -/*-------------------------------------------------------------------------*\ -* Returns a string describing the last host manipulation error. -\*-------------------------------------------------------------------------*/ const char *sock_hoststrerror(void) { switch (h_errno) { @@ -210,9 +199,6 @@ const char *sock_hoststrerror(void) } } -/*-------------------------------------------------------------------------*\ -* Returns a string describing the last socket manipulation error. -\*-------------------------------------------------------------------------*/ const char *sock_createstrerror(void) { switch (errno) { @@ -224,9 +210,6 @@ const char *sock_createstrerror(void) } } -/*-------------------------------------------------------------------------*\ -* Returns a string describing the last bind command error. -\*-------------------------------------------------------------------------*/ const char *sock_bindstrerror(void) { switch (errno) { @@ -241,9 +224,6 @@ const char *sock_bindstrerror(void) } } -/*-------------------------------------------------------------------------*\ -* Returns a string describing the last connect error. -\*-------------------------------------------------------------------------*/ const char *sock_connectstrerror(void) { switch (errno) { @@ -259,20 +239,12 @@ const char *sock_connectstrerror(void) } } -/*-------------------------------------------------------------------------*\ -* Sets the SO_REUSEADDR socket option -* Input -* sock: socket descriptor -\*-------------------------------------------------------------------------*/ void sock_setreuseaddr(p_sock ps) { int val = 1; setsockopt(*ps, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)); } -/*-------------------------------------------------------------------------*\ -* Put socket into blocking mode. -\*-------------------------------------------------------------------------*/ void sock_setblocking(p_sock ps) { int flags = fcntl(*ps, F_GETFL, 0); @@ -280,68 +252,9 @@ void sock_setblocking(p_sock ps) fcntl(*ps, F_SETFL, flags); } -/*-------------------------------------------------------------------------*\ -* Put socket into non-blocking mode. -\*-------------------------------------------------------------------------*/ void sock_setnonblocking(p_sock ps) { int flags = fcntl(*ps, F_GETFL, 0); flags |= O_NONBLOCK; fcntl(*ps, F_SETFL, flags); } - -/*-------------------------------------------------------------------------*\ -* Tries to set extended udp socket options -* Input -* udp: udp structure -* oldtop: top of stack -* Returns -* NULL if successfull, error message on error -\*-------------------------------------------------------------------------*/ -const char *sock_trysetoptions(lua_State *L, p_sock ps) -{ - if (!lua_istable(L, 1)) luaL_argerror(L, 1, "invalid options table"); - lua_pushnil(L); - while (lua_next(L, 1)) { - const char *err = try_setoption(L, ps); - lua_pop(L, 1); - if (err) return err; - } - return NULL; -} - -/*-------------------------------------------------------------------------*\ -* Set socket options from a table on top of Lua stack. -* Supports SO_KEEPALIVE, SO_DONTROUTE, and SO_BROADCAST options. -* Input -* sock: socket -* Returns -* 1 if successful, 0 otherwise -\*-------------------------------------------------------------------------*/ -static const char *try_setoption(lua_State *L, p_sock ps) -{ - static const char *options[] = { - "SO_KEEPALIVE", "SO_DONTROUTE", "SO_BROADCAST", NULL - }; - const char *option = lua_tostring(L, -2); - if (!lua_isstring(L, -2)) return "invalid option"; - switch (luaL_findstring(option, options)) { - case 0: return try_setbooloption(L, ps, SO_KEEPALIVE); - case 1: return try_setbooloption(L, ps, SO_DONTROUTE); - case 2: return try_setbooloption(L, ps, SO_BROADCAST); - default: return "unsupported option"; - } -} - -/*=========================================================================*\ -* Internal functions. -\*=========================================================================*/ -static const char *try_setbooloption(lua_State *L, p_sock ps, int name) -{ - int bool, res; - if (!lua_isnumber(L, -1)) luaL_error(L, "invalid option value"); - bool = (int) lua_tonumber(L, -1); - res = setsockopt(*ps, SOL_SOCKET, name, (char *) &bool, sizeof(bool)); - if (res < 0) return "error setting option"; - else return NULL; -} diff --git a/src/usocket.h b/src/usocket.h index f124bce..9e4b75a 100644 --- a/src/usocket.h +++ b/src/usocket.h @@ -31,6 +31,11 @@ #include #include +#ifdef __APPLE__ +/* for some reason socklen_t is not defined in mac os x */ +typedef int socklen_t; +#endif + typedef int t_sock; typedef t_sock *p_sock; From f330540576031528f0daac231c61d4dd06e8ba1e Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Wed, 11 Jun 2003 01:42:18 +0000 Subject: [PATCH 124/483] Compiles and runs on linux and windows, using DLLs! --- src/inet.c | 19 ++-- src/inet.h | 4 + src/tcp.c | 7 +- src/udp.c | 10 +- src/usocket.h | 6 +- src/wsocket.c | 261 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/wsocket.h | 22 +++++ 7 files changed, 309 insertions(+), 20 deletions(-) create mode 100644 src/wsocket.c create mode 100644 src/wsocket.h diff --git a/src/inet.c b/src/inet.c index 60106f2..b7f3ae5 100644 --- a/src/inet.c +++ b/src/inet.c @@ -19,10 +19,6 @@ static int inet_global_tohostname(lua_State *L); static void inet_pushresolved(lua_State *L, struct hostent *hp); -#ifdef INET_ATON -static int inet_aton(const char *cp, struct in_addr *inp); -#endif - static luaL_reg func[] = { { "toip", inet_global_toip }, { "tohostname", inet_global_tohostname }, @@ -196,9 +192,11 @@ static void inet_pushresolved(lua_State *L, struct hostent *hp) * Returns * NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ -const char *inet_tryconnect(p_sock ps, const char *address, ushort port) +const char *inet_tryconnect(p_sock ps, const char *address, + unsigned short port) { struct sockaddr_in remote; + const char *err; memset(&remote, 0, sizeof(remote)); remote.sin_family = AF_INET; remote.sin_port = htons(port); @@ -213,7 +211,7 @@ const char *inet_tryconnect(p_sock ps, const char *address, ushort port) } } else remote.sin_family = AF_UNSPEC; sock_setblocking(ps); - const char *err = sock_connect(ps, (SA *) &remote, sizeof(remote)); + err = sock_connect(ps, (SA *) &remote, sizeof(remote)); if (err) { sock_destroy(ps); *ps = SOCK_INVALID; @@ -233,10 +231,11 @@ const char *inet_tryconnect(p_sock ps, const char *address, ushort port) * Returns * NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ -const char *inet_trybind(p_sock ps, const char *address, ushort port, +const char *inet_trybind(p_sock ps, const char *address, unsigned short port, int backlog) { struct sockaddr_in local; + const char *err; memset(&local, 0, sizeof(local)); /* address is either wildcard or a valid ip address */ local.sin_addr.s_addr = htonl(INADDR_ANY); @@ -251,7 +250,7 @@ const char *inet_trybind(p_sock ps, const char *address, ushort port, memcpy(&local.sin_addr, *addr, sizeof(struct in_addr)); } sock_setblocking(ps); - const char *err = sock_bind(ps, (SA *) &local, sizeof(local)); + err = sock_bind(ps, (SA *) &local, sizeof(local)); if (err) { sock_destroy(ps); *ps = SOCK_INVALID; @@ -279,8 +278,8 @@ const char *inet_trycreate(p_sock ps, int type) * Some systems do not provide this so that we provide our own. It's not * marvelously fast, but it works just fine. \*-------------------------------------------------------------------------*/ -#ifdef COMPAT_INETATON -static int inet_aton(const char *cp, struct in_addr *inp) +#ifdef INET_ATON +int inet_aton(const char *cp, struct in_addr *inp) { unsigned int a = 0, b = 0, c = 0, d = 0; int n = 0, r; diff --git a/src/inet.h b/src/inet.h index b8d8f19..60be9e4 100644 --- a/src/inet.h +++ b/src/inet.h @@ -23,4 +23,8 @@ const char *inet_trycreate(p_sock ps, int type); int inet_meth_getpeername(lua_State *L, p_sock ps); int inet_meth_getsockname(lua_State *L, p_sock ps); +#ifdef INET_ATON +int inet_aton(const char *cp, struct in_addr *inp); +#endif + #endif /* INET_H_ */ diff --git a/src/tcp.c b/src/tcp.c index 74857c9..dc7683d 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -154,7 +154,7 @@ static int meth_connect(lua_State *L) { p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{master}", 1); const char *address = luaL_checkstring(L, 2); - unsigned short port = (ushort) luaL_checknumber(L, 3); + unsigned short port = (unsigned short) luaL_checknumber(L, 3); const char *err = inet_tryconnect(&tcp->sock, address, port); if (err) { lua_pushnil(L); @@ -174,7 +174,7 @@ static int meth_bind(lua_State *L) { p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{master}", 1); const char *address = luaL_checkstring(L, 2); - unsigned short port = (ushort) luaL_checknumber(L, 3); + unsigned short port = (unsigned short) luaL_checknumber(L, 3); int backlog = (int) luaL_optnumber(L, 4, 1); const char *err = inet_trybind(&tcp->sock, address, port, backlog); if (err) { @@ -227,12 +227,13 @@ static int meth_accept(lua_State *L) \*-------------------------------------------------------------------------*/ int global_create(lua_State *L) { + const char *err; /* allocate tcp object */ p_tcp tcp = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); /* set its type as master object */ aux_setclass(L, "tcp{master}", -1); /* try to allocate a system socket */ - const char *err = inet_trycreate(&tcp->sock, SOCK_STREAM); + err = inet_trycreate(&tcp->sock, SOCK_STREAM); if (err) { /* get rid of object on stack and push error */ lua_pop(L, 1); lua_pushnil(L); diff --git a/src/udp.c b/src/udp.c index bcf515b..79831e7 100644 --- a/src/udp.c +++ b/src/udp.c @@ -104,7 +104,7 @@ static int meth_sendto(lua_State *L) size_t count, sent = 0; const char *data = luaL_checklstring(L, 2, &count); const char *ip = luaL_checkstring(L, 3); - ushort port = (ushort) luaL_checknumber(L, 4); + unsigned short port = (unsigned short) luaL_checknumber(L, 4); p_tm tm = &udp->tm; struct sockaddr_in addr; int err; @@ -220,7 +220,8 @@ static int meth_setpeername(lua_State *L) const char *address = luaL_checkstring(L, 2); int connecting = strcmp(address, "*"); unsigned short port = connecting ? - (ushort) luaL_checknumber(L, 3) : (ushort) luaL_optnumber(L, 3, 0); + (unsigned short) luaL_checknumber(L, 3) : + (unsigned short) luaL_optnumber(L, 3, 0); const char *err = inet_tryconnect(&udp->sock, address, port); if (err) { lua_pushnil(L); @@ -251,7 +252,7 @@ static int meth_setsockname(lua_State *L) { p_udp udp = (p_udp) aux_checkclass(L, "udp{master}", 1); const char *address = luaL_checkstring(L, 2); - unsigned short port = (ushort) luaL_checknumber(L, 3); + unsigned short port = (unsigned short) luaL_checknumber(L, 3); const char *err = inet_trybind(&udp->sock, address, port, -1); if (err) { lua_pushnil(L); @@ -270,12 +271,13 @@ static int meth_setsockname(lua_State *L) \*-------------------------------------------------------------------------*/ int global_create(lua_State *L) { + const char *err; /* allocate udp object */ p_udp udp = (p_udp) lua_newuserdata(L, sizeof(t_udp)); /* set its type as master object */ aux_setclass(L, "udp{unconnected}", -1); /* try to allocate a system socket */ - const char *err = inet_trycreate(&udp->sock, SOCK_DGRAM); + err = inet_trycreate(&udp->sock, SOCK_DGRAM); if (err) { /* get rid of object on stack and push error */ lua_pop(L, 1); diff --git a/src/usocket.h b/src/usocket.h index 9e4b75a..034ae74 100644 --- a/src/usocket.h +++ b/src/usocket.h @@ -3,8 +3,8 @@ * * RCS ID: $Id$ \*=========================================================================*/ -#ifndef UNIX_H -#define UNIX_H +#ifndef USOCKET_H +#define USOCKET_H /*=========================================================================*\ * BSD include files @@ -41,4 +41,4 @@ typedef t_sock *p_sock; #define SOCK_INVALID (-1) -#endif /* UNIX_H */ +#endif /* USOCKET_H */ diff --git a/src/wsocket.c b/src/wsocket.c new file mode 100644 index 0000000..56e65ec --- /dev/null +++ b/src/wsocket.c @@ -0,0 +1,261 @@ +#include + +#include "socket.h" + +int sock_open(void) +{ + WORD wVersionRequested; + WSADATA wsaData; + int err; + wVersionRequested = MAKEWORD(2, 0); + err = WSAStartup(wVersionRequested, &wsaData ); + if (err != 0) return 0; + if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0) { + WSACleanup(); + return 0; + } + return 1; +} + +void sock_destroy(p_sock ps) +{ + closesocket(*ps); +} + +const char *sock_create(p_sock ps, int domain, int type, int protocol) +{ + t_sock sock = socket(domain, type, protocol); + if (sock == SOCK_INVALID) return sock_createstrerror(); + *ps = sock; + sock_setnonblocking(ps); + sock_setreuseaddr(ps); + return NULL; +} + +const char *sock_connect(p_sock ps, SA *addr, socklen_t addr_len) +{ + if (connect(*ps, addr, addr_len) < 0) return sock_connectstrerror(); + else return NULL; +} + +const char *sock_bind(p_sock ps, SA *addr, socklen_t addr_len) +{ + if (bind(*ps, addr, addr_len) < 0) return sock_bindstrerror(); + else return NULL; +} + +void sock_listen(p_sock ps, int backlog) +{ + listen(*ps, backlog); +} + +int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len, + int timeout) +{ + t_sock sock = *ps; + struct timeval tv; + SA dummy_addr; + socklen_t dummy_len; + fd_set fds; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + FD_ZERO(&fds); + FD_SET(sock, &fds); + if (select(sock+1, &fds, NULL, NULL, timeout >= 0 ? &tv : NULL) <= 0) + return IO_TIMEOUT; + if (!addr) addr = &dummy_addr; + if (!addr_len) addr_len = &dummy_len; + *pa = accept(sock, addr, addr_len); + if (*pa == SOCK_INVALID) return IO_ERROR; + else return IO_DONE; +} + +int sock_send(p_sock ps, const char *data, size_t count, size_t *sent, + int timeout) +{ + t_sock sock = *ps; + struct timeval tv; + fd_set fds; + ssize_t put = 0; + int err; + int ret; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + FD_ZERO(&fds); + FD_SET(sock, &fds); + ret = select(sock+1, NULL, &fds, NULL, timeout >= 0 ? &tv : NULL); + if (ret > 0) { + put = send(sock, data, count, 0); + if (put <= 0) { + /* a bug in WinSock forces us to do a busy wait until we manage + ** to write, because select returns immediately even though it + ** should have blocked us until we could write... */ + if (WSAGetLastError() == WSAEWOULDBLOCK) err = IO_DONE; + else err = IO_CLOSED; + *sent = 0; + } else { + *sent = put; + err = IO_DONE; + } + return err; + } else { + *sent = 0; + return IO_TIMEOUT; + } +} + +int sock_sendto(p_sock ps, const char *data, size_t count, size_t *sent, + SA *addr, socklen_t addr_len, int timeout) +{ + t_sock sock = *ps; + struct timeval tv; + fd_set fds; + ssize_t put = 0; + int err; + int ret; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + FD_ZERO(&fds); + FD_SET(sock, &fds); + ret = select(sock+1, NULL, &fds, NULL, timeout >= 0 ? &tv : NULL); + if (ret > 0) { + put = sendto(sock, data, count, 0, addr, addr_len); + if (put <= 0) { + /* a bug in WinSock forces us to do a busy wait until we manage + ** to write, because select returns immediately even though it + ** should have blocked us until we could write... */ + if (WSAGetLastError() == WSAEWOULDBLOCK) err = IO_DONE; + else err = IO_CLOSED; + *sent = 0; + } else { + *sent = put; + err = IO_DONE; + } + return err; + } else { + *sent = 0; + return IO_TIMEOUT; + } +} + +int sock_recv(p_sock ps, char *data, size_t count, size_t *got, int timeout) +{ + t_sock sock = *ps; + struct timeval tv; + fd_set fds; + int ret; + ssize_t taken = 0; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + FD_ZERO(&fds); + FD_SET(sock, &fds); + ret = select(sock+1, &fds, NULL, NULL, timeout >= 0 ? &tv : NULL); + if (ret > 0) { + taken = recv(sock, data, count, 0); + if (taken <= 0) { + *got = 0; + return IO_CLOSED; + } else { + *got = taken; + return IO_DONE; + } + } else { + *got = 0; + return IO_TIMEOUT; + } +} + +int sock_recvfrom(p_sock ps, char *data, size_t count, size_t *got, + SA *addr, socklen_t *addr_len, int timeout) +{ + t_sock sock = *ps; + struct timeval tv; + fd_set fds; + int ret; + ssize_t taken = 0; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + FD_ZERO(&fds); + FD_SET(sock, &fds); + ret = select(sock+1, &fds, NULL, NULL, timeout >= 0 ? &tv : NULL); + if (ret > 0) { + taken = recvfrom(sock, data, count, 0, addr, addr_len); + if (taken <= 0) { + *got = 0; + return IO_CLOSED; + } else { + *got = taken; + return IO_DONE; + } + } else { + *got = 0; + return IO_TIMEOUT; + } +} + +const char *sock_hoststrerror(void) +{ + switch (WSAGetLastError()) { + case HOST_NOT_FOUND: return "host not found"; + case NO_ADDRESS: return "unable to resolve host name"; + case NO_RECOVERY: return "name server error"; + case TRY_AGAIN: return "name server unavailable, try again later."; + default: return "unknown error"; + } +} + +const char *sock_createstrerror(void) +{ + switch (WSAGetLastError()) { + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAEMFILE: return "descriptor table is full"; + case WSAENOBUFS: return "insufficient buffer space"; + default: return "unknown error"; + } +} + +const char *sock_bindstrerror(void) +{ + switch (WSAGetLastError()) { + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAEADDRINUSE: return "address already in use"; + case WSAEINVAL: return "socket already bound"; + case WSAENOBUFS: return "too many connections"; + case WSAEFAULT: return "invalid address"; + case WSAENOTSOCK: return "not a socket descriptor"; + default: return "unknown error"; + } +} + +const char *sock_connectstrerror(void) +{ + switch (WSAGetLastError()) { + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAEADDRINUSE: return "address already in use"; + case WSAEADDRNOTAVAIL: return "address unavailable"; + case WSAECONNREFUSED: return "connection refused"; + case WSAENETUNREACH: return "network is unreachable"; + default: return "unknown error"; + } +} + +void sock_setreuseaddr(p_sock ps) +{ + int val = 1; + setsockopt(*ps, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)); +} + +void sock_setblocking(p_sock ps) +{ + u_long argp = 0; + ioctlsocket(*ps, FIONBIO, &argp); +} + +void sock_setnonblocking(p_sock ps) +{ + u_long argp = 1; + ioctlsocket(*ps, FIONBIO, &argp); +} diff --git a/src/wsocket.h b/src/wsocket.h new file mode 100644 index 0000000..ceecae7 --- /dev/null +++ b/src/wsocket.h @@ -0,0 +1,22 @@ +/*=========================================================================*\ +* Socket compatibilization module for Win32 +* +* RCS ID: $Id$ +\*=========================================================================*/ +#ifndef WSOCKET_H +#define WSOCKET_H + +/*=========================================================================*\ +* WinSock2 include files +\*=========================================================================*/ +#include +#include + +typedef int socklen_t; +typedef int ssize_t; +typedef SOCKET t_sock; +typedef t_sock *p_sock; + +#define SOCK_INVALID (INVALID_SOCKET) + +#endif /* WSOCKET_H */ From 71f6bb60bf2b7457091c7106190f92ab7e51f7c6 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 26 Jun 2003 18:47:49 +0000 Subject: [PATCH 125/483] Finished implementation of LuaSocket 2.0 alpha on Linux. Some testing still needed. --- NEW | 4 +- TODO | 26 +++--- etc/check-links.lua | 2 +- etc/dict.lua | 2 +- etc/get.lua | 2 +- etc/tftp.lua | 2 +- samples/daytimeclnt.lua | 2 +- samples/echoclnt.lua | 2 +- samples/echosrvr.lua | 2 +- samples/listener.lua | 2 +- samples/talker.lua | 2 +- samples/tinyirc.lua | 2 +- src/auxiliar.c | 191 +++++++++++++++++++++++----------------- src/auxiliar.h | 48 +++++++--- src/buffer.c | 33 +++---- src/buffer.h | 27 +++--- src/ftp.lua | 2 +- src/http.lua | 2 +- src/inet.c | 43 +-------- src/inet.h | 23 +++-- src/io.c | 40 +++++++++ src/io.h | 29 ++++-- src/luasocket.c | 20 ++--- src/luasocket.h | 10 ++- src/select.c | 52 +++++++---- src/select.h | 18 +++- src/smtp.lua | 2 +- src/socket.h | 15 ++-- src/tcp.c | 85 +++++++++++++++++- src/tcp.h | 19 +++- src/timeout.c | 4 +- src/timeout.h | 8 +- src/udp.c | 61 +++++++++++-- src/udp.h | 17 +++- src/url.lua | 2 +- src/usocket.c | 96 ++++++++++++++------ src/usocket.h | 9 +- src/wsocket.c | 88 +++++++++++++----- src/wsocket.h | 10 +-- test/testclnt.lua | 30 ++++--- test/testsrvr.lua | 5 +- 41 files changed, 700 insertions(+), 339 deletions(-) diff --git a/NEW b/NEW index 879f12c..e5efc97 100644 --- a/NEW +++ b/NEW @@ -8,7 +8,9 @@ a given domain/family and protocol. Then connect or bind if needed. Then use IO functions. All functions return a non-nil value as first return value if successful. -All functions return nil followed by error message in case of error. +All functions return whatever could be retrieved followed by error message +in case of error. The best way to check for errors is to check for the +presence of an error message. WARNING: The send function was affected. Better error messages and parameter checking. diff --git a/TODO b/TODO index 25d2586..5c2492d 100644 --- a/TODO +++ b/TODO @@ -1,24 +1,26 @@ +- Melhorar a interface de setoptions (aceitar nada como true, por exemplo) - Inicializaccao das classes pode falhar? - Ajeitar melhor a hierarquia de classes. Ajeitar o file... +- GARBAGE COLLECTOR! +- Adicionar um método sock:setoption??? +- testar em várias plataformas +- adicionar exemplos de expansão: pipe, local, named pipe * Como mostrar um erro em lua_socketlibopen()... * O location do "redirect" pode ser relativo ao servidor atual (não pode, mas os servidores fazem merda...) -* - Ajeitar para Lua 4.1 +* Ajeitar para Lua 5.0 +* Padronizar os retornos de funccao +* Separar as classes em arquivos +* Retorno de sendto em datagram sockets pode ser refused +* Fazer compilar com g++ -- Padronizar os retornos de funccao - Thread-safe - proteger gethostby*.* com um mutex GLOBAL! - - proteger o atomizar o conjunto (timedout, receive), (timedout, send) -- Usar "require" nos módulos + - proteger ou atomizar o conjunto (timedout, receive), (timedout, send) + - inet_ntoa também é uma merda. - SSL -- Fazer compilar com g++ -- usar lua_verror -- separar as classes em arquivos -- criar mais uma classe, a de stream, entre p_sock e p_client -- criar um internal include file ls.h -- impedir que voe quando chamar accept(udpsocket()) -- trocar recv and send por read e write (ver se funciona) +- Proxy support pro http - checar operações em closed sockets - checar teste de writable socket com select @@ -34,6 +36,4 @@ - unix 92 bytes maximo no endereço, incluindo o zero - unix 9216 maximo de datagram size -- retorno de send/receive em datagram sockets pode ser refused... -- adicionar um método sock:setoption??? diff --git a/etc/check-links.lua b/etc/check-links.lua index 4c96fdc..c45131c 100644 --- a/etc/check-links.lua +++ b/etc/check-links.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- Little program that checks links in HTML files --- LuaSocket 1.5 sample files. +-- LuaSocket sample files -- Author: Diego Nehab -- RCS ID: $Id$ ----------------------------------------------------------------------------- diff --git a/etc/dict.lua b/etc/dict.lua index 89bdb4f..9926538 100644 --- a/etc/dict.lua +++ b/etc/dict.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- Little program to download DICT word definitions --- LuaSocket 1.5 sample files +-- LuaSocket sample files -- Author: Diego Nehab -- RCS ID: $Id$ ----------------------------------------------------------------------------- diff --git a/etc/get.lua b/etc/get.lua index e972d16..caaa607 100644 --- a/etc/get.lua +++ b/etc/get.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- Little program to download files from URLs --- LuaSocket 1.5 sample files +-- LuaSocket sample files -- Author: Diego Nehab -- RCS ID: $Id$ ----------------------------------------------------------------------------- diff --git a/etc/tftp.lua b/etc/tftp.lua index a0db68e..d1b5594 100644 --- a/etc/tftp.lua +++ b/etc/tftp.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- TFTP support for the Lua language --- LuaSocket 1.5 toolkit. +-- LuaSocket toolkit. -- Author: Diego Nehab -- Conforming to: RFC 783, LTN7 -- RCS ID: $Id$ diff --git a/samples/daytimeclnt.lua b/samples/daytimeclnt.lua index 85ddca1..5064fff 100644 --- a/samples/daytimeclnt.lua +++ b/samples/daytimeclnt.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- UDP sample: daytime protocol client --- LuaSocket 1.5 sample files. +-- LuaSocket sample files -- Author: Diego Nehab -- RCS ID: $Id$ ----------------------------------------------------------------------------- diff --git a/samples/echoclnt.lua b/samples/echoclnt.lua index bca0b4d..e028b86 100644 --- a/samples/echoclnt.lua +++ b/samples/echoclnt.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- UDP sample: echo protocol client --- LuaSocket 1.5 sample files +-- LuaSocket sample files -- Author: Diego Nehab -- RCS ID: $Id$ ----------------------------------------------------------------------------- diff --git a/samples/echosrvr.lua b/samples/echosrvr.lua index 18bd84e..127ccb8 100644 --- a/samples/echosrvr.lua +++ b/samples/echosrvr.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- UDP sample: echo protocol server --- LuaSocket 1.5 sample files +-- LuaSocket sample files -- Author: Diego Nehab -- RCS ID: $Id$ ----------------------------------------------------------------------------- diff --git a/samples/listener.lua b/samples/listener.lua index 4846419..dff4d25 100644 --- a/samples/listener.lua +++ b/samples/listener.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- TCP sample: Little program to dump lines received at a given port --- LuaSocket 1.5 sample files +-- LuaSocket sample files -- Author: Diego Nehab -- RCS ID: $Id$ ----------------------------------------------------------------------------- diff --git a/samples/talker.lua b/samples/talker.lua index c7a239a..1b0652f 100644 --- a/samples/talker.lua +++ b/samples/talker.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- TCP sample: Little program to send text lines to a given host/port --- LuaSocket 1.5 sample files +-- LuaSocket sample files -- Author: Diego Nehab -- RCS ID: $Id$ ----------------------------------------------------------------------------- diff --git a/samples/tinyirc.lua b/samples/tinyirc.lua index 0b20303..0ad00ab 100644 --- a/samples/tinyirc.lua +++ b/samples/tinyirc.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- Select sample: simple text line server --- LuaSocket 1.5 sample files. +-- LuaSocket sample files. -- Author: Diego Nehab -- RCS ID: $Id$ ----------------------------------------------------------------------------- diff --git a/src/auxiliar.c b/src/auxiliar.c index 96138f1..8b2fa37 100644 --- a/src/auxiliar.c +++ b/src/auxiliar.c @@ -1,142 +1,167 @@ /*=========================================================================*\ * Auxiliar routines for class hierarchy manipulation +* LuaSocket toolkit * * RCS ID: $Id$ \*=========================================================================*/ +#include + +#include "luasocket.h" #include "auxiliar.h" /*=========================================================================*\ * Exported functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ -* Creates a new class. A class has methods given by the func array and the -* field 'class' tells the object class. The table 'group' list the class -* groups the object belongs to. +* Initializes the module \*-------------------------------------------------------------------------*/ -void aux_newclass(lua_State *L, const char *name, luaL_reg *func) +void aux_open(lua_State *L) { - lua_pushstring(L, name); + /* create namespace table */ + lua_pushstring(L, LUASOCKET_LIBNAME); lua_newtable(L); - lua_pushstring(L, "__index"); - lua_newtable(L); - luaL_openlib(L, NULL, func, 0); - lua_pushstring(L, "class"); - lua_pushstring(L, name); - lua_rawset(L, -3); - lua_pushstring(L, "group"); - lua_newtable(L); - lua_rawset(L, -3); - lua_rawset(L, -3); - lua_rawset(L, LUA_REGISTRYINDEX); -} - -/*-------------------------------------------------------------------------*\ -* Add group to object list of groups. -\*-------------------------------------------------------------------------*/ -void aux_add2group(lua_State *L, const char *name, const char *group) -{ - lua_pushstring(L, name); - lua_rawget(L, LUA_REGISTRYINDEX); - lua_pushstring(L, "__index"); - lua_rawget(L, -2); - lua_pushstring(L, "group"); - lua_rawget(L, -2); - lua_pushstring(L, group); +#ifdef LUASOCKET_DEBUG + lua_pushstring(L, "debug"); lua_pushnumber(L, 1); lua_rawset(L, -3); - lua_pop(L, 3); +#endif + lua_settable(L, LUA_GLOBALSINDEX); + /* make sure modules know what is our namespace */ + lua_pushstring(L, "LUASOCKET_LIBNAME"); + lua_pushstring(L, LUASOCKET_LIBNAME); + lua_settable(L, LUA_GLOBALSINDEX); } /*-------------------------------------------------------------------------*\ -* Get a userdata making sure the object belongs to a given class. +* Creates a new class with given methods \*-------------------------------------------------------------------------*/ -void *aux_checkclass(lua_State *L, const char *name, int objidx) +void aux_newclass(lua_State *L, const char *classname, luaL_reg *func) { - void *data = aux_getclassudata(L, name, objidx); + luaL_newmetatable(L, classname); /* mt */ + lua_pushstring(L, "__index"); /* mt,"__index" */ + lua_newtable(L); /* mt,"__index",it */ + luaL_openlib(L, NULL, func, 0); +#ifdef LUASOCKET_DEBUG + lua_pushstring(L, "class"); /* mt,"__index",it,"class" */ + lua_pushstring(L, classname); /* mt,"__index",it,"class",classname */ + lua_rawset(L, -3); /* mt,"__index",it */ +#endif + /* get __gc method from class and use it for garbage collection */ + lua_pushstring(L, "__gc"); /* mt,"__index",it,"__gc" */ + lua_pushstring(L, "__gc"); /* mt,"__index",it,"__gc","__gc" */ + lua_rawget(L, -3); /* mt,"__index",it,"__gc",fn */ + lua_rawset(L, -5); /* mt,"__index",it */ + lua_rawset(L, -3); /* mt */ + lua_pop(L, 1); +} + +/*-------------------------------------------------------------------------*\ +* Insert class into group +\*-------------------------------------------------------------------------*/ +void aux_add2group(lua_State *L, const char *classname, const char *groupname) +{ + luaL_getmetatable(L, classname); + lua_pushstring(L, groupname); + lua_pushboolean(L, 1); + lua_rawset(L, -3); + lua_pop(L, 1); +} + +/*-------------------------------------------------------------------------*\ +* Make sure argument is a boolean +\*-------------------------------------------------------------------------*/ +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); +} + +/*-------------------------------------------------------------------------*\ +* Calls appropriate option handler +\*-------------------------------------------------------------------------*/ +int aux_meth_setoption(lua_State *L, luaL_reg *opt) +{ + const char *name = luaL_checkstring(L, 2); /* obj, name, args */ + while (opt->name && strcmp(name, opt->name)) + opt++; + if (!opt->func) { + char msg[45]; + sprintf(msg, "unknown option `%.35s'", name); + luaL_argerror(L, 2, msg); + } + lua_remove(L, 2); /* obj, args */ + lua_pushcfunction(L, opt->func); /* obj, args, func */ + lua_insert(L, 1); /* func, obj, args */ + lua_call(L, lua_gettop(L)-1, LUA_MULTRET); + return lua_gettop(L); +} + +/*-------------------------------------------------------------------------*\ +* 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 *data = aux_getclassudata(L, classname, objidx); if (!data) { char msg[45]; - sprintf(msg, "%.35s expected", name); + sprintf(msg, "%.35s expected", classname); luaL_argerror(L, objidx, msg); } return data; } /*-------------------------------------------------------------------------*\ -* Get a userdata making sure the object belongs to a given group. +* Return userdata pointer if object belongs to a given group, abort with +* error otherwise \*-------------------------------------------------------------------------*/ -void *aux_checkgroup(lua_State *L, const char *group, int objidx) +void *aux_checkgroup(lua_State *L, const char *groupname, int objidx) { - void *data = aux_getgroupudata(L, group, objidx); + void *data = aux_getgroupudata(L, groupname, objidx); if (!data) { char msg[45]; - sprintf(msg, "%.35s expected", group); + sprintf(msg, "%.35s expected", groupname); luaL_argerror(L, objidx, msg); } return data; } /*-------------------------------------------------------------------------*\ -* Set object class. +* Set object class \*-------------------------------------------------------------------------*/ -void aux_setclass(lua_State *L, const char *name, int objidx) +void aux_setclass(lua_State *L, const char *classname, int objidx) { - lua_pushstring(L, name); - lua_rawget(L, LUA_REGISTRYINDEX); + luaL_getmetatable(L, classname); if (objidx < 0) objidx--; lua_setmetatable(L, objidx); } -/*=========================================================================*\ -* Internal functions -\*=========================================================================*/ /*-------------------------------------------------------------------------*\ -* Get a userdata if object belongs to a given group. +* Get a userdata pointer if object belongs to a given group. Return NULL +* otherwise \*-------------------------------------------------------------------------*/ -void *aux_getgroupudata(lua_State *L, const char *group, int objidx) +void *aux_getgroupudata(lua_State *L, const char *groupname, int objidx) { - if (!lua_getmetatable(L, objidx)) + if (!lua_getmetatable(L, objidx)) return NULL; - lua_pushstring(L, "__index"); - lua_rawget(L, -2); - if (!lua_istable(L, -1)) { - lua_pop(L, 2); - return NULL; - } - lua_pushstring(L, "group"); - lua_rawget(L, -2); - if (!lua_istable(L, -1)) { - lua_pop(L, 3); - return NULL; - } - lua_pushstring(L, group); + lua_pushstring(L, groupname); lua_rawget(L, -2); if (lua_isnil(L, -1)) { - lua_pop(L, 4); + lua_pop(L, 2); return NULL; + } else { + lua_pop(L, 2); + return lua_touserdata(L, objidx); } - lua_pop(L, 4); - return lua_touserdata(L, objidx); } /*-------------------------------------------------------------------------*\ -* Get a userdata if object belongs to a given class. +* Get a userdata pointer if object belongs to a given class. Return NULL +* otherwise \*-------------------------------------------------------------------------*/ -void *aux_getclassudata(lua_State *L, const char *group, int objidx) +void *aux_getclassudata(lua_State *L, const char *classname, int objidx) { - if (!lua_getmetatable(L, objidx)) - return NULL; - lua_pushstring(L, "__index"); - lua_rawget(L, -2); - if (!lua_istable(L, -1)) { - lua_pop(L, 2); - return NULL; - } - lua_pushstring(L, "class"); - lua_rawget(L, -2); - if (lua_isnil(L, -1)) { - lua_pop(L, 3); - return NULL; - } - lua_pop(L, 3); - return lua_touserdata(L, objidx); + return luaL_checkudata(L, objidx, classname); } + diff --git a/src/auxiliar.h b/src/auxiliar.h index 66be31d..324e800 100644 --- a/src/auxiliar.h +++ b/src/auxiliar.h @@ -1,22 +1,37 @@ +#ifndef AUX_H +#define AUX_H /*=========================================================================*\ * Auxiliar routines for class hierarchy manipulation +* LuaSocket toolkit +* +* 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 +* of groups. This module provides the functionality to: +* +* - create new classes +* - add classes to groups +* - set the class of object +* - check if an object belongs to a given class or group +* +* 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. +* +* 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. +* +* The mapping from class name to the corresponding metatable and the +* reverse mapping are done using lauxlib. * * RCS ID: $Id$ \*=========================================================================*/ -#ifndef AUX_H -#define AUX_H #include #include -void aux_newclass(lua_State *L, const char *name, luaL_reg *func); -void aux_add2group(lua_State *L, const char *name, const char *group); -void *aux_checkclass(lua_State *L, const char *name, int objidx); -void *aux_checkgroup(lua_State *L, const char *group, int objidx); -void *aux_getclassudata(lua_State *L, const char *group, int objidx); -void *aux_getgroupudata(lua_State *L, const char *group, int objidx); -void aux_setclass(lua_State *L, const char *name, int objidx); - /* min and max macros */ #ifndef MIN #define MIN(x, y) ((x) < (y) ? x : y) @@ -25,4 +40,15 @@ void aux_setclass(lua_State *L, const char *name, int objidx); #define MAX(x, y) ((x) > (y) ? x : y) #endif -#endif +void 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); +void aux_setclass(lua_State *L, const char *classname, int objidx); +void *aux_checkclass(lua_State *L, const char *classname, int objidx); +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_meth_setoption(lua_State *L, luaL_reg *opt); +int aux_checkboolean(lua_State *L, int objidx); + +#endif /* AUX_H */ diff --git a/src/buffer.c b/src/buffer.c index ab059bb..c860f35 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -1,12 +1,12 @@ /*=========================================================================*\ -* Buffered input/output routines +* Input/Output interface for Lua programs +* LuaSocket toolkit * * RCS ID: $Id$ \*=========================================================================*/ #include #include -#include "error.h" #include "auxiliar.h" #include "buffer.h" @@ -42,7 +42,7 @@ void buf_init(p_buf buf, p_io io, p_tm tm) } /*-------------------------------------------------------------------------*\ -* Send data through buffered object +* object:send() interface \*-------------------------------------------------------------------------*/ int buf_meth_send(lua_State *L, p_buf buf) { @@ -59,7 +59,7 @@ int buf_meth_send(lua_State *L, p_buf buf) total += sent; } lua_pushnumber(L, total); - error_push(L, err); + io_pusherror(L, err); #ifdef LUASOCKET_DEBUG /* push time elapsed during operation as the last return value */ lua_pushnumber(L, (tm_gettime() - tm_getstart(tm))/1000.0); @@ -68,7 +68,7 @@ int buf_meth_send(lua_State *L, p_buf buf) } /*-------------------------------------------------------------------------*\ -* Receive data from a buffered object +* object:receive() interface \*-------------------------------------------------------------------------*/ int buf_meth_receive(lua_State *L, p_buf buf) { @@ -101,13 +101,13 @@ int buf_meth_receive(lua_State *L, p_buf buf) luaL_argcheck(L, 0, arg, "invalid receive pattern"); break; } - /* raw pattern */ + /* get a fixed number of bytes */ } else err = recvraw(L, buf, (size_t) lua_tonumber(L, arg)); } /* push nil for each pattern after an error */ for ( ; arg <= top; arg++) lua_pushnil(L); /* last return is an error code */ - error_push(L, err); + io_pusherror(L, err); #ifdef LUASOCKET_DEBUG /* push time elapsed during operation as the last return value */ lua_pushnumber(L, (tm_gettime() - tm_getstart(tm))/1000.0); @@ -127,9 +127,10 @@ int buf_isempty(p_buf buf) * Internal functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ -* Sends a raw block of data through a buffered object. +* Sends a block of data (unbuffered) \*-------------------------------------------------------------------------*/ -static int sendraw(p_buf buf, const char *data, size_t count, size_t *sent) +static +int sendraw(p_buf buf, const char *data, size_t count, size_t *sent) { p_io io = buf->io; p_tm tm = buf->tm; @@ -145,7 +146,7 @@ static int sendraw(p_buf buf, const char *data, size_t count, size_t *sent) } /*-------------------------------------------------------------------------*\ -* Reads a raw block of data from a buffered object. +* Reads a fixed number of bytes (buffered) \*-------------------------------------------------------------------------*/ static int recvraw(lua_State *L, p_buf buf, size_t wanted) @@ -167,7 +168,7 @@ int recvraw(lua_State *L, p_buf buf, size_t wanted) } /*-------------------------------------------------------------------------*\ -* Reads everything until the connection is closed +* Reads everything until the connection is closed (buffered) \*-------------------------------------------------------------------------*/ static int recvall(lua_State *L, p_buf buf) @@ -187,12 +188,12 @@ int recvall(lua_State *L, p_buf buf) /*-------------------------------------------------------------------------*\ * Reads a line terminated by a CR LF pair or just by a LF. The CR and LF -* are not returned by the function and are discarded from the buffer. +* are not returned by the function and are discarded from the buffer \*-------------------------------------------------------------------------*/ static int recvline(lua_State *L, p_buf buf) { - int err = 0; + int err = IO_DONE; luaL_Buffer b; luaL_buffinit(L, &b); while (err == IO_DONE) { @@ -215,7 +216,8 @@ int recvline(lua_State *L, p_buf buf) } /*-------------------------------------------------------------------------*\ -* Skips a given number of bytes in read buffer +* Skips a given number of bytes from read buffer. No data is read from the +* transport layer \*-------------------------------------------------------------------------*/ static void buf_skip(p_buf buf, size_t count) @@ -227,7 +229,7 @@ void buf_skip(p_buf buf, size_t count) /*-------------------------------------------------------------------------*\ * Return any data available in buffer, or get more data from transport layer -* if buffer is empty. +* if buffer is empty \*-------------------------------------------------------------------------*/ static int buf_get(p_buf buf, const char **data, size_t *count) @@ -245,3 +247,4 @@ int buf_get(p_buf buf, const char **data, size_t *count) *data = buf->data + buf->first; return err; } + diff --git a/src/buffer.h b/src/buffer.h index 1502ef0..12b90a0 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -1,21 +1,31 @@ +#ifndef BUF_H +#define BUF_H /*=========================================================================*\ -* Buffered input/output routines +* Input/Output interface for Lua programs +* LuaSocket toolkit +* +* Line patterns require buffering. Reading one character at a time involves +* too many system calls and is very slow. This module implements the +* LuaSocket interface for input/output on connected objects, as seen by +* Lua programs. +* +* Input is buffered. Output is *not* buffered because there was no simple +* way of making sure the buffered output data would ever be sent. +* +* The module is built on top of the I/O abstraction defined in io.h and the +* timeout management is done with the timeout.h interface. * * RCS ID: $Id$ \*=========================================================================*/ -#ifndef BUF_H -#define BUF_H - #include + #include "io.h" #include "timeout.h" /* buffer size in bytes */ #define BUF_SIZE 8192 -/*-------------------------------------------------------------------------*\ -* Buffer control structure -\*-------------------------------------------------------------------------*/ +/* buffer control structure */ typedef struct t_buf_ { p_io io; /* IO driver used for this buffer */ p_tm tm; /* timeout management for this buffer */ @@ -24,9 +34,6 @@ typedef struct t_buf_ { } t_buf; typedef t_buf *p_buf; -/*-------------------------------------------------------------------------*\ -* Exported functions -\*-------------------------------------------------------------------------*/ void buf_open(lua_State *L); void buf_init(p_buf buf, p_io io, p_tm tm); int buf_meth_send(lua_State *L, p_buf buf); diff --git a/src/ftp.lua b/src/ftp.lua index c48f2c7..9d75d2a 100644 --- a/src/ftp.lua +++ b/src/ftp.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- FTP support for the Lua language --- LuaSocket 1.5 toolkit. +-- LuaSocket toolkit. -- Author: Diego Nehab -- Conforming to: RFC 959, LTN7 -- RCS ID: $Id$ diff --git a/src/http.lua b/src/http.lua index d531a2f..4ef2c87 100644 --- a/src/http.lua +++ b/src/http.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- HTTP/1.1 client support for the Lua language. --- LuaSocket 1.5 toolkit. +-- LuaSocket toolkit. -- Author: Diego Nehab -- Conforming to: RFC 2616, LTN7 -- RCS ID: $Id$ diff --git a/src/inet.c b/src/inet.c index b7f3ae5..312a45b 100644 --- a/src/inet.c +++ b/src/inet.c @@ -1,8 +1,10 @@ /*=========================================================================*\ * Internet domain functions +* LuaSocket toolkit * * RCS ID: $Id$ \*=========================================================================*/ +#include #include #include @@ -16,7 +18,6 @@ \*=========================================================================*/ static int inet_global_toip(lua_State *L); static int inet_global_tohostname(lua_State *L); - static void inet_pushresolved(lua_State *L, struct hostent *hp); static luaL_reg func[] = { @@ -43,11 +44,6 @@ void inet_open(lua_State *L) /*-------------------------------------------------------------------------*\ * Returns all information provided by the resolver given a host name * or ip address -* Lua Input: address -* address: ip address or hostname to dns lookup -* Lua Returns -* On success: first IP address followed by a resolved table -* On error: nil, followed by an error message \*-------------------------------------------------------------------------*/ static int inet_global_toip(lua_State *L) { @@ -72,11 +68,6 @@ static int inet_global_toip(lua_State *L) /*-------------------------------------------------------------------------*\ * Returns all information provided by the resolver given a host name * or ip address -* Lua Input: address -* address: ip address or host name to reverse dns lookup -* Lua Returns -* On success: canonic name followed by a resolved table -* On error: nil, followed by an error message \*-------------------------------------------------------------------------*/ static int inet_global_tohostname(lua_State *L) { @@ -102,11 +93,6 @@ static int inet_global_tohostname(lua_State *L) \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Retrieves socket peer name -* Input: -* sock: socket -* Lua Returns -* On success: ip address and port of peer -* On error: nil \*-------------------------------------------------------------------------*/ int inet_meth_getpeername(lua_State *L, p_sock ps) { @@ -123,11 +109,6 @@ int inet_meth_getpeername(lua_State *L, p_sock ps) /*-------------------------------------------------------------------------*\ * Retrieves socket local name -* Input: -* sock: socket -* Lua Returns -* On success: local ip address and port -* On error: nil \*-------------------------------------------------------------------------*/ int inet_meth_getsockname(lua_State *L, p_sock ps) { @@ -147,8 +128,6 @@ int inet_meth_getsockname(lua_State *L, p_sock ps) \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Passes all resolver information to Lua as a table -* Input -* hp: hostent structure returned by resolver \*-------------------------------------------------------------------------*/ static void inet_pushresolved(lua_State *L, struct hostent *hp) { @@ -185,12 +164,6 @@ static void inet_pushresolved(lua_State *L, struct hostent *hp) /*-------------------------------------------------------------------------*\ * Tries to connect to remote address (address, port) -* Input -* ps: pointer to socket -* address: host name or ip address -* port: port number to bind to -* Returns -* NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ const char *inet_tryconnect(p_sock ps, const char *address, unsigned short port) @@ -224,12 +197,6 @@ const char *inet_tryconnect(p_sock ps, const char *address, /*-------------------------------------------------------------------------*\ * Tries to bind socket to (address, port) -* Input -* sock: pointer to socket -* address: host name or ip address -* port: port number to bind to -* Returns -* NULL in case of success, error message otherwise \*-------------------------------------------------------------------------*/ const char *inet_trybind(p_sock ps, const char *address, unsigned short port, int backlog) @@ -264,10 +231,6 @@ const char *inet_trybind(p_sock ps, const char *address, unsigned short port, /*-------------------------------------------------------------------------*\ * Tries to create a new inet socket -* Input -* sock: pointer to socket -* Returns -* NULL if successfull, error message on error \*-------------------------------------------------------------------------*/ const char *inet_trycreate(p_sock ps, int type) { @@ -299,3 +262,5 @@ int inet_aton(const char *cp, struct in_addr *inp) return 1; } #endif + + diff --git a/src/inet.h b/src/inet.h index 60be9e4..244a310 100644 --- a/src/inet.h +++ b/src/inet.h @@ -1,25 +1,30 @@ +#ifndef INET_H +#define INET_H /*=========================================================================*\ * Internet domain functions +* LuaSocket toolkit +* +* This module implements the creation and connection of internet domain +* sockets, on top of the socket.h interface, and the interface of with the +* resolver. +* +* The function inet_aton is provided for the platforms where it is not +* available. The module also implements the interface of the internet +* getpeername and getsockname functions as seen by Lua programs. +* +* The Lua functions toip and tohostname are also implemented here. * * RCS ID: $Id$ \*=========================================================================*/ -#ifndef INET_H -#define INET_H - #include #include "socket.h" -/*-------------------------------------------------------------------------*\ -* Exported functions -\*-------------------------------------------------------------------------*/ void inet_open(lua_State *L); - const char *inet_tryconnect(p_sock ps, const char *address, unsigned short port); const char *inet_trybind(p_sock ps, const char *address, unsigned short port, int backlog); const char *inet_trycreate(p_sock ps, int type); - int inet_meth_getpeername(lua_State *L, p_sock ps); int inet_meth_getsockname(lua_State *L, p_sock ps); @@ -27,4 +32,4 @@ int inet_meth_getsockname(lua_State *L, p_sock ps); int inet_aton(const char *cp, struct in_addr *inp); #endif -#endif /* INET_H_ */ +#endif /* INET_H */ diff --git a/src/io.c b/src/io.c index 902124a..2d62147 100644 --- a/src/io.c +++ b/src/io.c @@ -1,8 +1,48 @@ +/*=========================================================================*\ +* Input/Output abstraction +* LuaSocket toolkit +* +* RCS ID: $Id$ +\*=========================================================================*/ #include "io.h" +/*=========================================================================*\ +* Exported functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Initializes C structure +\*-------------------------------------------------------------------------*/ void io_init(p_io io, p_send send, p_recv recv, void *ctx) { io->send = send; io->recv = recv; io->ctx = ctx; } + +/*-------------------------------------------------------------------------*\ +* Translate error codes to Lua +\*-------------------------------------------------------------------------*/ +void io_pusherror(lua_State *L, int code) +{ + switch (code) { + case IO_DONE: + lua_pushnil(L); + break; + case IO_TIMEOUT: + lua_pushstring(L, "timeout"); + break; + case IO_LIMITED: + lua_pushstring(L, "limited"); + break; + case IO_CLOSED: + lua_pushstring(L, "closed"); + break; + case IO_REFUSED: + lua_pushstring(L, "refused"); + break; + default: + lua_pushstring(L, "unknown error"); + break; + } +} + diff --git a/src/io.h b/src/io.h index 30d445b..f56094a 100644 --- a/src/io.h +++ b/src/io.h @@ -1,16 +1,30 @@ #ifndef IO_H #define IO_H - +/*=========================================================================*\ +* Input/Output abstraction +* LuaSocket toolkit +* +* This module defines the interface that LuaSocket expects from the +* transport layer for streamed input/output. The idea is that if any +* transport implements this interface, then the buffer.c functions +* automatically work on it. +* +* The module socket.h implements this interface, and thus the module tcp.h +* is very simple. +* +* RCS ID: $Id$ +\*=========================================================================*/ #include +#include /* IO error codes */ enum { - IO_DONE, /* operation completed successfully */ - IO_TIMEOUT, /* operation timed out */ - IO_CLOSED, /* the connection has been closed */ - IO_ERROR, /* something wrong... */ - IO_REFUSED, /* transfer has been refused */ - IO_LIMITED /* maximum number of bytes reached */ + IO_DONE, /* operation completed successfully */ + IO_TIMEOUT, /* operation timed out */ + IO_CLOSED, /* the connection has been closed */ + IO_ERROR, /* something wrong... */ + IO_REFUSED, /* transfer has been refused */ + IO_LIMITED /* maximum number of bytes reached */ }; /* interface to send function */ @@ -39,6 +53,7 @@ typedef struct t_io_ { } t_io; typedef t_io *p_io; +void io_pusherror(lua_State *L, int code); void io_init(p_io io, p_send send, p_recv recv, void *ctx); #endif /* IO_H */ diff --git a/src/luasocket.c b/src/luasocket.c index 5541d7f..96deac1 100644 --- a/src/luasocket.c +++ b/src/luasocket.c @@ -1,4 +1,5 @@ /*=========================================================================*\ +* LuaSocket toolkit * Networking support for the Lua language * Diego Nehab * 26/11/1999 @@ -7,7 +8,7 @@ * connectivity of the Lua language. The Lua interface to networking * functions follows the Sockets API closely, trying to simplify all tasks * involved in setting up both client and server connections. The provided -* IO routines, however, follow the Lua style, being very similar to the +* IO routines, however, follow the Lua style, being very similar to the * standard Lua read and write functions. * * RCS ID: $Id$ @@ -24,6 +25,7 @@ \*=========================================================================*/ #include "luasocket.h" +#include "auxiliar.h" #include "timeout.h" #include "buffer.h" #include "socket.h" @@ -38,23 +40,11 @@ /*-------------------------------------------------------------------------*\ * Initializes all library modules. \*-------------------------------------------------------------------------*/ -LUASOCKET_API int luaopen_socketlib(lua_State *L) +LUASOCKET_API int luaopen_socket(lua_State *L) { if (!sock_open()) return 0; - /* create namespace table */ - lua_pushstring(L, LUASOCKET_LIBNAME); - lua_newtable(L); -#ifdef LUASOCKET_DEBUG - lua_pushstring(L, "debug"); - lua_pushnumber(L, 1); - lua_settable(L, -3); -#endif - lua_settable(L, LUA_GLOBALSINDEX); - /* make sure modules know what is our namespace */ - lua_pushstring(L, "LUASOCKET_LIBNAME"); - lua_pushstring(L, LUASOCKET_LIBNAME); - lua_settable(L, LUA_GLOBALSINDEX); /* initialize all modules */ + aux_open(L); tm_open(L); buf_open(L); inet_open(L); diff --git a/src/luasocket.h b/src/luasocket.h index 6c25af2..7756605 100644 --- a/src/luasocket.h +++ b/src/luasocket.h @@ -1,17 +1,19 @@ +#ifndef LUASOCKET_H +#define LUASOCKET_H /*=========================================================================*\ +* LuaSocket toolkit * Networking support for the Lua language * Diego Nehab * 9/11/1999 * * RCS ID: $Id$ \*=========================================================================*/ -#ifndef LUASOCKET_H -#define LUASOCKET_H +#include /*-------------------------------------------------------------------------*\ * Current luasocket version \*-------------------------------------------------------------------------*/ -#define LUASOCKET_VERSION "LuaSocket 1.5 (alpha)" +#define LUASOCKET_VERSION "LuaSocket 2.0 (alpha)" /*-------------------------------------------------------------------------*\ * Library's namespace @@ -28,6 +30,6 @@ /*-------------------------------------------------------------------------*\ * Initializes the library. \*-------------------------------------------------------------------------*/ -LUASOCKET_API int luaopen_socketlib(lua_State *L); +LUASOCKET_API int luaopen_socket(lua_State *L); #endif /* LUASOCKET_H */ diff --git a/src/select.c b/src/select.c index 3cabbd1..9769667 100644 --- a/src/select.c +++ b/src/select.c @@ -1,5 +1,7 @@ /*=========================================================================*\ * Select implementation +* LuaSocket toolkit +* * RCS ID: $Id$ \*=========================================================================*/ #include @@ -12,6 +14,9 @@ #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); @@ -31,6 +36,12 @@ static luaL_reg func[] = { {NULL, NULL} }; +/*=========================================================================*\ +* Internal function prototypes. +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ void select_open(lua_State *L) { /* get select auxiliar lua function from lua code and register @@ -45,6 +56,9 @@ void select_open(lua_State *L) aux_newclass(L, "select{fd_set}", set); } +/*=========================================================================*\ +* Global Lua functions +\*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Waits for a set of sockets until a condition is met or timeout. \*-------------------------------------------------------------------------*/ @@ -63,10 +77,10 @@ static int global_select(lua_State *L) lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, 1); /* pass fd_set objects */ - read_fd_set = lua_newuserdata(L, sizeof(fd_set)); + 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 = lua_newuserdata(L, sizeof(fd_set)); + 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 */ @@ -76,20 +90,9 @@ static int global_select(lua_State *L) return 3; } -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; -} - +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ static int meth_set(lua_State *L) { fd_set *set = (fd_set *) aux_checkclass(L, "select{fd_set}", 1); @@ -107,6 +110,23 @@ static int meth_isset(lua_State *L) return 1; } +/*=========================================================================*\ +* 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 void check_obj_tab(lua_State *L, int tabidx) { if (tabidx < 0) tabidx = lua_gettop(L) + tabidx + 1; diff --git a/src/select.h b/src/select.h index 9521fae..0e1eeb4 100644 --- a/src/select.h +++ b/src/select.h @@ -1,9 +1,19 @@ -/*=========================================================================*\ -* Select implementation -* RCS ID: $Id$ -\*=========================================================================*/ #ifndef SELECT_H #define SELECT_H +/*=========================================================================*\ +* Select implementation +* LuaSocket toolkit +* +* To make the code as simple as possible, the select function is +* implemented int Lua, with a few helper functions written in C. +* +* Each object that can be passed to the select function has to be in the +* group select{able} and export two methods: fd() and dirty(). Fd returns +* the descriptor to be passed to the select function. Dirty() should return +* true if there is data ready for reading (required for buffered input). +* +* RCS ID: $Id$ +\*=========================================================================*/ void select_open(lua_State *L); diff --git a/src/smtp.lua b/src/smtp.lua index 604f79b..209825b 100644 --- a/src/smtp.lua +++ b/src/smtp.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- SMTP support for the Lua language. --- LuaSocket 1.5 toolkit +-- LuaSocket toolkit -- Author: Diego Nehab -- Conforming to: RFC 821, LTN7 -- RCS ID: $Id$ diff --git a/src/socket.h b/src/socket.h index 70ebc52..c7db5f2 100644 --- a/src/socket.h +++ b/src/socket.h @@ -1,11 +1,16 @@ +#ifndef SOCK_H +#define SOCK_H /*=========================================================================*\ * Socket compatibilization module +* LuaSocket toolkit +* +* BSD Sockets and WinSock are similar, but there are a few irritating +* differences. Also, not all *nix platforms behave the same. This module +* (and the associated usocket.h and wsocket.h) factor these differences and +* creates a interface compatible with the io.h module. * * RCS ID: $Id$ \*=========================================================================*/ -#ifndef SOCK_H -#define SOCK_H - #include "io.h" /*=========================================================================*\ @@ -32,7 +37,6 @@ int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len, const char *sock_connect(p_sock ps, SA *addr, socklen_t addr_len); const char *sock_bind(p_sock ps, SA *addr, socklen_t addr_len); void sock_listen(p_sock ps, int backlog); - int sock_send(p_sock ps, const char *data, size_t count, size_t *sent, int timeout); int sock_recv(p_sock ps, char *data, size_t count, @@ -41,11 +45,8 @@ int sock_sendto(p_sock ps, const char *data, size_t count, size_t *sent, SA *addr, socklen_t addr_len, int timeout); int sock_recvfrom(p_sock ps, char *data, size_t count, size_t *got, SA *addr, socklen_t *addr_len, int timeout); - void sock_setnonblocking(p_sock ps); void sock_setblocking(p_sock ps); -void sock_setreuseaddr(p_sock ps); - const char *sock_hoststrerror(void); const char *sock_createstrerror(void); const char *sock_bindstrerror(void); diff --git a/src/tcp.c b/src/tcp.c index dc7683d..28f38f5 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -1,5 +1,6 @@ /*=========================================================================*\ * TCP object +* LuaSocket toolkit * * RCS ID: $Id$ \*=========================================================================*/ @@ -13,7 +14,6 @@ #include "auxiliar.h" #include "socket.h" #include "inet.h" -#include "error.h" #include "tcp.h" /*=========================================================================*\ @@ -28,9 +28,13 @@ static int meth_getpeername(lua_State *L); static int meth_receive(lua_State *L); static int meth_accept(lua_State *L); static int meth_close(lua_State *L); +static int meth_setoption(lua_State *L); static int meth_timeout(lua_State *L); static int meth_fd(lua_State *L); static int meth_dirty(lua_State *L); +static int opt_nodelay(lua_State *L); +static int opt_keepalive(lua_State *L); +static int opt_linger(lua_State *L); /* tcp object methods */ static luaL_reg tcp[] = { @@ -45,11 +49,21 @@ static luaL_reg tcp[] = { {"getsockname", meth_getsockname}, {"timeout", meth_timeout}, {"close", meth_close}, + {"setoption", meth_setoption}, + {"__gc", meth_close}, {"fd", meth_fd}, {"dirty", meth_dirty}, {NULL, NULL} }; +/* socket option handlers */ +static luaL_reg opt[] = { + {"keepalive", opt_keepalive}, + {"nodelay", opt_nodelay}, + {"linger", opt_linger}, + {NULL, NULL} +}; + /* functions in library namespace */ static luaL_reg func[] = { {"tcp", global_create}, @@ -71,6 +85,7 @@ void tcp_open(lua_State *L) aux_add2group(L, "tcp{server}", "tcp{any}"); aux_add2group(L, "tcp{client}", "tcp{client, server}"); aux_add2group(L, "tcp{server}", "tcp{client, server}"); + /* both server and client objects are selectable */ aux_add2group(L, "tcp{client}", "select{able}"); aux_add2group(L, "tcp{server}", "select{able}"); /* define library functions */ @@ -96,19 +111,81 @@ static int meth_receive(lua_State *L) return buf_meth_receive(L, &tcp->buf); } +/*-------------------------------------------------------------------------*\ +* Option handlers +\*-------------------------------------------------------------------------*/ +static int meth_setoption(lua_State *L) +{ + return aux_meth_setoption(L, opt); +} + +static int opt_boolean(lua_State *L, int level, int name) +{ + p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{any}", 1); + int val = aux_checkboolean(L, 2); + if (setsockopt(tcp->sock, level, name, (char *) &val, sizeof(val)) < 0) { + lua_pushnil(L); + lua_pushstring(L, "setsockopt failed"); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +/* disables the Nagle algorithm */ +static int opt_nodelay(lua_State *L) +{ + struct protoent *pe = getprotobyname("TCP"); + if (!pe) { + lua_pushnil(L); + lua_pushstring(L, "getprotobyname"); + return 2; + } + return opt_boolean(L, pe->p_proto, TCP_NODELAY); +} + +static int opt_keepalive(lua_State *L) +{ + return opt_boolean(L, SOL_SOCKET, SO_KEEPALIVE); +} + +int opt_linger(lua_State *L) +{ + p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{client}", 1); + struct linger li; + if (!lua_istable(L, 2)) + luaL_typerror(L, 2, lua_typename(L, LUA_TTABLE)); + lua_pushstring(L, "onoff"); + lua_gettable(L, 2); + if (!lua_isnumber(L, -1)) luaL_argerror(L, 2, "invalid onoff field"); + li.l_onoff = (int) lua_tonumber(L, -1); + lua_pushstring(L, "linger"); + lua_gettable(L, 2); + if (!lua_isnumber(L, -1)) luaL_argerror(L, 2, "invalid linger field"); + li.l_linger = (int) lua_tonumber(L, -1); + if (setsockopt(tcp->sock, SOL_SOCKET, SO_LINGER, + (char *) &li, sizeof(li) < 0)) { + lua_pushnil(L); + lua_pushstring(L, "setsockopt failed"); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + /*-------------------------------------------------------------------------*\ * Select support methods \*-------------------------------------------------------------------------*/ static int meth_fd(lua_State *L) { - p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{client, server}", 1); + p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{client, server}", 1); lua_pushnumber(L, tcp->sock); return 1; } static int meth_dirty(lua_State *L) { - p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{client, server}", 1); + p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{client, server}", 1); lua_pushboolean(L, !buf_isempty(&tcp->buf)); return 1; } @@ -207,7 +284,7 @@ static int meth_accept(lua_State *L) if (client->sock == SOCK_INVALID) { if (tm_get(tm) == 0) { lua_pushnil(L); - error_push(L, IO_TIMEOUT); + io_pusherror(L, IO_TIMEOUT); return 2; } } else break; diff --git a/src/tcp.h b/src/tcp.h index f4319f3..82b88a9 100644 --- a/src/tcp.h +++ b/src/tcp.h @@ -1,6 +1,21 @@ #ifndef TCP_H #define TCP_H - +/*=========================================================================*\ +* TCP object +* LuaSocket toolkit +* +* The tcp.h module is basicly a glue that puts together modules buffer.h, +* timeout.h socket.h and inet.h to provide the LuaSocket TCP (AF_INET, +* SOCK_STREAM) support. +* +* Three classes are defined: master, client and server. The master class is +* a newly created tcp object, that has not been bound or connected. Server +* objects are tcp objects bound to some local address. Client objects are +* tcp objects either connected to some address or returned by the accept +* method of a server object. +* +* RCS ID: $Id$ +\*=========================================================================*/ #include #include "buffer.h" @@ -17,4 +32,4 @@ typedef t_tcp *p_tcp; void tcp_open(lua_State *L); -#endif +#endif /* TCP_H */ diff --git a/src/timeout.c b/src/timeout.c index 1553069..6a30e3a 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -1,8 +1,6 @@ /*=========================================================================*\ * Timeout management functions -* Global Lua functions: -* _sleep -* _time +* LuaSocket toolkit * * RCS ID: $Id$ \*=========================================================================*/ diff --git a/src/timeout.h b/src/timeout.h index 43476cb..32eb836 100644 --- a/src/timeout.h +++ b/src/timeout.h @@ -1,11 +1,11 @@ +#ifndef TM_H +#define TM_H /*=========================================================================*\ * Timeout management functions +* LuaSocket toolkit * * RCS ID: $Id$ \*=========================================================================*/ -#ifndef TM_H -#define TM_H - #include /* timeout control structure */ @@ -28,4 +28,4 @@ int tm_get(p_tm tm); int tm_gettime(void); int tm_meth_timeout(lua_State *L, p_tm tm); -#endif +#endif /* TM_H */ diff --git a/src/udp.c b/src/udp.c index 79831e7..b772b2e 100644 --- a/src/udp.c +++ b/src/udp.c @@ -1,5 +1,6 @@ /*=========================================================================*\ * UDP object +* LuaSocket toolkit * * RCS ID: $Id$ \*=========================================================================*/ @@ -13,7 +14,6 @@ #include "auxiliar.h" #include "socket.h" #include "inet.h" -#include "error.h" #include "udp.h" /*=========================================================================*\ @@ -29,9 +29,12 @@ static int meth_getpeername(lua_State *L); static int meth_setsockname(lua_State *L); static int meth_setpeername(lua_State *L); static int meth_close(lua_State *L); +static int meth_setoption(lua_State *L); static int meth_timeout(lua_State *L); static int meth_fd(lua_State *L); static int meth_dirty(lua_State *L); +static int opt_dontroute(lua_State *L); +static int opt_broadcast(lua_State *L); /* udp object methods */ static luaL_reg udp[] = { @@ -45,11 +48,20 @@ static luaL_reg udp[] = { {"receivefrom", meth_receivefrom}, {"timeout", meth_timeout}, {"close", meth_close}, + {"setoption", meth_setoption}, + {"__gc", meth_close}, {"fd", meth_fd}, {"dirty", meth_dirty}, {NULL, NULL} }; +/* socket options */ +static luaL_reg opt[] = { + {"dontroute", opt_dontroute}, + {"broadcast", opt_broadcast}, + {NULL, NULL} +}; + /* functions in library namespace */ static luaL_reg func[] = { {"udp", global_create}, @@ -91,7 +103,9 @@ static int meth_send(lua_State *L) err = sock_send(&udp->sock, data, count, &sent, tm_get(tm)); if (err == IO_DONE) lua_pushnumber(L, sent); else lua_pushnil(L); - error_push(L, err); + /* a 'closed' error on an unconnected means the target address was not + * accepted by the transport layer */ + io_pusherror(L, err == IO_CLOSED ? IO_REFUSED : err); return 2; } @@ -118,7 +132,9 @@ static int meth_sendto(lua_State *L) (SA *) &addr, sizeof(addr), tm_get(tm)); if (err == IO_DONE) lua_pushnumber(L, sent); else lua_pushnil(L); - error_push(L, err == IO_CLOSED ? IO_REFUSED : err); + /* a 'closed' error on an unconnected means the target address was not + * accepted by the transport layer */ + io_pusherror(L, err == IO_CLOSED ? IO_REFUSED : err); return 2; } @@ -137,7 +153,7 @@ static int meth_receive(lua_State *L) err = sock_recv(&udp->sock, buffer, count, &got, tm_get(tm)); if (err == IO_DONE) lua_pushlstring(L, buffer, got); else lua_pushnil(L); - error_push(L, err); + io_pusherror(L, err); return 2; } @@ -164,7 +180,7 @@ static int meth_receivefrom(lua_State *L) return 3; } else { lua_pushnil(L); - error_push(L, err); + io_pusherror(L, err); return 2; } } @@ -174,14 +190,14 @@ static int meth_receivefrom(lua_State *L) \*-------------------------------------------------------------------------*/ static int meth_fd(lua_State *L) { - p_udp udp = (p_udp) aux_checkclass(L, "udp{any}", 1); + p_udp udp = (p_udp) aux_checkgroup(L, "udp{any}", 1); lua_pushnumber(L, udp->sock); return 1; } static int meth_dirty(lua_State *L) { - p_udp udp = (p_udp) aux_checkclass(L, "udp{any}", 1); + p_udp udp = (p_udp) aux_checkgroup(L, "udp{any}", 1); (void) udp; lua_pushboolean(L, 0); return 1; @@ -202,6 +218,37 @@ static int meth_getsockname(lua_State *L) return inet_meth_getsockname(L, &udp->sock); } +/*-------------------------------------------------------------------------*\ +* Option handlers +\*-------------------------------------------------------------------------*/ +static int meth_setoption(lua_State *L) +{ + return aux_meth_setoption(L, opt); +} + +static int opt_boolean(lua_State *L, int level, int name) +{ + p_udp udp = (p_udp) aux_checkgroup(L, "udp{any}", 1); + int val = aux_checkboolean(L, 2); + if (setsockopt(udp->sock, level, name, (char *) &val, sizeof(val)) < 0) { + lua_pushnil(L); + lua_pushstring(L, "setsockopt failed"); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +static int opt_dontroute(lua_State *L) +{ + return opt_boolean(L, SOL_SOCKET, SO_DONTROUTE); +} + +static int opt_broadcast(lua_State *L) +{ + return opt_boolean(L, SOL_SOCKET, SO_BROADCAST); +} + /*-------------------------------------------------------------------------*\ * Just call tm methods \*-------------------------------------------------------------------------*/ diff --git a/src/udp.h b/src/udp.h index a6f17e2..699e31a 100644 --- a/src/udp.h +++ b/src/udp.h @@ -1,6 +1,19 @@ #ifndef UDP_H #define UDP_H - +/*=========================================================================*\ +* UDP object +* LuaSocket toolkit +* +* The udp.h module provides LuaSocket with support for UDP protocol +* (AF_INET, SOCK_DGRAM). +* +* Two classes are defined: connected and unconnected. UDP objects are +* originally unconnected. They can be "connected" to a given address +* with a call to the setpeername function. The same function can be used to +* break the connection. +* +* RCS ID: $Id$ +\*=========================================================================*/ #include #include "timeout.h" @@ -16,4 +29,4 @@ typedef t_udp *p_udp; void udp_open(lua_State *L); -#endif +#endif /* UDP_H */ diff --git a/src/url.lua b/src/url.lua index 06de9d3..65da57a 100644 --- a/src/url.lua +++ b/src/url.lua @@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- URI parsing, composition and relative URL resolution --- LuaSocket 1.5 toolkit. +-- LuaSocket toolkit. -- Author: Diego Nehab -- Conforming to: RFC 2396, LTN7 -- RCS ID: $Id$ diff --git a/src/usocket.c b/src/usocket.c index 062a0ff..cdd550c 100644 --- a/src/usocket.c +++ b/src/usocket.c @@ -1,49 +1,78 @@ +/*=========================================================================*\ +* Socket compatibilization module for Unix +* LuaSocket toolkit +* +* RCS ID: $Id$ +\*=========================================================================*/ #include #include "socket.h" +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ int sock_open(void) { - /* instals a handler to ignore sigpipe. */ - struct sigaction new; - memset(&new, 0, sizeof(new)); - new.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &new, NULL); + /* instals a handler to ignore sigpipe or it will crash us */ + struct sigaction ignore; + memset(&ignore, 0, sizeof(ignore)); + ignore.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &ignore, NULL); return 1; } +/*-------------------------------------------------------------------------*\ +* Close and inutilize socket +\*-------------------------------------------------------------------------*/ void sock_destroy(p_sock ps) { close(*ps); + *ps = SOCK_INVALID; } +/*-------------------------------------------------------------------------*\ +* Creates and sets up a socket +\*-------------------------------------------------------------------------*/ const char *sock_create(p_sock ps, int domain, int type, int protocol) { + int val = 1; t_sock sock = socket(domain, type, protocol); if (sock == SOCK_INVALID) return sock_createstrerror(); *ps = sock; sock_setnonblocking(ps); - sock_setreuseaddr(ps); + setsockopt(*ps, SOL_SOCKET, SO_REUSEADDR, (char *) &val, sizeof(val)); return NULL; } +/*-------------------------------------------------------------------------*\ +* Connects or returns error message +\*-------------------------------------------------------------------------*/ const char *sock_connect(p_sock ps, SA *addr, socklen_t addr_len) { if (connect(*ps, addr, addr_len) < 0) return sock_connectstrerror(); else return NULL; } +/*-------------------------------------------------------------------------*\ +* Binds or returns error message +\*-------------------------------------------------------------------------*/ const char *sock_bind(p_sock ps, SA *addr, socklen_t addr_len) { if (bind(*ps, addr, addr_len) < 0) return sock_bindstrerror(); else return NULL; } +/*-------------------------------------------------------------------------*\ +* +\*-------------------------------------------------------------------------*/ void sock_listen(p_sock ps, int backlog) { listen(*ps, backlog); } +/*-------------------------------------------------------------------------*\ +* Accept with timeout +\*-------------------------------------------------------------------------*/ int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len, int timeout) { @@ -65,6 +94,9 @@ int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len, else return IO_DONE; } +/*-------------------------------------------------------------------------*\ +* Send with timeout +\*-------------------------------------------------------------------------*/ int sock_send(p_sock ps, const char *data, size_t count, size_t *sent, int timeout) { @@ -99,6 +131,9 @@ int sock_send(p_sock ps, const char *data, size_t count, size_t *sent, } } +/*-------------------------------------------------------------------------*\ +* Sendto with timeout +\*-------------------------------------------------------------------------*/ int sock_sendto(p_sock ps, const char *data, size_t count, size_t *sent, SA *addr, socklen_t addr_len, int timeout) { @@ -133,6 +168,9 @@ int sock_sendto(p_sock ps, const char *data, size_t count, size_t *sent, } } +/*-------------------------------------------------------------------------*\ +* Receive with timeout +\*-------------------------------------------------------------------------*/ int sock_recv(p_sock ps, char *data, size_t count, size_t *got, int timeout) { t_sock sock = *ps; @@ -160,6 +198,9 @@ int sock_recv(p_sock ps, char *data, size_t count, size_t *got, int timeout) } } +/*-------------------------------------------------------------------------*\ +* Recvfrom with timeout +\*-------------------------------------------------------------------------*/ int sock_recvfrom(p_sock ps, char *data, size_t count, size_t *got, SA *addr, socklen_t *addr_len, int timeout) { @@ -188,6 +229,29 @@ int sock_recvfrom(p_sock ps, char *data, size_t count, size_t *got, } } +/*-------------------------------------------------------------------------*\ +* Put socket into blocking mode +\*-------------------------------------------------------------------------*/ +void sock_setblocking(p_sock ps) +{ + int flags = fcntl(*ps, F_GETFL, 0); + flags &= (~(O_NONBLOCK)); + fcntl(*ps, F_SETFL, flags); +} + +/*-------------------------------------------------------------------------*\ +* Put socket into non-blocking mode +\*-------------------------------------------------------------------------*/ +void sock_setnonblocking(p_sock ps) +{ + int flags = fcntl(*ps, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(*ps, F_SETFL, flags); +} + +/*-------------------------------------------------------------------------*\ +* Error translation functions +\*-------------------------------------------------------------------------*/ const char *sock_hoststrerror(void) { switch (h_errno) { @@ -238,23 +302,3 @@ const char *sock_connectstrerror(void) default: return "unknown error"; } } - -void sock_setreuseaddr(p_sock ps) -{ - int val = 1; - setsockopt(*ps, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)); -} - -void sock_setblocking(p_sock ps) -{ - int flags = fcntl(*ps, F_GETFL, 0); - flags &= (~(O_NONBLOCK)); - fcntl(*ps, F_SETFL, flags); -} - -void sock_setnonblocking(p_sock ps) -{ - int flags = fcntl(*ps, F_GETFL, 0); - flags |= O_NONBLOCK; - fcntl(*ps, F_SETFL, flags); -} diff --git a/src/usocket.h b/src/usocket.h index 034ae74..85b7caa 100644 --- a/src/usocket.h +++ b/src/usocket.h @@ -1,10 +1,11 @@ +#ifndef USOCKET_H +#define USOCKET_H /*=========================================================================*\ * Socket compatibilization module for Unix +* LuaSocket toolkit * * RCS ID: $Id$ \*=========================================================================*/ -#ifndef USOCKET_H -#define USOCKET_H /*=========================================================================*\ * BSD include files @@ -30,9 +31,11 @@ /* IP stuff*/ #include #include +/* TCP options (nagle algorithm disable) */ +#include #ifdef __APPLE__ -/* for some reason socklen_t is not defined in mac os x */ +/* for some reason socklen_t is not defined in Mac Os X */ typedef int socklen_t; #endif diff --git a/src/wsocket.c b/src/wsocket.c index 56e65ec..2ce828e 100644 --- a/src/wsocket.c +++ b/src/wsocket.c @@ -1,14 +1,21 @@ +/*=========================================================================*\ +* Socket compatibilization module for Win32 +* LuaSocket toolkit +* +* RCS ID: $Id$ +\*=========================================================================*/ #include #include "socket.h" +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ int sock_open(void) { - WORD wVersionRequested; WSADATA wsaData; - int err; - wVersionRequested = MAKEWORD(2, 0); - err = WSAStartup(wVersionRequested, &wsaData ); + WORD wVersionRequested = MAKEWORD(2, 0); + int err = WSAStartup(wVersionRequested, &wsaData ); if (err != 0) return 0; if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0) { WSACleanup(); @@ -17,38 +24,58 @@ int sock_open(void) return 1; } +/*-------------------------------------------------------------------------*\ +* Close and inutilize socket +\*-------------------------------------------------------------------------*/ void sock_destroy(p_sock ps) { closesocket(*ps); + *ps = SOCK_INVALID; } +/*-------------------------------------------------------------------------*\ +* Creates and sets up a socket +\*-------------------------------------------------------------------------*/ const char *sock_create(p_sock ps, int domain, int type, int protocol) { + int val = 1; t_sock sock = socket(domain, type, protocol); if (sock == SOCK_INVALID) return sock_createstrerror(); *ps = sock; sock_setnonblocking(ps); - sock_setreuseaddr(ps); + setsockopt(*ps, SOL_SOCKET, SO_REUSEADDR, (char *) &val, sizeof(val)); return NULL; } +/*-------------------------------------------------------------------------*\ +* Connects or returns error message +\*-------------------------------------------------------------------------*/ const char *sock_connect(p_sock ps, SA *addr, socklen_t addr_len) { if (connect(*ps, addr, addr_len) < 0) return sock_connectstrerror(); else return NULL; } +/*-------------------------------------------------------------------------*\ +* Binds or returns error message +\*-------------------------------------------------------------------------*/ const char *sock_bind(p_sock ps, SA *addr, socklen_t addr_len) { if (bind(*ps, addr, addr_len) < 0) return sock_bindstrerror(); else return NULL; } +/*-------------------------------------------------------------------------*\ +* +\*-------------------------------------------------------------------------*/ void sock_listen(p_sock ps, int backlog) { listen(*ps, backlog); } +/*-------------------------------------------------------------------------*\ +* Accept with timeout +\*-------------------------------------------------------------------------*/ int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len, int timeout) { @@ -70,6 +97,9 @@ int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len, else return IO_DONE; } +/*-------------------------------------------------------------------------*\ +* Send with timeout +\*-------------------------------------------------------------------------*/ int sock_send(p_sock ps, const char *data, size_t count, size_t *sent, int timeout) { @@ -104,6 +134,9 @@ int sock_send(p_sock ps, const char *data, size_t count, size_t *sent, } } +/*-------------------------------------------------------------------------*\ +* Sendto with timeout +\*-------------------------------------------------------------------------*/ int sock_sendto(p_sock ps, const char *data, size_t count, size_t *sent, SA *addr, socklen_t addr_len, int timeout) { @@ -138,6 +171,9 @@ int sock_sendto(p_sock ps, const char *data, size_t count, size_t *sent, } } +/*-------------------------------------------------------------------------*\ +* Receive with timeout +\*-------------------------------------------------------------------------*/ int sock_recv(p_sock ps, char *data, size_t count, size_t *got, int timeout) { t_sock sock = *ps; @@ -165,6 +201,9 @@ int sock_recv(p_sock ps, char *data, size_t count, size_t *got, int timeout) } } +/*-------------------------------------------------------------------------*\ +* Recvfrom with timeout +\*-------------------------------------------------------------------------*/ int sock_recvfrom(p_sock ps, char *data, size_t count, size_t *got, SA *addr, socklen_t *addr_len, int timeout) { @@ -193,6 +232,27 @@ int sock_recvfrom(p_sock ps, char *data, size_t count, size_t *got, } } +/*-------------------------------------------------------------------------*\ +* Put socket into blocking mode +\*-------------------------------------------------------------------------*/ +void sock_setblocking(p_sock ps) +{ + u_long argp = 0; + ioctlsocket(*ps, FIONBIO, &argp); +} + +/*-------------------------------------------------------------------------*\ +* Put socket into non-blocking mode +\*-------------------------------------------------------------------------*/ +void sock_setnonblocking(p_sock ps) +{ + u_long argp = 1; + ioctlsocket(*ps, FIONBIO, &argp); +} + +/*-------------------------------------------------------------------------*\ +* Error translation functions +\*-------------------------------------------------------------------------*/ const char *sock_hoststrerror(void) { switch (WSAGetLastError()) { @@ -241,21 +301,3 @@ const char *sock_connectstrerror(void) default: return "unknown error"; } } - -void sock_setreuseaddr(p_sock ps) -{ - int val = 1; - setsockopt(*ps, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)); -} - -void sock_setblocking(p_sock ps) -{ - u_long argp = 0; - ioctlsocket(*ps, FIONBIO, &argp); -} - -void sock_setnonblocking(p_sock ps) -{ - u_long argp = 1; - ioctlsocket(*ps, FIONBIO, &argp); -} diff --git a/src/wsocket.h b/src/wsocket.h index ceecae7..d77841e 100644 --- a/src/wsocket.h +++ b/src/wsocket.h @@ -1,16 +1,16 @@ +#ifndef WSOCKET_H +#define WSOCKET_H /*=========================================================================*\ * Socket compatibilization module for Win32 +* LuaSocket toolkit * * RCS ID: $Id$ \*=========================================================================*/ -#ifndef WSOCKET_H -#define WSOCKET_H /*=========================================================================*\ -* WinSock2 include files +* WinSock include files \*=========================================================================*/ -#include -#include +#include typedef int socklen_t; typedef int ssize_t; diff --git a/test/testclnt.lua b/test/testclnt.lua index b2b4b18..e38c248 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -17,12 +17,14 @@ function warn(...) io.write("WARNING: ", s, "\n") end +pad = string.rep(" ", 8192) + function remote(...) local s = string.format(unpack(arg)) s = string.gsub(s, "\n", ";") s = string.gsub(s, "%s+", " ") s = string.gsub(s, "^%s*", "") - control:send(s, "\n") + control:send(pad, s, "\n") control:receive() end @@ -82,16 +84,19 @@ function reconnect() remote [[ if data then data:close() data = nil end data = server:accept() + data:setoption("nodelay", true) ]] data, err = socket.connect(host, port) if not data then fail(err) else pass("connected!") end + data:setoption("nodelay", true) end pass("attempting control connection...") control, err = socket.connect(host, port) if err then fail(err) else pass("connected!") end +control:setoption("nodelay", true) ------------------------------------------------------------------------ test("method registration") @@ -157,16 +162,21 @@ remote "data:send(str); data:close()" end -test_mixed(1) -test_mixed(17) -test_mixed(200) -test_mixed(4091) -test_mixed(80199) -test_mixed(4091) -test_mixed(200) -test_mixed(17) -test_mixed(1) +--test_mixed(1) +--test_mixed(17) +--test_mixed(200) +--test_mixed(4091) +--test_mixed(80199) +--test_mixed(4091) +--test_mixed(200) +--test_mixed(17) +--test_mixed(1) +test_mixed(4091) +test_mixed(4091) +test_mixed(4091) +test_mixed(4091) +test_mixed(4091) ------------------------------------------------------------------------ test("character line") reconnect() diff --git a/test/testsrvr.lua b/test/testsrvr.lua index 3c40840..39fe274 100644 --- a/test/testsrvr.lua +++ b/test/testsrvr.lua @@ -3,9 +3,11 @@ port = port or "8080" server, error = socket.bind(host, port) if not server then print("server: " .. tostring(error)) os.exit() end +ack = "\n" while 1 do print("server: waiting for client connection..."); control = server:accept() + control:setoption("nodelay", true) while 1 do command, error = control:receive() if error then @@ -13,13 +15,12 @@ while 1 do print("server: closing connection...") break end - sent, error = control:send("\n") + sent, error = control:send(ack) if error then control:close() print("server: closing connection...") break end - print(command); (loadstring(command))() end end From bd54cd42d4c8a7d6bb9550d8aa0eac243cda63c0 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Thu, 26 Jun 2003 21:14:17 +0000 Subject: [PATCH 126/483] Few adjustments for windows. --- etc/get.lua | 4 ++-- test/testclnt.lua | 6 +++--- test/testsrvr.lua | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/etc/get.lua b/etc/get.lua index caaa607..a093e24 100644 --- a/etc/get.lua +++ b/etc/get.lua @@ -63,13 +63,13 @@ end -- kind of copied from luasocket's manual callback examples function receive2disk(file, size) local aux = { - start = socket._time(), + start = socket.time(), got = 0, file = io.open(file, "wb"), size = size } local receive_cb = function(chunk, err) - local dt = socket._time() - aux.start -- elapsed time since start + local dt = socket.time() - aux.start -- elapsed time since start if not chunk or chunk == "" then io.write("\n") aux.file:close() diff --git a/test/testclnt.lua b/test/testclnt.lua index e38c248..20ef0b6 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -84,19 +84,19 @@ function reconnect() remote [[ if data then data:close() data = nil end data = server:accept() - data:setoption("nodelay", true) + -- data:setoption("nodelay", true) ]] data, err = socket.connect(host, port) if not data then fail(err) else pass("connected!") end - data:setoption("nodelay", true) + -- data:setoption("nodelay", true) end pass("attempting control connection...") control, err = socket.connect(host, port) if err then fail(err) else pass("connected!") end -control:setoption("nodelay", true) +-- control:setoption("nodelay", true) ------------------------------------------------------------------------ test("method registration") diff --git a/test/testsrvr.lua b/test/testsrvr.lua index 39fe274..6010789 100644 --- a/test/testsrvr.lua +++ b/test/testsrvr.lua @@ -7,7 +7,7 @@ ack = "\n" while 1 do print("server: waiting for client connection..."); control = server:accept() - control:setoption("nodelay", true) + -- control:setoption("nodelay", true) while 1 do command, error = control:receive() if error then From 167727be3cefb3f45a26b0b1d96dc94a900a84b4 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 30 Jun 2003 06:10:02 +0000 Subject: [PATCH 127/483] Bug in usocket_recv... --- makefile.dist | 66 ++++++++++++++++++++++++--------------------- samples/tinyirc.lua | 42 +++++++++++++++-------------- src/auxiliar.c | 5 ++++ src/inet.c | 1 - 4 files changed, 62 insertions(+), 52 deletions(-) diff --git a/makefile.dist b/makefile.dist index e493305..9cf2757 100644 --- a/makefile.dist +++ b/makefile.dist @@ -2,45 +2,49 @@ # Distribution makefile #-------------------------------------------------------------------------- -DIST = luasocket-1.5-alpha +DIST = luasocket-2.0-alpha LUA = \ - concat.lua \ - code.lua \ - url.lua \ - http.lua \ - smtp.lua \ - ftp.lua \ - select.lua \ - luasocket.lua +auxiliar.lua +code.lua +concat.lua +ftp.lua TESTS = \ - testclnt.lua \ - testsrvr.lua \ - testcmd.lua \ - codetest.lua \ - urltest.lua \ - concattest.lua \ - ftptest.lua \ - httptest.lua \ - smtptest.lua \ - mbox.lua \ - udptest.lua +codetest.lua +concattest.lua +ftptest.lua EXAMPLES = \ - check-links.lua \ - daytimeclnt.lua \ - echoclnt.lua \ - echosrvr.lua \ - get.lua \ - listener.lua \ - talker.lua \ - tinyirc.lua +check-links.lua +daytimeclnt.lua +echoclnt.lua +echosrvr.lua +dict.lua ETC = \ - cl-compat.lua \ - tftp.lua \ - dict.lua +cl-compat.lua + +get.lua +http.lua +httptest.lua +listener.lua +lua.lua +luasocket.lua +mbox.lua +noglobals.lua +select.lua +smtp.lua +smtptest.lua +talker.lua +testclnt.lua +test.lua +testsrvr.lua +tftp.lua +tinyirc.lua +udptest.lua +url.lua +urltest.lua MAIN = \ auxiliar.c \ diff --git a/samples/tinyirc.lua b/samples/tinyirc.lua index 0ad00ab..d9cb896 100644 --- a/samples/tinyirc.lua +++ b/samples/tinyirc.lua @@ -6,7 +6,7 @@ ----------------------------------------------------------------------------- host = host or "*" port1 = port1 or 8080 -port2 = port2 or 8081 +port2 = port2 or 8181 if arg then host = arg[1] or host port1 = arg[2] or port1 @@ -15,11 +15,16 @@ end server1, error = socket.bind(host, port1) assert(server1, error) -server1:timeout(1) +server1:timeout(1) -- make sure we don't block in accept server2, error = socket.bind(host, port2) assert(server2, error) -server2:timeout(1) +server2:timeout(1) -- make sure we don't block in accept +io.write("Servers bound\n") + +-- simple set implementation +-- the select function doesn't care about what is passed to it as long as +-- it behaves like a table function newset() local reverse = {} local set = {} @@ -32,46 +37,43 @@ function newset() table.remove(set, reverse[value]) reverse[value] = nil end, - id = function(set, value) - return reverse[value] - end }}) return set end -sockets = newset() +set = newset() -sockets:insert(server1) -sockets:insert(server2) +io.write("Inserting servers in set\n") +set:insert(server1) +set:insert(server2) while 1 do - local readable, _, error = socket.select(sockets, nil) + local readable, _, error = socket.select(set, nil) for _, input in readable do -- is it a server socket? - local id = sockets:id(input) if input == server1 or input == server2 then + io.write("Waiting for clients\n") local new = input:accept() if new then new:timeout(1) - sockets:insert(new) - io.write("Server ", id, " got client ", sockets:id(new), "\n") + io.write("Inserting client in set\n") + set:insert(new) end -- it is a client socket else local line, error = input:receive() if error then input:close() - io.write("Removing client ", id, "\n") - sockets:remove(input) + io.write("Removing client from set\n") + set:remove(input) else - io.write("Broadcasting line '", id, "> ", line, "'.\n") - __, writable, error = socket.select(nil, sockets, 1) + io.write("Broadcasting line '", line, "'\n") + __, writable, error = socket.select(nil, set, 1) if not error then for ___, output in writable do - io.write("Sending to client ", sockets:id(output), "\n") - output:send(id, "> ", line, "\r\n") + output:send(line .. "\n") end - else io.write("No one ready to listen!!!\n") end + else io.write("No client ready to receive!!!\n") end end end end diff --git a/src/auxiliar.c b/src/auxiliar.c index 8b2fa37..65425c5 100644 --- a/src/auxiliar.c +++ b/src/auxiliar.c @@ -25,6 +25,11 @@ void aux_open(lua_State *L) lua_pushnumber(L, 1); lua_rawset(L, -3); #endif + /* make version string available so scripts */ + lua_pushstring(L, "version"); + lua_pushstring(L, LUASOCKET_VERSION); + lua_rawset(L, -3); + /* store namespace as global */ lua_settable(L, LUA_GLOBALSINDEX); /* make sure modules know what is our namespace */ lua_pushstring(L, "LUASOCKET_LIBNAME"); diff --git a/src/inet.c b/src/inet.c index 312a45b..574399c 100644 --- a/src/inet.c +++ b/src/inet.c @@ -177,7 +177,6 @@ const char *inet_tryconnect(p_sock ps, const char *address, if (!strlen(address) || !inet_aton(address, &remote.sin_addr)) { struct hostent *hp = gethostbyname(address); struct in_addr **addr; - remote.sin_family = AF_INET; if (!hp) return sock_hoststrerror(); addr = (struct in_addr **) hp->h_addr_list; memcpy(&remote.sin_addr, *addr, sizeof(struct in_addr)); From a581d2333be87796e2758806d38880e3e2be8a78 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 30 Jun 2003 18:53:10 +0000 Subject: [PATCH 128/483] Releasing 2.0 alpha!!! --- NEW | 25 +++++++++------ README | 9 ++---- TODO | 4 --- makefile.dist | 78 +++++++++++++++++++++++------------------------ test/testclnt.lua | 34 ++++++++++----------- 5 files changed, 74 insertions(+), 76 deletions(-) diff --git a/NEW b/NEW index e5efc97..34eac97 100644 --- a/NEW +++ b/NEW @@ -1,17 +1,21 @@ -All functions provided by the library are in the namespace "socket". -Functions such as send/receive/timeout/close etc do not exist in the -namespace. They are now only available as methods of the appropriate -objects. +Major C code rewrite. Code is modular and extensible. Hopefully, next +versions will include code for local domain sockets, file descriptors, +pipes (on unix) and named pipes (on windows) as a bonus. -Object has been changed to become more uniform. First create an object for -a given domain/family and protocol. Then connect or bind if needed. Then -use IO functions. +All functions provided by the library are in the namespace "socket". +Functions such as send/receive/timeout/close etc do not exist anymore as +stand alone functions. They are now only available as methods of the +appropriate objects. + +TCP has been changed to become more uniform. First create an object, then +connect or bind if needed. Then use IO functions. The "socket.connect" and +"socket.bind" functions are provided for simplicity, but they just call +"socket.tcp" followed by the ":connect" or ":bind" methods. All functions return a non-nil value as first return value if successful. All functions return whatever could be retrieved followed by error message in case of error. The best way to check for errors is to check for the -presence of an error message. -WARNING: The send function was affected. +presence of an error message. WARNING: The send function was affected. Better error messages and parameter checking. @@ -20,3 +24,6 @@ setpeername with address "*". socket.sleep and socket.time are now part of the library and are supported. + +Socket options interface has been improved and TCP now also supports socket +options. diff --git a/README b/README index 091ce6f..611d2a2 100644 --- a/README +++ b/README @@ -1,12 +1,9 @@ This release is work in progress. It has been tested on WinXP, Mac OS X, -SunOS and Linux. The most important change is a major rewrite of the C code -that attempts to make the library extensible. Also, all functions were -moved to the 'socket' namespace. A few changes are expected for the final -version, mostly in order to make the API more uniform. +SunOS and Linux. -In this version, all Lua code has been built into the binary. For that, you +In this version, all Lua code should be built into the binary. For that, you will need a working versions of luac and bin2c, both available with your -Lua distribution. Check makefile for details. +Lua distribution. Check the makefile for details. Have fun, Diego Nehab. diff --git a/TODO b/TODO index 5c2492d..ffc1ab0 100644 --- a/TODO +++ b/TODO @@ -1,8 +1,4 @@ - Melhorar a interface de setoptions (aceitar nada como true, por exemplo) -- Inicializaccao das classes pode falhar? -- Ajeitar melhor a hierarquia de classes. Ajeitar o file... -- GARBAGE COLLECTOR! -- Adicionar um método sock:setoption??? - testar em várias plataformas - adicionar exemplos de expansão: pipe, local, named pipe diff --git a/makefile.dist b/makefile.dist index 9cf2757..353467b 100644 --- a/makefile.dist +++ b/makefile.dist @@ -5,46 +5,45 @@ DIST = luasocket-2.0-alpha LUA = \ -auxiliar.lua -code.lua -concat.lua -ftp.lua + auxiliar.lua \ + code.lua \ + concat.lua \ + ftp.lua \ + http.lua \ + select.lua \ + smtp.lua \ + url.lua TESTS = \ -codetest.lua -concattest.lua -ftptest.lua + codetest.lua \ + concattest.lua \ + ftptest.lua \ + mbox.lua \ + httptest.lua \ + noglobals.lua \ + smtptest.lua \ + testclnt.lua \ + testsrvr.lua \ + udptest.lua \ + urltest.lua EXAMPLES = \ -check-links.lua -daytimeclnt.lua -echoclnt.lua -echosrvr.lua -dict.lua + daytimeclnt.lua \ + echoclnt.lua \ + echosrvr.lua \ + dict.lua \ + listener.lua \ + talker.lua \ + tinyirc.lua ETC = \ -cl-compat.lua + check-links.lua \ + cl-compat.lua \ + get.lua \ + lua.lua \ + luasocket.lua \ + tftp.lua \ -get.lua -http.lua -httptest.lua -listener.lua -lua.lua -luasocket.lua -mbox.lua -noglobals.lua -select.lua -smtp.lua -smtptest.lua -talker.lua -testclnt.lua -test.lua -testsrvr.lua -tftp.lua -tinyirc.lua -udptest.lua -url.lua -urltest.lua MAIN = \ auxiliar.c \ @@ -57,10 +56,8 @@ MAIN = \ inet.h \ io.c \ io.h \ - lua.c \ luasocket.c \ luasocket.h \ - makefile \ select.c \ select.h \ socket.h \ @@ -73,23 +70,24 @@ MAIN = \ usocket.c \ usocket.h \ wsocket.c \ - wsocket.h \ + wsocket.h dist: mkdir -p $(DIST)/examples mkdir -p $(DIST)/tests mkdir -p $(DIST)/etc - mkdir -p $(DIST)/lua cp -vf $(MAIN) $(DIST) cp -vf $(LUA) $(DIST) cp -vf makefile $(DIST) cp -vf README $(DIST) + cp -vf lua.README $(DIST) + cp -vf NEW $(DIST) cp -vf $(EXAMPLES) $(DIST)/examples - cp -vf README.examples $(DIST)/examples/README + cp -vf examples.README $(DIST)/examples/README cp -vf $(TESTS) $(DIST)/tests - cp -vf README.tests $(DIST)/tests/README + cp -vf tests.README $(DIST)/tests/README cp -vf $(ETC) $(DIST)/etc - cp -vf README.etc $(DIST)/etc/README + cp -vf etc.README $(DIST)/etc/README tar -zcvf $(DIST).tar.gz $(DIST) zip -r $(DIST).zip $(DIST) diff --git a/test/testclnt.lua b/test/testclnt.lua index 20ef0b6..b58ca2b 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -120,6 +120,7 @@ test_methods(socket.tcp(), { "setsockname", "getpeername", "getsockname", + "setoption", "timeout", "close", }) @@ -133,6 +134,7 @@ test_methods(socket.udp(), { "sendto", "receive", "receivefrom", + "setoption", "timeout", "close", }) @@ -162,21 +164,16 @@ remote "data:send(str); data:close()" end ---test_mixed(1) ---test_mixed(17) ---test_mixed(200) ---test_mixed(4091) ---test_mixed(80199) ---test_mixed(4091) ---test_mixed(200) ---test_mixed(17) ---test_mixed(1) +test_mixed(1) +test_mixed(17) +test_mixed(200) +test_mixed(4091) +test_mixed(80199) +test_mixed(4091) +test_mixed(200) +test_mixed(17) +test_mixed(1) -test_mixed(4091) -test_mixed(4091) -test_mixed(4091) -test_mixed(4091) -test_mixed(4091) ------------------------------------------------------------------------ test("character line") reconnect() @@ -203,6 +200,11 @@ test_asciiline(200) test_asciiline(4091) test_asciiline(80199) test_asciiline(800000) +test_asciiline(80199) +test_asciiline(4091) +test_asciiline(200) +test_asciiline(17) +test_asciiline(1) ------------------------------------------------------------------------ test("binary line") @@ -479,9 +481,7 @@ function test_selectbugs() pass("invalid input: ok") end --- test_selectbugs() - - +test_selectbugs() test(string.format("done in %.2fs", socket.time() - start)) From d9a1bdbf69db0470377498bc51e51768f6cee970 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Mon, 30 Jun 2003 18:55:58 +0000 Subject: [PATCH 129/483] Forgot this. --- etc/README | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 etc/README diff --git a/etc/README b/etc/README new file mode 100644 index 0000000..34b7074 --- /dev/null +++ b/etc/README @@ -0,0 +1,58 @@ +This directory contains code that is more useful than the examples. This code +*is* supported. + + lua.lua and luasocket.lua + +These are modules to suport dynamic loading of LuaSocket by the stand alone +Lua Interpreter with the use of the "require" function. For my Mac OS X +system, I place lua.lua in /usr/local/lua, luasocket.lua and the +libluasocket.dylib in /usr/local/lua/luasocket and set the following +environment variables: + + LUA_PATH=/usr/local/lua/?/?.lua + LUA_INIT=@/usr/local/lua/lua.lua + LUA_FUNCNAME=_? + LUA_LIBNAME=/usr/local/lua/?/lib?.dylib + +With that, I can run any luasocket application with the command line: + + lua -l luasocket