forked from lunarmodules/luasec
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1)remove http2 folder. 2)add modified http.lua inside luasec 3) modify https.lua 4) add test.lua to check redirects working basically implemented suggestions from lunarmodules#38
- Loading branch information
Showing
7 changed files
with
481 additions
and
784 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
/src/*.o | ||
/src/luasocket/*.o | ||
/*.dll | ||
/samples/certs/ | ||
/samples/certs/ | ||
ssl.so |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,363 @@ | ||
----------------------------------------------------------------------------- | ||
-- HTTP/1.1 client support for the Lua language. | ||
-- LuaSocket toolkit. | ||
-- Author: Diego Nehab | ||
----------------------------------------------------------------------------- | ||
|
||
----------------------------------------------------------------------------- | ||
-- Declare module and import dependencies | ||
------------------------------------------------------------------------------- | ||
local socket = require("socket") | ||
local url = require("socket.url") | ||
local ltn12 = require("ltn12") | ||
local mime = require("mime") | ||
local string = require("string") | ||
local headers = require("socket.headers") | ||
local base = _G | ||
local table = require("table") | ||
socket.http = {} | ||
local _M = socket.http | ||
|
||
----------------------------------------------------------------------------- | ||
-- Program constants | ||
----------------------------------------------------------------------------- | ||
-- connection timeout in seconds | ||
_M.TIMEOUT = 60 | ||
-- default port for document retrieval | ||
_M.PORT = 80 | ||
-- user agent field sent in request | ||
_M.USERAGENT = socket._VERSION | ||
|
||
----------------------------------------------------------------------------- | ||
-- Reads MIME headers from a connection, unfolding where needed | ||
----------------------------------------------------------------------------- | ||
local function receiveheaders(sock, headers) | ||
local line, name, value, err | ||
headers = headers or {} | ||
-- get first line | ||
line, err = sock:receive() | ||
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 = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) | ||
if not (name and value) then return nil, "malformed reponse headers" end | ||
name = string.lower(name) | ||
-- get next line (value might be folded) | ||
line, err = sock:receive() | ||
if err then return nil, err end | ||
-- unfold any folded values | ||
while string.find(line, "^%s") do | ||
value = value .. line | ||
line = sock:receive() | ||
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 | ||
end | ||
return headers | ||
end | ||
|
||
----------------------------------------------------------------------------- | ||
-- Extra sources and sinks | ||
----------------------------------------------------------------------------- | ||
socket.sourcet["http-chunked"] = function(sock, headers) | ||
return base.setmetatable({ | ||
getfd = function() return sock:getfd() end, | ||
dirty = function() return sock:dirty() end | ||
}, { | ||
__call = function() | ||
-- get chunk size, skip extention | ||
local line, err = sock:receive() | ||
if err then return nil, err end | ||
local size = base.tonumber(string.gsub(line, ";.*", ""), 16) | ||
if not size then return nil, "invalid chunk size" end | ||
-- was it the last chunk? | ||
if size > 0 then | ||
-- if not, get chunk and skip terminating CRLF | ||
local chunk, err = sock:receive(size) | ||
if chunk then sock:receive() end | ||
return chunk, err | ||
else | ||
-- if it was, read trailers into headers table | ||
headers, err = receiveheaders(sock, headers) | ||
if not headers then return nil, err end | ||
end | ||
end | ||
}) | ||
end | ||
|
||
socket.sinkt["http-chunked"] = function(sock) | ||
return base.setmetatable({ | ||
getfd = function() return sock:getfd() end, | ||
dirty = function() return sock:dirty() end | ||
}, { | ||
__call = function(self, chunk, err) | ||
if not chunk then return sock:send("0\r\n\r\n") end | ||
local size = string.format("%X\r\n", string.len(chunk)) | ||
return sock:send(size .. chunk .. "\r\n") | ||
end | ||
}) | ||
end | ||
|
||
----------------------------------------------------------------------------- | ||
-- Low level HTTP API | ||
----------------------------------------------------------------------------- | ||
local metat = { __index = {} } | ||
|
||
function _M.open(reqt) | ||
-- create socket with user connect function | ||
local c = socket.try(reqt:create()) -- method call, passing reqt table as self! | ||
local h = base.setmetatable({ c = c }, metat) | ||
-- create finalized try | ||
h.try = socket.newtry(function() h:close() end) | ||
-- set timeout before connecting | ||
h.try(c:settimeout(_M.TIMEOUT)) | ||
h.try(c:connect(reqt.host, reqt.port or _M.PORT)) | ||
-- here everything worked | ||
return h | ||
end | ||
|
||
function metat.__index:sendrequestline(method, uri) | ||
local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) | ||
return self.try(self.c:send(reqline)) | ||
end | ||
|
||
function metat.__index:sendheaders(tosend) | ||
local canonic = headers.canonic | ||
local h = "\r\n" | ||
for f, v in base.pairs(tosend) do | ||
h = (canonic[f] or f) .. ": " .. v .. "\r\n" .. h | ||
end | ||
self.try(self.c:send(h)) | ||
return 1 | ||
end | ||
|
||
function metat.__index:sendbody(headers, source, step) | ||
source = source or ltn12.source.empty() | ||
step = step or ltn12.pump.step | ||
-- if we don't know the size in advance, send chunked and hope for the best | ||
local mode = "http-chunked" | ||
if headers["content-length"] then mode = "keep-open" end | ||
return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) | ||
end | ||
|
||
function metat.__index:receivestatusline() | ||
local status = self.try(self.c:receive(5)) | ||
-- identify HTTP/0.9 responses, which do not contain a status line | ||
-- this is just a heuristic, but is what the RFC recommends | ||
if status ~= "HTTP/" then return nil, status end | ||
-- otherwise proceed reading a status line | ||
status = self.try(self.c:receive("*l", status)) | ||
local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) | ||
return self.try(base.tonumber(code), status) | ||
end | ||
|
||
function metat.__index:receiveheaders() | ||
return self.try(receiveheaders(self.c)) | ||
end | ||
|
||
function metat.__index:receivebody(headers, sink, step) | ||
sink = sink or ltn12.sink.null() | ||
step = step or ltn12.pump.step | ||
local length = base.tonumber(headers["content-length"]) | ||
local t = headers["transfer-encoding"] -- shortcut | ||
local mode = "default" -- connection close | ||
if t and t ~= "identity" then mode = "http-chunked" | ||
elseif base.tonumber(headers["content-length"]) then mode = "by-length" end | ||
return self.try(ltn12.pump.all(socket.source(mode, self.c, length), | ||
sink, step)) | ||
end | ||
|
||
function metat.__index:receive09body(status, sink, step) | ||
local source = ltn12.source.rewind(socket.source("until-closed", self.c)) | ||
source(status) | ||
return self.try(ltn12.pump.all(source, sink, step)) | ||
end | ||
|
||
function metat.__index:close() | ||
return self.c:close() | ||
end | ||
|
||
----------------------------------------------------------------------------- | ||
-- High level HTTP API | ||
----------------------------------------------------------------------------- | ||
local function adjusturi(reqt) | ||
local u = reqt | ||
-- if there is a proxy, we need the full url. otherwise, just a part. | ||
if not reqt.proxy and not _M.PROXY then | ||
u = { | ||
path = socket.try(reqt.path, "invalid path 'nil'"), | ||
params = reqt.params, | ||
query = reqt.query, | ||
fragment = reqt.fragment | ||
} | ||
end | ||
return url.build(u) | ||
end | ||
|
||
local function adjustproxy(reqt) | ||
local proxy = reqt.proxy or _M.PROXY | ||
if proxy then | ||
proxy = url.parse(proxy) | ||
return proxy.host, proxy.port or 3128 | ||
else | ||
return reqt.host, reqt.port | ||
end | ||
end | ||
|
||
local function adjustheaders(reqt) | ||
-- default headers | ||
local host = string.gsub(reqt.authority, "^.-@", "") | ||
local lower = { | ||
["user-agent"] = _M.USERAGENT, | ||
["host"] = host, | ||
["connection"] = "close, TE", | ||
["te"] = "trailers" | ||
} | ||
-- if we have authentication information, pass it along | ||
if reqt.user and reqt.password then | ||
lower["authorization"] = | ||
"Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password)) | ||
end | ||
-- override with user headers | ||
for i,v in base.pairs(reqt.headers or lower) do | ||
lower[string.lower(i)] = v | ||
end | ||
return lower | ||
end | ||
|
||
-- default url parts | ||
local default = { | ||
host = "", | ||
port = _M.PORT, | ||
path ="/", | ||
scheme = "http" | ||
} | ||
|
||
local function adjustrequest(reqt) | ||
-- parse url if provided | ||
local nreqt = reqt.url and url.parse(reqt.url, default) or {} | ||
-- explicit components override url | ||
for i,v in base.pairs(reqt) do nreqt[i] = v end | ||
if nreqt.port == "" then nreqt.port = 80 end | ||
socket.try(nreqt.host and nreqt.host ~= "", | ||
"invalid host '" .. base.tostring(nreqt.host) .. "'") | ||
-- compute uri if user hasn't overriden | ||
nreqt.uri = reqt.uri or adjusturi(nreqt) | ||
-- ajust host and port if there is a proxy | ||
nreqt.host, nreqt.port = adjustproxy(nreqt) | ||
-- adjust headers in request | ||
nreqt.headers = adjustheaders(nreqt) | ||
return nreqt | ||
end | ||
|
||
local function shouldredirect(reqt, code, headers) | ||
return headers.location and | ||
string.gsub(headers.location, "%s", "") ~= "" and | ||
(reqt.redirect ~= false) and | ||
(code == 301 or code == 302 or code == 303 or code == 307) and | ||
(not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") | ||
and (not reqt.nredirects or reqt.nredirects < 5) | ||
end | ||
|
||
local function shouldreceivebody(reqt, code) | ||
if reqt.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 | ||
|
||
-- forward declarations | ||
local trequest, tredirect | ||
|
||
--[[local]] function tredirect(reqt, location) | ||
local result, code, headers, status = trequest { | ||
-- the RFC says the redirect URL has to be absolute, but some | ||
-- servers do not respect that | ||
url = url.absolute(reqt.url, location), | ||
source = reqt.source, | ||
sink = reqt.sink, | ||
headers = reqt.headers, | ||
proxy = reqt.proxy, | ||
nredirects = (reqt.nredirects or 0) + 1, | ||
create = reqt.create | ||
} | ||
-- pass location header back as a hint we redirected | ||
headers = headers or {} | ||
headers.location = headers.location or location | ||
return result, code, headers, status | ||
end | ||
|
||
--[[local]] function trequest(reqt) | ||
-- we loop until we get what we want, or | ||
-- until we are sure there is no way to get it | ||
local nreqt = adjustrequest(reqt) | ||
local h = _M.open(nreqt) | ||
-- send request line and headers | ||
h:sendrequestline(nreqt.method, nreqt.uri) | ||
h:sendheaders(nreqt.headers) | ||
-- if there is a body, send it | ||
if nreqt.source then | ||
h:sendbody(nreqt.headers, nreqt.source, nreqt.step) | ||
end | ||
local code, status = h:receivestatusline() | ||
-- if it is an HTTP/0.9 server, simply get the body and we are done | ||
if not code then | ||
h:receive09body(status, nreqt.sink, nreqt.step) | ||
return 1, 200 | ||
end | ||
local headers | ||
-- ignore any 100-continue messages | ||
while code == 100 do | ||
headers = h:receiveheaders() | ||
code, status = h:receivestatusline() | ||
end | ||
headers = h:receiveheaders() | ||
-- at this point we should have a honest reply from the server | ||
-- we can't redirect if we already used the source, so we report the error | ||
if shouldredirect(nreqt, code, headers) and not nreqt.source then | ||
h:close() | ||
return tredirect(reqt, headers.location) | ||
end | ||
-- here we are finally done | ||
if shouldreceivebody(nreqt, code) then | ||
h:receivebody(headers, nreqt.sink, nreqt.step) | ||
end | ||
h:close() | ||
return 1, code, headers, status | ||
end | ||
|
||
-- parses a shorthand form into the advanced table form. | ||
-- adds field `target` to the table. This will hold the return values. | ||
_M.parseRequest = function(u, b) | ||
local t = {} | ||
local reqt = { | ||
url = u, | ||
sink = ltn12.sink.table(t), | ||
target = t, | ||
} | ||
if b then | ||
reqt.source = ltn12.source.string(b) | ||
reqt.headers = { | ||
["content-length"] = string.len(b), | ||
["content-type"] = "application/x-www-form-urlencoded" | ||
} | ||
reqt.method = "POST" | ||
end | ||
return reqt | ||
end | ||
|
||
_M.request = socket.protect(function(reqt, body) | ||
if base.type(reqt) == "string" then | ||
reqt = _M.parseRequest(reqt, body) | ||
local t, code, headers, status = reqt.target, socket.skip(1, _M.request(reqt)) | ||
return table.concat(t), code, headers, status | ||
else | ||
reqt.create = reqt.create or socket.tcp | ||
return trequest(reqt) | ||
end | ||
end) | ||
|
||
return _M |
Oops, something went wrong.