diff --git a/FIX b/FIX
index 0ec76a1..3d0b3de 100644
--- a/FIX
+++ b/FIX
@@ -14,3 +14,4 @@ fixed a bug in select.c that prevented sockets with descriptor 0 from working (R
fixed a "bug" that caused dns.toip to crash under uLinux
fixed a "bug" that caused a crash in gethostbyname under VMS
DEBUG and VERSION became _DEBUG and _VERSION
+send returns the right value if input is "". Alexander Marinov
diff --git a/config b/config
new file mode 100644
index 0000000..dcc3955
--- /dev/null
+++ b/config
@@ -0,0 +1,47 @@
+#------
+# LuaSocket makefile configuration
+#
+
+#------
+# Output file names
+#
+EXT=so
+SOCKET_V=2.0.0
+MIME_V=1.0.0
+SOCKET_SO=socket-core.$(EXT).$(SOCKET_V)
+MIME_SO=mime-core.$(EXT).$(MIME_V)
+UNIX_SO=unix.$(EXT)
+
+#------
+# Lua includes and libraries
+#
+LUAINC=
+LUALIB=
+
+#------
+# Compat-5.1 directory
+#
+COMPAT=compat-5.1r3
+
+#------
+# Top of your Lua installation
+# Relative paths will be inside src tree
+#
+INSTALL_TOP=/usr/local/share/lua/5.0
+
+INSTALL_DATA=cp
+INSTALL_EXEC=cp
+INSTALL_LINK=ln
+
+#------
+# Compiler and linker settings
+#
+CC=gcc
+DEF=-DLUASOCKET_DEBUG -DUNIX_HAS_SUN_LEN
+CFLAGS= $(LUAINC) -I$(COMPAT) $(DEF) -pedantic -Wall -O2
+LDFLAGS=-bundle -undefined dynamic_lookup
+LD=export MACOSX_DEPLOYMENT_TARGET="10.3"; gcc
+
+#------
+# End of makefile configuration
+#
diff --git a/doc/index.html b/doc/index.html
index ee97e02..933fa5f 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -165,32 +165,35 @@ support.
-- Improved: tcp{client}:send(data, i) now returns (i+sent-1). This is great for non-blocking I/O, but might break some code;
-
- Improved: HTTP, SMTP, and FTP functions accept a new field
-connect that can be used to replace the function invoked to
-create and connect the sockets used internally;
-
- Fixed: url.absolute() was not working when base_url was
-already parsed;
-
- Fixed: http.request() was redirecting even when the location
-header was empty (well, it shouldn't be empty);
-
- Fixed: tcp{client}:shutdown() was checking for group instead of class;
-
- Fixed: socket.try() can't be used in place of assert(). The manual and examples don't do it anymore;
-
- Improved: Get rid of require("base") kludge in package.loaded;
-
- Fixedd: Parts of the manual referred to require("http") instead of
+
- Improved: tcp:send(data, i, j) to return (i+sent-1). This is great for non-blocking I/O, but might break some code;
+
- Improved: HTTP, SMTP, and FTP functions to accept a new field
+create that overrides the function used to create socket objects;
+
- Fixed: url.absolute() to work when base_url is in
+parsed form;
+
- Fixed: http.request() not to redirect when the location
+header is empty (naughty servers...);
+
- Fixed: tcp{client}:shutdown() to check for class instead of
+group;
+
- Fixed: The manual to stop using socket.try() in place of
+assert(), since it can't;
+
- Improved: Got rid of package.loaded.base = _G kludge;
+
- Fixed: Parts of the manual referred to require("http") instead of
require("socket.http");
-
- Improved: Changed 'l' prefix in C libraries to 'c' to avoid clash with LHF
-libraries;
-
- Improved: Using bundles in Mac OS X;
-
- Fixed: luasocket.h was exporting luaopen_socket
-instead of luaopen_csocket;
-
- Fixed: udp:setpeername() only worked for
-udp{unconnected}. Now you can "disconnect" an UDP socket;
-
- Fixed: bug in http.lua that caused some requests to fail (Florian
-Berger);
-
- Fixed: bug in select.c that prevented sockets with descriptor 0 from working (Renato Maia);
-
- Fixed: "bug" that caused dns.toip to crash under uLinux;
-
- Fixed: "bug" that caused a crash in gethostbyname under VMS
+
- Improved: Socket and MIME binaries are called 'core' each inside their
+directory (ex. "socket/core.dll"). The 'l' prefix was just a bad idea;
+
- Improved: Using bundles in Mac OS X, instead of dylibs;
+
- Fixed: luasocket.h to export luaopen_socketcore;
+
- Fixed: udp:setpeername() so you can "disconnect" an
+UDP socket;
+
- Fixed: A weird bug in HTTP support that caused some requests to
+fail (Florian Berger);
+
- Fixed: Bug in socket.select() that caused sockets
+with descriptor 0 to be ignored (Renato Maia);
+
- Fixed: "Bug" that caused dns.toip() to crash under uLinux
+(William Trenker);
+
- Fixed: "Bug" that caused gethostbyname to crash under VMS
(Renato Maia);
+
- Fixed: tcp:send("") to return 0 bytes sent (Alexander Marinov);
- Improved: socket.DEBUG and socket.VERSION became socket._DEBUGs and socket._VERSION for uniformity with other libraries.
diff --git a/etc/check-links-nb.lua b/etc/check-links-nb.lua
new file mode 100644
index 0000000..7e8df1b
--- /dev/null
+++ b/etc/check-links-nb.lua
@@ -0,0 +1,262 @@
+-----------------------------------------------------------------------------
+-- Little program that checks links in HTML files, using coroutines and
+-- non-blocking I/O. Thus, faster than simpler version of same program
+-- LuaSocket sample files
+-- Author: Diego Nehab
+-- RCS ID: $$
+-----------------------------------------------------------------------------
+local socket = require("socket")
+
+TIMEOUT = 10
+
+-- we need to yield across calls to protect, so we can't use pcall
+-- we borrow and simplify code from coxpcall to reimplement socket.protect
+-- before loading http
+function socket.protect(f)
+ return function(...)
+ local co = coroutine.create(f)
+ while true do
+ local results = {coroutine.resume(co, unpack(arg))}
+ local status = results[1]
+ table.remove(results, 1)
+ if not status then
+ return nil, results[1][1]
+ end
+ if coroutine.status(co) == "suspended" then
+ arg = {coroutine.yield(unpack(results))}
+ else
+ return unpack(results)
+ end
+ end
+ end
+end
+
+local http = require("socket.http")
+local url = require("socket.url")
+
+-- creates a new set data structure
+function newset()
+ local reverse = {}
+ local set = {}
+ return setmetatable(set, {__index = {
+ insert = function(set, value)
+ if not reverse[value] then
+ table.insert(set, value)
+ reverse[value] = table.getn(set)
+ end
+ end,
+ remove = function(set, value)
+ local index = reverse[value]
+ if index then
+ reverse[value] = nil
+ local top = table.remove(set)
+ if top ~= value then
+ reverse[top] = index
+ set[index] = top
+ end
+ end
+ end
+ }})
+end
+
+local context = {}
+local sending = newset()
+local receiving = newset()
+local nthreads = 0
+
+-- socket.tcp() replacement for non-blocking I/O
+-- implements enough functionality to be used with http.request
+-- in Lua 5.1, we have coroutine.running to simplify things...
+function newcreate(thread)
+ return function()
+ -- try to create underlying socket
+ local tcp, error = socket.tcp()
+ if not tcp then return nil, error end
+ -- put it in non-blocking mode right away
+ tcp:settimeout(0)
+ local trap = {
+ -- we ignore settimeout to preserve our 0 timeout
+ settimeout = function(self, mode, value)
+ return 1
+ end,
+ -- send in non-blocking mode and yield on timeout
+ send = function(self, data, first, last)
+ first = (first or 1) - 1
+ local result, error
+ while true do
+ result, error, first = tcp:send(data, first+1, last)
+ if error == "timeout" then
+ -- tell dispatcher we want to keep sending
+ sending:insert(tcp)
+ -- mark time we started waiting
+ context[tcp].last = socket.gettime()
+ -- return control to dispatcher
+ if coroutine.yield() == "timeout" then
+ return nil, "timeout"
+ end
+ else return result, error, first end
+ end
+ end,
+ -- receive in non-blocking mode and yield on timeout
+ receive = function(self, pattern)
+ local error, partial = "timeout", ""
+ local value
+ while true do
+ value, error, partial = tcp:receive(pattern, partial)
+ if error == "timeout" then
+ -- tell dispatcher we want to keep receiving
+ receiving:insert(tcp)
+ -- mark time we started waiting
+ context[tcp].last = socket.gettime()
+ -- return control to dispatcher
+ if coroutine.yield() == "timeout" then
+ return nil, "timeout"
+ end
+ else return value, error, partial end
+ end
+ end,
+ -- connect in non-blocking mode and yield on timeout
+ connect = function(self, host, port)
+ local result, error = tcp:connect(host, port)
+ if error == "timeout" then
+ -- tell dispatcher we will be able to write uppon connection
+ sending:insert(tcp)
+ -- mark time we started waiting
+ context[tcp].last = socket.gettime()
+ -- return control to dispatcher
+ if coroutine.yield() == "timeout" then
+ return nil, "timeout"
+ end
+ -- when we come back, check if connection was successful
+ result, error = tcp:connect(host, port)
+ if result or error == "already connected" then return 1
+ else return nil, "non-blocking connect failed" end
+ else return result, error end
+ end,
+ close = function(self)
+ context[tcp] = nil
+ return tcp:close()
+ end
+ }
+ -- add newly created socket to context
+ context[tcp] = {
+ thread = thread,
+ trap = trap
+ }
+ return trap
+ end
+end
+
+-- get the status of a URL, non-blocking
+function getstatus(from, link)
+ local parsed = url.parse(link, {scheme = "file"})
+ if parsed.scheme == "http" then
+ local thread = coroutine.create(function(thread, from, link)
+ local r, c, h, s = http.request{
+ method = "HEAD",
+ url = link,
+ create = newcreate(thread)
+ }
+ if c == 200 then io.write('\t', link, '\n')
+ else io.write('\t', link, ': ', c, '\n') end
+ nthreads = nthreads - 1
+ end)
+ nthreads = nthreads + 1
+ assert(coroutine.resume(thread, thread, from, link))
+ end
+end
+
+-- dispatch all threads until we are done
+function dispatch()
+ while nthreads > 0 do
+ -- check which sockets are interesting and act on them
+ local readable, writable = socket.select(receiving, sending, 1)
+ -- for all readable connections, resume their threads
+ for _, who in ipairs(readable) do
+ if context[who] then
+ receiving:remove(who)
+ assert(coroutine.resume(context[who].thread))
+ end
+ end
+ -- for all writable connections, do the same
+ for _, who in ipairs(writable) do
+ if context[who] then
+ sending:remove(who)
+ assert(coroutine.resume(context[who].thread))
+ end
+ end
+ -- politely ask replacement I/O functions in idle threads to
+ -- return reporting a timeout
+ local now = socket.gettime()
+ for who, data in pairs(context) do
+ if data.last and now - data.last > TIMEOUT then
+ assert(coroutine.resume(context[who].thread, "timeout"))
+ end
+ end
+ end
+end
+
+function readfile(path)
+ path = url.unescape(path)
+ local file, error = io.open(path, "r")
+ if file then
+ local body = file:read("*a")
+ file:close()
+ return body
+ else return nil, error end
+end
+
+function retrieve(u)
+ local parsed = url.parse(u, { scheme = "file" })
+ local body, headers, code, error
+ local base = u
+ if parsed.scheme == "http" then
+ body, code, headers = http.request(u)
+ if code == 200 then
+ base = base or headers.location
+ end
+ if not body then
+ error = code
+ end
+ elseif parsed.scheme == "file" then
+ body, error = readfile(parsed.path)
+ else error = string.format("unhandled scheme '%s'", parsed.scheme) end
+ return base, body, error
+end
+
+function getlinks(body, base)
+ -- get rid of comments
+ body = string.gsub(body, "%<%!%-%-.-%-%-%>", "")
+ local links = {}
+ -- extract links
+ body = string.gsub(body, '[Hh][Rr][Ee][Ff]%s*=%s*"([^"]*)"', function(href)
+ table.insert(links, url.absolute(base, href))
+ end)
+ body = string.gsub(body, "[Hh][Rr][Ee][Ff]%s*=%s*'([^']*)'", function(href)
+ table.insert(links, url.absolute(base, href))
+ end)
+ string.gsub(body, "[Hh][Rr][Ee][Ff]%s*=%s*(.-)>", function(href)
+ table.insert(links, url.absolute(base, href))
+ end)
+ return links
+end
+
+function checklinks(from)
+ local base, body, error = retrieve(from)
+ if not body then print(error) return end
+ local links = getlinks(body, base)
+ for _, link in ipairs(links) do
+ getstatus(from, link)
+ end
+end
+
+arg = arg or {}
+if table.getn(arg) < 1 then
+ print("Usage:\n luasocket check-links.lua {}")
+ exit()
+end
+for _, a in ipairs(arg) do
+ print("Checking ", a)
+ checklinks(url.absolute("file:", a))
+end
+dispatch()
diff --git a/samples/forward.lua b/samples/forward.lua
index a53ab5d..548a753 100644
--- a/samples/forward.lua
+++ b/samples/forward.lua
@@ -2,7 +2,7 @@
local socket = require"socket"
-- creates a new set data structure
-function newset(a)
+function newset()
local reverse = {}
local set = {}
return setmetatable(set, {__index = {
diff --git a/src/buffer.c b/src/buffer.c
index 62211d8..1188fda 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -122,7 +122,8 @@ int buf_meth_receive(lua_State *L, p_buf buf) {
if (p[0] == '*' && p[1] == 'l') err = recvline(buf, &b);
else if (p[0] == '*' && p[1] == 'a') err = recvall(buf, &b);
else luaL_argcheck(L, 0, 2, "invalid receive pattern");
- /* get a fixed number of bytes */
+ /* get a fixed number of bytes (minus what was already partially
+ * received) */
} else err = recvraw(buf, (size_t) lua_tonumber(L, 2)-size, &b);
/* check if there was an error */
if (err != IO_DONE) {
diff --git a/test/httptest.lua b/test/httptest.lua
index 86f14a4..3816b54 100644
--- a/test/httptest.lua
+++ b/test/httptest.lua
@@ -26,7 +26,7 @@ host = host or "localhost" -- "diego.student.princeton.edu"
proxy = proxy or "http://localhost:3128"
prefix = prefix or "/luasocket-test"
cgiprefix = cgiprefix or "/luasocket-test-cgi"
-index_file = "test/index.html"
+index_file = "index.html"
-- read index with CRLF convention
index = readfile(index_file)