aboutsummaryrefslogtreecommitdiff
path: root/src/http.lua
diff options
context:
space:
mode:
Diffstat (limited to 'src/http.lua')
-rw-r--r--src/http.lua98
1 files changed, 55 insertions, 43 deletions
diff --git a/src/http.lua b/src/http.lua
index 1a7b101..91c52da 100644
--- a/src/http.lua
+++ b/src/http.lua
@@ -28,9 +28,40 @@ PORT = 80
28USERAGENT = socket._VERSION 28USERAGENT = socket._VERSION
29 29
30----------------------------------------------------------------------------- 30-----------------------------------------------------------------------------
31-- Reads MIME headers from a connection, unfolding where needed
32-----------------------------------------------------------------------------
33local function receiveheaders(sock, headers)
34 local line, name, value, err
35 headers = headers or {}
36 -- get first line
37 line, err = sock:receive()
38 if err then return nil, err end
39 -- headers go until a blank line is found
40 while line ~= "" do
41 -- get field-name and value
42 name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)"))
43 if not (name and value) then return nil, "malformed reponse headers" end
44 name = string.lower(name)
45 -- get next line (value might be folded)
46 line, err = sock:receive()
47 if err then return nil, err end
48 -- unfold any folded values
49 while string.find(line, "^%s") do
50 value = value .. line
51 line = sock:receive()
52 if err then return nil, err end
53 end
54 -- save pair in table
55 if headers[name] then headers[name] = headers[name] .. ", " .. value
56 else headers[name] = value end
57 end
58 return headers
59end
60
61-----------------------------------------------------------------------------
31-- Extra sources and sinks 62-- Extra sources and sinks
32----------------------------------------------------------------------------- 63-----------------------------------------------------------------------------
33socket.sourcet["http-chunked"] = function(sock) 64socket.sourcet["http-chunked"] = function(sock, headers)
34 return base.setmetatable({ 65 return base.setmetatable({
35 getfd = function() return sock:getfd() end, 66 getfd = function() return sock:getfd() end,
36 dirty = function() return sock:dirty() end 67 dirty = function() return sock:dirty() end
@@ -42,18 +73,15 @@ socket.sourcet["http-chunked"] = function(sock)
42 local size = base.tonumber(string.gsub(line, ";.*", ""), 16) 73 local size = base.tonumber(string.gsub(line, ";.*", ""), 16)
43 if not size then return nil, "invalid chunk size" end 74 if not size then return nil, "invalid chunk size" end
44 -- was it the last chunk? 75 -- was it the last chunk?
45 if size <= 0 then 76 if size > 0 then
46 -- skip trailer headers, if any 77 -- if not, get chunk and skip terminating CRLF
47 local line, err = sock:receive()
48 while not err and line ~= "" do
49 line, err = sock:receive()
50 end
51 return nil, err
52 else
53 -- get chunk and skip terminating CRLF
54 local chunk, err, part = sock:receive(size) 78 local chunk, err, part = sock:receive(size)
55 if chunk then sock:receive() end 79 if chunk then sock:receive() end
56 return chunk, err 80 return chunk, err
81 else
82 -- if it was, read trailers into headers table
83 headers, err = receiveheaders(sock, headers)
84 if not headers then return nil, err end
57 end 85 end
58 end 86 end
59 }) 87 })
@@ -78,8 +106,8 @@ end
78local metat = { __index = {} } 106local metat = { __index = {} }
79 107
80-- default connect function, respecting the timeout 108-- default connect function, respecting the timeout
81local function connect(host, port) 109local function connect(host, port, create)
82 local c, e = socket.tcp() 110 local c, e = (create or socket.tcp)()
83 if not c then return nil, e end 111 if not c then return nil, e end
84 c:settimeout(TIMEOUT) 112 c:settimeout(TIMEOUT)
85 local r, e = c:connect(host, port or PORT) 113 local r, e = c:connect(host, port or PORT)
@@ -90,9 +118,9 @@ local function connect(host, port)
90 return c 118 return c
91end 119end
92 120
93function open(host, port, user) 121function open(host, port, create)
94 -- create socket with user connect function, or with default 122 -- create socket with user connect function, or with default
95 local c = socket.try((user or connect)(host, port)) 123 local c = socket.try(connect(host, port, create))
96 -- create our http request object, pointing to the socket 124 -- create our http request object, pointing to the socket
97 local h = base.setmetatable({ c = c }, metat) 125 local h = base.setmetatable({ c = c }, metat)
98 -- make sure the object close gets called on exception 126 -- make sure the object close gets called on exception
@@ -130,37 +158,16 @@ function metat.__index:receivestatusline()
130end 158end
131 159
132function metat.__index:receiveheaders() 160function metat.__index:receiveheaders()
133 local line, name, value 161 return self.try(receiveheaders(self.c))
134 local headers = {}
135 -- get first line
136 line = self.try(self.c:receive())
137 -- headers go until a blank line is found
138 while line ~= "" do
139 -- get field-name and value
140 name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)"))
141 self.try(name and value, "malformed reponse headers")
142 name = string.lower(name)
143 -- get next line (value might be folded)
144 line = self.try(self.c:receive())
145 -- unfold any folded values
146 while string.find(line, "^%s") do
147 value = value .. line
148 line = self.try(self.c:receive())
149 end
150 -- save pair in table
151 if headers[name] then headers[name] = headers[name] .. ", " .. value
152 else headers[name] = value end
153 end
154 return headers
155end 162end
156 163
157function metat.__index:receivebody(headers, sink, step) 164function metat.__index:receivebody(headers, sink, step)
158 sink = sink or ltn12.sink.null() 165 sink = sink or ltn12.sink.null()
159 step = step or ltn12.pump.step 166 step = step or ltn12.pump.step
160 local length = base.tonumber(headers["content-length"]) 167 local length = base.tonumber(headers["content-length"])
161 local TE = headers["transfer-encoding"] 168 local t = headers["transfer-encoding"] -- shortcut
162 local mode = "default" -- connection close 169 local mode = "default" -- connection close
163 if TE and TE ~= "identity" then mode = "http-chunked" 170 if t and t ~= "identity" then mode = "http-chunked"
164 elseif base.tonumber(headers["content-length"]) then mode = "by-length" end 171 elseif base.tonumber(headers["content-length"]) then mode = "by-length" end
165 return self.try(ltn12.pump.all(socket.source(mode, self.c, length), 172 return self.try(ltn12.pump.all(socket.source(mode, self.c, length),
166 sink, step)) 173 sink, step))
@@ -198,16 +205,21 @@ local function adjustproxy(reqt)
198end 205end
199 206
200local function adjustheaders(headers, host) 207local function adjustheaders(headers, host)
201 local lower = {} 208 -- default headers
202 -- override with user values 209 local lower = {
210 ["user-agent"] = USERAGENT,
211 ["host"] = host,
212 ["connection"] = "close, TE",
213 ["te"] = "trailers"
214 }
215 -- override with user headers
203 for i,v in pairs(headers or lower) do 216 for i,v in pairs(headers or lower) do
204 lower[string.lower(i)] = v 217 lower[string.lower(i)] = v
205 end 218 end
206 lower["user-agent"] = lower["user-agent"] or USERAGENT
207 lower["host"] = lower["host"] or host
208 return lower 219 return lower
209end 220end
210 221
222-- default url parts
211local default = { 223local default = {
212 host = "", 224 host = "",
213 port = PORT, 225 port = PORT,
@@ -280,7 +292,7 @@ end
280 292
281function trequest(reqt) 293function trequest(reqt)
282 reqt = adjustrequest(reqt) 294 reqt = adjustrequest(reqt)
283 local h = open(reqt.host, reqt.port, reqt.connect) 295 local h = open(reqt.host, reqt.port, reqt.create)
284 h:sendrequestline(reqt.method, reqt.uri) 296 h:sendrequestline(reqt.method, reqt.uri)
285 h:sendheaders(reqt.headers) 297 h:sendheaders(reqt.headers)
286 if reqt.source then h:sendbody(reqt.headers, reqt.source, reqt.step) end 298 if reqt.source then h:sendbody(reqt.headers, reqt.source, reqt.step) end