aboutsummaryrefslogtreecommitdiff
path: root/src/http.lua
diff options
context:
space:
mode:
Diffstat (limited to 'src/http.lua')
-rw-r--r--src/http.lua312
1 files changed, 312 insertions, 0 deletions
diff --git a/src/http.lua b/src/http.lua
new file mode 100644
index 0000000..8f08725
--- /dev/null
+++ b/src/http.lua
@@ -0,0 +1,312 @@
1-----------------------------------------------------------------------------
2-- Simple HTTP/1.1 support for the Lua language using the LuaSocket toolkit.
3-- Author: Diego Nehab
4-- Date: 26/12/2000
5-- Conforming to: RFC 2068
6-----------------------------------------------------------------------------
7
8-----------------------------------------------------------------------------
9-- Program constants
10-----------------------------------------------------------------------------
11-- connection timeout in seconds
12local TIMEOUT = 60
13-- default port for document retrieval
14local PORT = 80
15-- user agent field sent in request
16local USERAGENT = "LuaSocket/HTTP 1.0"
17
18-----------------------------------------------------------------------------
19-- Tries to get a line from the server or close socket if error
20-- sock: socket connected to the server
21-- Returns
22-- line: line received or nil in case of error
23-- err: error message if any
24-----------------------------------------------------------------------------
25local try_getline = function(sock)
26 line, err = sock:receive()
27 if err then
28 sock:close()
29 return nil, err
30 end
31 return line
32end
33
34-----------------------------------------------------------------------------
35-- Tries to send a line to the server or close socket if error
36-- sock: socket connected to the server
37-- line: line to send
38-- Returns
39-- err: error message if any
40-----------------------------------------------------------------------------
41local try_sendline = function(sock, line)
42 err = sock:send(line)
43 if err then sock:close() end
44 return err
45end
46
47-----------------------------------------------------------------------------
48-- Retrieves status from http reply
49-- Input
50-- reply: http reply string
51-- Returns
52-- status: integer with status code
53-----------------------------------------------------------------------------
54local get_status = function(reply)
55 local _,_, status = strfind(reply, " (%d%d%d) ")
56 return tonumber(status)
57end
58
59-----------------------------------------------------------------------------
60-- Receive server reply messages
61-- Input
62-- sock: server socket
63-- Returns
64-- status: server reply status code or nil if error
65-- reply: full server reply
66-- err: error message if any
67-----------------------------------------------------------------------------
68local get_reply = function(sock)
69 local reply, err
70 reply, err = %try_getline(sock)
71 if not err then return %get_status(reply), reply
72 else return nil, nil, err end
73end
74
75-----------------------------------------------------------------------------
76-- Receive and parse mime headers
77-- Input
78-- sock: server socket
79-- mime: a table that might already contain mime headers
80-- Returns
81-- mime: a table with all mime headers in the form
82-- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"}
83-- all name_i are lowercase
84-- nil and error message in case of error
85-----------------------------------------------------------------------------
86local get_mime = function(sock, mime)
87 local line, err
88 local name, value
89 -- get first line
90 line, err = %try_getline(sock)
91 if err then return nil, err end
92 -- headers go until a blank line is found
93 while line ~= "" do
94 -- get field-name and value
95 _,_, name, value = strfind(line, "(.-):%s*(.*)")
96 name = strlower(name)
97 -- get next line (value might be folded)
98 line, err = %try_getline(sock)
99 if err then return nil, err end
100 -- unfold any folded values
101 while not err and line ~= "" and (strsub(line, 1, 1) == " ") do
102 value = value .. line
103 line, err = %try_getline(sock)
104 if err then return nil, err end
105 end
106 -- save pair in table
107 if mime[name] then
108 -- join any multiple field
109 mime[name] = mime[name] .. ", " .. value
110 else
111 -- new field
112 mime[name] = value
113 end
114 end
115 return mime
116end
117
118-----------------------------------------------------------------------------
119-- Receives http body
120-- Input
121-- sock: server socket
122-- mime: initial mime headers
123-- Returns
124-- body: a string containing the body of the document
125-- nil and error message in case of error
126-- Obs:
127-- mime: headers might be modified by chunked transfer
128-----------------------------------------------------------------------------
129local get_body = function(sock, mime)
130 local body, err
131 if mime["transfer-encoding"] == "chunked" then
132 local chunk_size, line
133 body = ""
134 repeat
135 -- get chunk size, skip extention
136 line, err = %try_getline(sock)
137 if err then return nil, err end
138 chunk_size = tonumber(gsub(line, ";.*", ""), 16)
139 if not chunk_size then
140 sock:close()
141 return nil, "invalid chunk size"
142 end
143 -- get chunk
144 line, err = sock:receive(chunk_size)
145 if err then
146 sock:close()
147 return nil, err
148 end
149 -- concatenate new chunk
150 body = body .. line
151 -- skip blank line
152 _, err = %try_getline(sock)
153 if err then return nil, err end
154 until chunk_size <= 0
155 -- store extra mime headers
156 --_, err = %get_mime(sock, mime)
157 --if err then return nil, err end
158 elseif mime["content-length"] then
159 body, err = sock:receive(tonumber(mime["content-length"]))
160 if err then
161 sock:close()
162 return nil, err
163 end
164 else
165 -- get it all until connection closes!
166 body, err = sock:receive("*a")
167 if err then
168 sock:close()
169 return nil, err
170 end
171 end
172 -- return whole body
173 return body
174end
175
176-----------------------------------------------------------------------------
177-- Parses a url and returns its scheme, user, password, host, port
178-- and path components, according to RFC 1738, Uniform Resource Locators (URL),
179-- of December 1994
180-- Input
181-- url: unique resource locator desired
182-- default: table containing default values to be returned
183-- Returns
184-- table with the following fields:
185-- host: host to connect
186-- path: url path
187-- port: host port to connect
188-- user: user name
189-- pass: password
190-- scheme: protocol
191-----------------------------------------------------------------------------
192local split_url = function(url, default)
193 -- initialize default parameters
194 local parsed = default or {}
195 -- get scheme
196 url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end)
197 -- get user name and password. both can be empty!
198 -- moreover, password can be ommited
199 url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p)
200 %parsed.user = u
201 -- there can be an empty password, but the ':' has to be there
202 -- or else there is no password
203 %parsed.pass = nil -- kill default password
204 if c == ":" then %parsed.pass = p end
205 end)
206 -- get host
207 url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end)
208 -- get port if any
209 url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end)
210 -- whatever is left is the path
211 if url ~= "" then parsed.path = url end
212 return parsed
213end
214
215-----------------------------------------------------------------------------
216-- Sends a GET message through socket
217-- Input
218-- socket: http connection socket
219-- path: path requested
220-- mime: mime headers to send in request
221-- Returns
222-- err: nil in case of success, error message otherwise
223-----------------------------------------------------------------------------
224local send_get = function(sock, path, mime)
225 local err = %try_sendline(sock, "GET " .. path .. " HTTP/1.1\r\n")
226 if err then return err end
227 for i, v in mime do
228 err = %try_sendline(sock, i .. ": " .. v .. "\r\n")
229 if err then return err end
230 end
231 err = %try_sendline(sock, "\r\n")
232 return err
233end
234
235-----------------------------------------------------------------------------
236-- Converts field names to lowercase
237-- Input
238-- headers: user header fields
239-- parsed: parsed url components
240-- Returns
241-- mime: a table with the same headers, but with lowercase field names
242-----------------------------------------------------------------------------
243local fill_headers = function(headers, parsed)
244 local mime = {}
245 headers = headers or {}
246 for i,v in headers do
247 mime[strlower(i)] = v
248 end
249 mime["connection"] = "close"
250 mime["host"] = parsed.host
251 mime["user-agent"] = %USERAGENT
252 if parsed.user and parsed.pass then -- Basic Authentication
253 mime["authorization"] = "Basic "..
254 base64(parsed.user .. ":" .. parsed.pass)
255 end
256 return mime
257end
258
259-----------------------------------------------------------------------------
260-- We need base64 convertion routines for Basic Authentication Scheme
261-----------------------------------------------------------------------------
262dofile("base64.lua")
263
264-----------------------------------------------------------------------------
265-- Downloads and receives a http url, with its mime headers
266-- Input
267-- url: unique resource locator desired
268-- headers: headers to send with request
269-- tried: is this an authentication retry?
270-- Returns
271-- body: document body, if successfull
272-- mime: headers received with document, if sucessfull
273-- reply: server reply, if successfull
274-- err: error message, if any
275-----------------------------------------------------------------------------
276function http_get(url, headers)
277 local sock, err, mime, body, status, reply
278 -- get url components
279 local parsed = %split_url(url, {port = %PORT, path ="/"})
280 -- fill default headers
281 headers = %fill_headers(headers, parsed)
282 -- try connection
283 sock, err = connect(parsed.host, parsed.port)
284 if not sock then return nil, nil, nil, err end
285 -- set connection timeout
286 sock:timeout(%TIMEOUT)
287 -- send request
288 err = %send_get(sock, parsed.path, headers)
289 if err then return nil, nil, nil, err end
290 -- get server message
291 status, reply, err = %get_reply(sock)
292 if err then return nil, nil, nil, err end
293 -- get url accordingly
294 if status == 200 then -- ok, go on and get it
295 mime, err = %get_mime(sock, {})
296 if err then return nil, nil, reply, err end
297 body, err = %get_body(sock, mime)
298 if err then return nil, mime, reply, err end
299 sock:close()
300 return body, mime, reply
301 elseif status == 301 then -- moved permanently, try again
302 mime = %get_mime(sock, {})
303 sock:close()
304 if mime["location"] then return http_get(mime["location"], headers)
305 else return nil, mime, reply end
306 elseif status == 401 then
307 mime, err = %get_mime(sock, {})
308 if err then return nil, nil, reply, err end
309 return nil, mime, reply
310 end
311 return nil, nil, reply
312end