Merge 0.9->trunk
authorMatthew Wild <mwild1@gmail.com>
Fri, 12 Apr 2013 00:32:10 +0100
changeset 5467 9b7c919bf238
parent 5457 953888c31071 (current diff)
parent 5466 e3b9dc9dd940 (diff)
child 5469 fcdcc37ffec1
Merge 0.9->trunk
util/httpstream.lua
--- a/core/s2smanager.lua	Tue Apr 09 15:51:08 2013 +0200
+++ b/core/s2smanager.lua	Fri Apr 12 00:32:10 2013 +0100
@@ -9,8 +9,8 @@
 
 
 local hosts = prosody.hosts;
-local tostring, pairs, getmetatable, newproxy, setmetatable
-    = tostring, pairs, getmetatable, newproxy, setmetatable;
+local tostring, pairs, setmetatable
+    = tostring, pairs, setmetatable;
 
 local logger_init = require "util.logger".init;
 
--- a/core/sessionmanager.lua	Tue Apr 09 15:51:08 2013 +0200
+++ b/core/sessionmanager.lua	Fri Apr 12 00:32:10 2013 +0100
@@ -24,9 +24,6 @@
 local initialize_filters = require "util.filters".initialize;
 local gettime = require "socket".gettime;
 
-local newproxy = newproxy;
-local getmetatable = getmetatable;
-
 module "sessionmanager"
 
 function new_session(conn)
--- a/net/http.lua	Tue Apr 09 15:51:08 2013 +0200
+++ b/net/http.lua	Fri Apr 12 00:32:10 2013 +0100
@@ -9,7 +9,8 @@
 local socket = require "socket"
 local b64 = require "util.encodings".base64.encode;
 local url = require "socket.url"
-local httpstream_new = require "util.httpstream".new;
+local httpstream_new = require "net.http.parser".new;
+local util_http = require "util.http";
 
 local ssl_available = pcall(require, "ssl");
 
@@ -70,52 +71,12 @@
 	requests[conn] = nil;
 end
 
-function urlencode(s) return s and (s:gsub("[^a-zA-Z0-9.~_-]", function (c) return format("%%%02x", c:byte()); end)); end
-function urldecode(s) return s and (s:gsub("%%(%x%x)", function (c) return char(tonumber(c,16)); end)); end
-
-local function _formencodepart(s)
-	return s and (s:gsub("%W", function (c)
-		if c ~= " " then
-			return format("%%%02x", c:byte());
-		else
-			return "+";
-		end
-	end));
-end
-
-function formencode(form)
-	local result = {};
-	if form[1] then -- Array of ordered { name, value }
-		for _, field in ipairs(form) do
-			t_insert(result, _formencodepart(field.name).."=".._formencodepart(field.value));
-		end
-	else -- Unordered map of name -> value
-		for name, value in pairs(form) do
-			t_insert(result, _formencodepart(name).."=".._formencodepart(value));
-		end
-	end
-	return t_concat(result, "&");
-end
-
-function formdecode(s)
-	if not s:match("=") then return urldecode(s); end
-	local r = {};
-	for k, v in s:gmatch("([^=&]*)=([^&]*)") do
-		k, v = k:gsub("%+", "%%20"), v:gsub("%+", "%%20");
-		k, v = urldecode(k), urldecode(v);
-		t_insert(r, { name = k, value = v });
-		r[k] = v;
-	end
-	return r;
-end
-
-local function request_reader(request, data, startpos)
+local function request_reader(request, data)
 	if not request.parser then
 		if not data then return; end
 		local function success_cb(r)
 			if request.callback then
-				for k,v in pairs(r) do request[k] = v; end
-				request.callback(r.body, r.code, request, r);
+				request.callback(r.body, r.code, r, request);
 				request.callback = nil;
 			end
 			destroy_request(request);
@@ -216,6 +177,10 @@
 	end
 end
 
-_M.urlencode = urlencode;
+local urlencode, urldecode = util_http.urlencode, util_http.urldecode;
+local formencode, formdecode = util_http.formencode, util_http.formdecode;
+
+_M.urlencode, _M.urldecode = urlencode, urldecode;
+_M.formencode, _M.formdecode = formencode, formdecode;
 
 return _M;
--- a/net/http/parser.lua	Tue Apr 09 15:51:08 2013 +0200
+++ b/net/http/parser.lua	Fri Apr 12 00:32:10 2013 +0100
@@ -1,8 +1,7 @@
-
 local tonumber = tonumber;
 local assert = assert;
 local url_parse = require "socket.url".parse;
-local urldecode = require "net.http".urldecode;
+local urldecode = require "util.http".urldecode;
 
 local function preprocess_path(path)
 	path = urldecode((path:gsub("//+", "/")));
@@ -29,7 +28,7 @@
 	local client = true;
 	if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end
 	local buf = "";
-	local chunked;
+	local chunked, chunk_size, chunk_start;
 	local state = nil;
 	local packet;
 	local len;
@@ -65,12 +64,12 @@
 							first_line = line;
 							if client then
 								httpversion, status_code, reason_phrase = line:match("^HTTP/(1%.[01]) (%d%d%d) (.*)$");
+								status_code = tonumber(status_code);
 								if not status_code then error = true; return error_cb("invalid-status-line"); end
 								have_body = not
 									 ( (options_cb and options_cb().method == "HEAD")
 									or (status_code == 204 or status_code == 304 or status_code == 301)
 									or (status_code >= 100 and status_code < 200) );
-								chunked = have_body and headers["transfer-encoding"] == "chunked";
 							else
 								method, path, httpversion = line:match("^(%w+) (%S+) HTTP/(1%.[01])$");
 								if not method then error = true; return error_cb("invalid-status-line"); end
@@ -78,6 +77,7 @@
 						end
 					end
 					if not first_line then error = true; return error_cb("invalid-status-line"); end
+					chunked = have_body and headers["transfer-encoding"] == "chunked";
 					len = tonumber(headers["content-length"]); -- TODO check for invalid len
 					if client then
 						-- FIXME handle '100 Continue' response (by skipping it)
@@ -120,22 +120,30 @@
 				if state then -- read body
 					if client then
 						if chunked then
-							local index = buf:find("\r\n", nil, true);
-							if not index then return; end -- not enough data
-							local chunk_size = buf:match("^%x+");
-							if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end
-							chunk_size = tonumber(chunk_size, 16);
-							index = index + 2;
-							if chunk_size == 0 then
-								state = nil; success_cb(packet);
-							elseif #buf - index + 1 >= chunk_size then -- we have a chunk
-								packet.body = packet.body..buf:sub(index, index + chunk_size - 1);
-								buf = buf:sub(index + chunk_size);
+							if not buf:find("\r\n", nil, true) then
+								return;
+							end -- not enough data
+							if not chunk_size then
+								chunk_size, chunk_start = buf:match("^(%x+)[^\r\n]*\r\n()");
+								chunk_size = chunk_size and tonumber(chunk_size, 16);
+								if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end
 							end
-							error("trailers"); -- FIXME MUST read trailers
+							if chunk_size == 0 and buf:find("\r\n\r\n", chunk_start-2, true) then
+								state, chunk_size = nil, nil;
+								buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped
+								success_cb(packet);
+							elseif #buf - chunk_start + 2 >= chunk_size then -- we have a chunk
+								packet.body = packet.body..buf:sub(chunk_start, chunk_start + chunk_size);
+								buf = buf:sub(chunk_start + chunk_size + 2);
+								chunk_size, chunk_start = nil, nil;
+							else -- Partial chunk remaining
+								break;
+							end
 						elseif len and #buf >= len then
 							packet.body, buf = buf:sub(1, len), buf:sub(len + 1);
 							state = nil; success_cb(packet);
+						else
+							break;
 						end
 					elseif #buf >= len then
 						packet.body, buf = buf:sub(1, len), buf:sub(len + 1);
--- a/util/http.lua	Tue Apr 09 15:51:08 2013 +0200
+++ b/util/http.lua	Fri Apr 12 00:32:10 2013 +0100
@@ -7,9 +7,54 @@
 
 local http = {};
 
+function http.urlencode(s)
+	return s and (s:gsub("[^a-zA-Z0-9.~_-]", function (c) return format("%%%02x", c:byte()); end));
+end
+function http.urldecode(s)
+	return s and (s:gsub("%%(%x%x)", function (c) return char(tonumber(c,16)); end));
+end
+
+local function _formencodepart(s)
+	return s and (s:gsub("%W", function (c)
+		if c ~= " " then
+			return format("%%%02x", c:byte());
+		else
+			return "+";
+		end
+	end));
+end
+
+function http.formencode(form)
+	local result = {};
+	if form[1] then -- Array of ordered { name, value }
+		for _, field in ipairs(form) do
+			t_insert(result, _formencodepart(field.name).."=".._formencodepart(field.value));
+		end
+	else -- Unordered map of name -> value
+		for name, value in pairs(form) do
+			t_insert(result, _formencodepart(name).."=".._formencodepart(value));
+		end
+	end
+	return t_concat(result, "&");
+end
+
+function http.formdecode(s)
+	if not s:match("=") then return urldecode(s); end
+	local r = {};
+	for k, v in s:gmatch("([^=&]*)=([^&]*)") do
+		k, v = k:gsub("%+", "%%20"), v:gsub("%+", "%%20");
+		k, v = urldecode(k), urldecode(v);
+		t_insert(r, { name = k, value = v });
+		r[k] = v;
+	end
+	return r;
+end
+
 function http.contains_token(field, token)
 	field = ","..field:gsub("[ \t]", ""):lower()..",";
 	return field:find(","..token:lower()..",", 1, true) ~= nil;
 end
 
+
+
 return http;
--- a/util/httpstream.lua	Tue Apr 09 15:51:08 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,134 +0,0 @@
-
-local coroutine = coroutine;
-local tonumber = tonumber;
-
-local deadroutine = coroutine.create(function() end);
-coroutine.resume(deadroutine);
-
-module("httpstream")
-
-local function parser(success_cb, parser_type, options_cb)
-	local data = coroutine.yield();
-	local function readline()
-		local pos = data:find("\r\n", nil, true);
-		while not pos do
-			data = data..coroutine.yield();
-			pos = data:find("\r\n", nil, true);
-		end
-		local r = data:sub(1, pos-1);
-		data = data:sub(pos+2);
-		return r;
-	end
-	local function readlength(n)
-		while #data < n do
-			data = data..coroutine.yield();
-		end
-		local r = data:sub(1, n);
-		data = data:sub(n + 1);
-		return r;
-	end
-	local function readheaders()
-		local headers = {}; -- read headers
-		while true do
-			local line = readline();
-			if line == "" then break; end -- headers done
-			local key, val = line:match("^([^%s:]+): *(.*)$");
-			if not key then coroutine.yield("invalid-header-line"); end -- TODO handle multi-line and invalid headers
-			key = key:lower();
-			headers[key] = headers[key] and headers[key]..","..val or val;
-		end
-		return headers;
-	end
-	
-	if not parser_type or parser_type == "server" then
-		while true do
-			-- read status line
-			local status_line = readline();
-			local method, path, httpversion = status_line:match("^(%S+)%s+(%S+)%s+HTTP/(%S+)$");
-			if not method then coroutine.yield("invalid-status-line"); end
-			path = path:gsub("^//+", "/"); -- TODO parse url more
-			local headers = readheaders();
-			
-			-- read body
-			local len = tonumber(headers["content-length"]);
-			len = len or 0; -- TODO check for invalid len
-			local body = readlength(len);
-			
-			success_cb({
-				method = method;
-				path = path;
-				httpversion = httpversion;
-				headers = headers;
-				body = body;
-			});
-		end
-	elseif parser_type == "client" then
-		while true do
-			-- read status line
-			local status_line = readline();
-			local httpversion, status_code, reason_phrase = status_line:match("^HTTP/(%S+)%s+(%d%d%d)%s+(.*)$");
-			status_code = tonumber(status_code);
-			if not status_code then coroutine.yield("invalid-status-line"); end
-			local headers = readheaders();
-			
-			-- read body
-			local have_body = not
-				 ( (options_cb and options_cb().method == "HEAD")
-				or (status_code == 204 or status_code == 304 or status_code == 301)
-				or (status_code >= 100 and status_code < 200) );
-			
-			local body;
-			if have_body then
-				local len = tonumber(headers["content-length"]);
-				if headers["transfer-encoding"] == "chunked" then
-					body = "";
-					while true do
-						local chunk_size = readline():match("^%x+");
-						if not chunk_size then coroutine.yield("invalid-chunk-size"); end
-						chunk_size = tonumber(chunk_size, 16)
-						if chunk_size == 0 then break; end
-						body = body..readlength(chunk_size);
-						if readline() ~= "" then coroutine.yield("invalid-chunk-ending"); end
-					end
-					local trailers = readheaders();
-				elseif len then -- TODO check for invalid len
-					body = readlength(len);
-				else -- read to end
-					repeat
-						local newdata = coroutine.yield();
-						data = data..newdata;
-					until newdata == "";
-					body, data = data, "";
-				end
-			end
-			
-			success_cb({
-				code = status_code;
-				httpversion = httpversion;
-				headers = headers;
-				body = body;
-			});
-		end
-	else coroutine.yield("unknown-parser-type"); end
-end
-
-function new(success_cb, error_cb, parser_type, options_cb)
-	local co = coroutine.create(parser);
-	coroutine.resume(co, success_cb, parser_type, options_cb)
-	return {
-		feed = function(self, data)
-			if not data then
-				if parser_type == "client" then coroutine.resume(co, ""); end
-				co = deadroutine;
-				return error_cb();
-			end
-			local success, result = coroutine.resume(co, data);
-			if result then
-				co = deadroutine;
-				return error_cb(result);
-			end
-		end;
-	};
-end
-
-return _M;