208 lines
5.1 KiB
Lua
208 lines
5.1 KiB
Lua
--[[
|
|
Copyright (c) 2014 Team Sparkle
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
]]
|
|
|
|
function url_parse(url)
|
|
local colon, slash
|
|
local proto, host, port
|
|
|
|
-- Work out the protocol.
|
|
colon = url:find(":")
|
|
if not colon then return end
|
|
proto = url:sub(1,colon-1)
|
|
url = url:sub(colon+1)
|
|
|
|
-- We only handle so many (or should I say "few") protocols.
|
|
if proto == "http" then
|
|
-- Get that // bit.
|
|
if url:sub(1,2) ~= "//" then
|
|
print("expected \"//\" after \":\"")
|
|
return
|
|
end
|
|
url = url:sub(3)
|
|
|
|
-- If there is a : before a /, there's a port to read.
|
|
colon = url:find(":")
|
|
slash = url:find("/")
|
|
|
|
-- FIXME: This step probably throws a Lua error if it's not a numeric.
|
|
if colon and ((not slash) or slash > colon) then
|
|
host = url:sub(1, colon-1)
|
|
local portstr = url:sub(colon+1, (slash and slash-1))
|
|
port = 0 + portstr
|
|
else
|
|
host = url:sub(1, (slash and slash-1))
|
|
port = 80
|
|
end
|
|
|
|
-- Include the / in the URL.
|
|
if slash then
|
|
url = url:sub(slash)
|
|
else
|
|
url = "/"
|
|
end
|
|
|
|
-- Return all the details.
|
|
return proto, host, port, url
|
|
else
|
|
print("unknown protocol \""..proto.."\"")
|
|
return
|
|
end
|
|
|
|
end
|
|
|
|
--print(url_parse("http://iceball.rakiru.com:27790/master.json"))
|
|
|
|
function http_new(settings)
|
|
local this = {
|
|
url = settings.url,
|
|
method = settings.method or "GET",
|
|
state = "send",
|
|
recv_state = "head",
|
|
req_code = nil,
|
|
req_len = nil,
|
|
data = nil,
|
|
}
|
|
|
|
function this.read_header(s)
|
|
if s == "" then
|
|
-- FIXME: validate the state BEFORE we actually move
|
|
this.recv_state = "body"
|
|
elseif not this.req_code then
|
|
-- FIXME: parse this properly
|
|
local http_ver = s:sub(1, s:find(" ")-1)
|
|
s = s:sub(s:find(" ")+1)
|
|
this.req_code = 0 + s:sub(1, s:find(" ")-1)
|
|
else
|
|
local fieldpos = s:find(":")
|
|
local name = s:sub(1, fieldpos-1)
|
|
local field = s:sub(fieldpos+2)
|
|
print("["..name.."|"..field.."]")
|
|
|
|
if name == "Length" then
|
|
-- FIXME: This can cause a Lua error if not a number
|
|
this.req_len = 0 + field
|
|
end
|
|
end
|
|
end
|
|
|
|
function this.parse_stuff()
|
|
if this.recv_state == "head" then
|
|
-- Read headers
|
|
while this.recv_state == "head" do
|
|
local pos = this.data:find("\r\n")
|
|
if not pos then break end
|
|
|
|
this.read_header(this.data:sub(1,pos-1))
|
|
this.data = this.data:sub(pos+2)
|
|
end
|
|
end
|
|
|
|
-- This can always move from head to body in one packet.
|
|
if this.recv_state == "body" then
|
|
if this.data and #(this.data) >= this.req_len then
|
|
this.data = this.data:sub(1,this.req_len)
|
|
this.close()
|
|
this.state = "pass"
|
|
return this.data
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function this.finalise()
|
|
this.close()
|
|
this.state = ((this.recv_state == "body" and this.data) and "pass") or "fail"
|
|
return (this.recv_state == "body" and this.data)
|
|
end
|
|
|
|
function this.update()
|
|
if this.state == "send" then
|
|
this.rq = common.tcp_send(this.sockfd, this.rq)
|
|
if not this.rq then
|
|
this.fail()
|
|
elseif this.rq == "" then
|
|
this.state = "recv"
|
|
end
|
|
elseif this.state == "recv" then
|
|
local d = common.tcp_recv(this.sockfd)
|
|
if not d then
|
|
return this.finalise()
|
|
end
|
|
|
|
this.data = (this.data or "") .. d
|
|
return this.parse_stuff()
|
|
elseif this.state == "fail" then
|
|
return nil
|
|
elseif this.state == "pass" then
|
|
return this.data
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function this.close()
|
|
if this.sockfd then
|
|
common.tcp_close(this.sockfd)
|
|
this.sockfd = nil
|
|
end
|
|
end
|
|
|
|
function this.fail()
|
|
this.close()
|
|
this.state = "fail"
|
|
end
|
|
|
|
function this.init()
|
|
-- Parse the URL.
|
|
local rq = ""
|
|
local proto, host, port, root = url_parse(this.url)
|
|
if proto ~= "http" then
|
|
print("http requests require the http protocol to be used")
|
|
return
|
|
end
|
|
|
|
-- Set up the request.
|
|
-- TODO: Sanitise the URLs so they don't have spaces
|
|
rq = rq .. "GET " .. root .. " HTTP/1.1\r\n"
|
|
rq = rq .. "Host: ".. host .."\r\n"
|
|
rq = rq .. "Accept: */*\r\n"
|
|
rq = rq .. "User-Agent: IceballQuery/1.0\r\n"
|
|
rq = rq .. "\r\n"
|
|
|
|
-- Begin the fetch now.
|
|
-- FIXME: This can throw an error in some weird cases, apparently.
|
|
this.sockfd = common.tcp_connect(host, port)
|
|
if not this.sockfd then
|
|
print("connection failed")
|
|
return
|
|
end
|
|
this.rq = rq
|
|
|
|
-- Return this object.
|
|
return this
|
|
end
|
|
|
|
return this.init()
|
|
end
|
|
|