branches/luci-0.8: remove bitrot
[project/luci.git] / libs / lucittpd / luasrc / ttpd / server.lua
1 --[[
2 LuCIttpd
3 (c) 2008 Steven Barth <steven@midlink.org>
4 (c) 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
5
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 $Id$
13 ]]--
14
15 local ipairs, pairs = ipairs, pairs
16 local tostring, tonumber = tostring, tonumber
17 local pcall, assert = pcall, assert
18
19 local os = require "os"
20 local io = require "io"
21 local uci = require "luci.model.uci"
22 local util = require "luci.util"
23 local ltn12 = require "luci.ltn12"
24 local proto = require "luci.http.protocol"
25 local string = require "string"
26 local date = require "luci.http.protocol.date"
27
28 module "luci.ttpd.server"
29
30 BUFSIZE = 4096
31 VERSION = 0.91
32
33
34 -- File Resource
35 IOResource = util.class()
36
37 function IOResource.__init__(self, fd, offset, len)
38 self.fd, self.offset, self.len = fd, offset, len
39 end
40
41
42 VHost = util.class()
43
44 function VHost.__init__(self, handler)
45 self.handler = handler
46 self.dhandler = {}
47 end
48
49 function VHost.process(self, request, sourcein, sinkerr, ...)
50 local handler = self.handler
51
52 local uri = request.env.REQUEST_URI:match("^([^?]*)")
53
54 -- SCRIPT_NAME
55 request.env.SCRIPT_NAME = ""
56
57 -- Call URI part
58 request.env.PATH_INFO = uri
59
60 for k, dhandler in pairs(self.dhandler) do
61 if k == uri or k.."/" == uri:sub(1, #k+1) then
62 handler = dhandler
63 request.env.SCRIPT_NAME = k
64 request.env.PATH_INFO = uri:sub(#k+1)
65 break;
66 end
67 end
68
69 if handler then
70 return handler:process(request, sourcein, sinkerr, ...)
71 end
72 end
73
74 function VHost.get_default_handler(self)
75 return self.handler
76 end
77
78 function VHost.set_default_handler(self, handler)
79 self.handler = handler
80 end
81
82 function VHost.get_handlers(self)
83 return self.dhandler
84 end
85
86 function VHost.set_handler(self, match, handler)
87 self.dhandler[match] = handler
88 end
89
90
91
92 Server = util.class()
93
94 function Server.__init__(self, host)
95 self.uci = uci.cursor()
96 self.host = host
97 self.vhosts = {}
98
99 self.rbuf = ""
100 self.wbuf = ""
101 end
102
103 function Server.get_default_vhost(self)
104 return self.host
105 end
106
107 function Server.set_default_vhost(self, vhost)
108 self.host = vhost
109 end
110
111 function Server.get_vhosts(self)
112 return self.vhosts
113 end
114
115 function Server.set_vhost(self, name, vhost)
116 self.vhosts[name] = vhost
117 end
118
119 function Server.flush(self)
120 if #self.wbuf > 0 then
121 self._write(self.wbuf)
122 self.wbuf = ""
123 end
124 end
125
126 function Server.read(self, len)
127 while #self.rbuf < len do
128 self.rbuf = self.rbuf .. self._read(len - #self.rbuf)
129 end
130
131 local chunk = self.rbuf:sub(1, len)
132 self.rbuf = self.rbuf:sub(len + 1)
133 return chunk
134 end
135
136 function Server.limitsource(self, limit)
137 limit = limit or 0
138
139 return function()
140 if limit < 1 then
141 return nil
142 else
143 local read = (limit > BUFSIZE) and BUFSIZE or limit
144 limit = limit - read
145 return self:read(read)
146 end
147 end
148 end
149
150 -- Adapted from Luaposix
151 function Server.receiveheaders(self)
152 local line, name, value, err
153 local headers = {}
154 -- get first line
155 line, err = self:readline()
156 if err then return nil, err end
157 -- headers go until a blank line is found
158 while line do
159 -- get field-name and value
160 _, _, name, value = line:find("^(.-):%s*(.*)")
161 if not (name and value) then return nil, "malformed reponse headers" end
162 name = name:lower()
163 -- get next line (value might be folded)
164 line, err = self:readline()
165 if err then return nil, err end
166 -- unfold any folded values
167 while line:find("^%s") do
168 value = value .. line
169 line = self:readline()
170 if err then return nil, err end
171 end
172 -- save pair in table
173 if headers[name] then headers[name] = headers[name] .. ", " .. value
174 else headers[name] = value end
175 end
176 return headers
177 end
178
179 function Server.readchunk(self)
180 -- get chunk size, skip extention
181 local line, err = self:readline()
182 if err then return nil, err end
183 local size = tonumber(line:gsub(";.*", ""), 16)
184 if not size then return nil, "invalid chunk size" end
185 -- was it the last chunk?
186 if size > 0 then
187 -- if not, get chunk and skip terminating CRLF
188 local chunk, err, part = self:read(size)
189 if chunk then self:readline() end
190 return chunk, err
191 else
192 -- if it was, read trailers into headers table
193 headers, err = self:receiveheaders()
194 if not headers then return nil, err end
195 end
196 end
197
198 function Server.readline(self)
199 if #self.rbuf < 1 then
200 self.rbuf = self._read(BUFSIZE)
201 end
202
203 while true do
204 local le = self.rbuf:find("\r\n", nil, true)
205 if le then
206 if le == 1 then -- EoH
207 self.rbuf = self.rbuf:sub(le + 2)
208 return nil
209 else -- Header
210 local line = self.rbuf:sub(1, le - 1)
211 self.rbuf = self.rbuf:sub(le + 2)
212 return line
213 end
214 else
215 if #self.rbuf >= BUFSIZE then
216 return nil, "Invalid Request"
217 end
218 self.rbuf = self.rbuf .. self._read(BUFSIZE-#self.rbuf)
219 end
220 end
221 end
222
223 function Server.sink(self)
224 return function(chunk, err)
225 if err then
226 return nil, err
227 elseif chunk then
228 local stat, err = pcall(self.write, self, chunk)
229 if stat then
230 return stat
231 else
232 return nil, err
233 end
234 else
235 return true
236 end
237 end
238 end
239
240 function Server.chunksink(self)
241 return function(chunk, err)
242 local stat, err = pcall(self.writechunk, self, chunk)
243 if stat then
244 return stat
245 else
246 return nil, err
247 end
248 end
249 end
250
251 function Server.writechunk(self, chunk, err)
252 self:flush()
253 if not chunk then return self._write("0\r\n\r\n") end
254 local size = string.format("%X\r\n", #chunk)
255 return self._write(size .. chunk .. "\r\n")
256 end
257
258 function Server.write(self, chunk)
259 while #chunk > 0 do
260 local missing = BUFSIZE - #self.wbuf
261 self.wbuf = self.wbuf .. chunk:sub(1, missing)
262 chunk = chunk:sub(missing + 1)
263 if #self.wbuf == BUFSIZE then
264 assert(self._write(self.wbuf))
265 self.wbuf = ""
266 end
267 end
268 end
269
270 function Server.close(self)
271 self:flush()
272 self._close()
273 end
274
275 function Server.sendfile(self, fd, offset, len)
276 self:flush()
277 self._sendfile(fd, offset, len)
278 end
279
280
281 function Server.error(self, code, msg)
282 hcode = tostring(code)
283
284 self:write( "HTTP/1.0 " .. hcode .. " " ..
285 proto.statusmsg[code] .. "\r\n" )
286 self:write( "Connection: close\r\n" )
287 self:write( "Content-Type: text/plain\r\n\r\n" )
288
289 if msg then
290 self:write( "HTTP-Error " .. code .. ": " .. msg .. "\r\n" )
291 end
292 end
293
294
295 function Server.process(self, functions)
296 util.update(self, functions)
297
298 local sourcein = ltn12.source.empty()
299 local sourcehdr = function() return self:readline() or "" end
300 local sinkerr = ltn12.sink.file( io.stderr )
301 local sinkout = self:sink()
302
303 local close = false
304 local stat, message, err
305
306 repeat
307 -- parse headers
308 stat, message, err = pcall(proto.parse_message_header, sourcehdr)
309
310 -- remote socket closed
311 if not stat and message == 0 then
312 break
313 end
314
315 -- remote timeout
316 if not stat and message == 11 then
317 --self:error(408)
318 break
319 end
320
321 -- any other error
322 if not stat or not message then
323 self:error(400, err)
324 break
325 end
326
327 -- keep-alive
328 if message.http_version == 1.1 then
329 close = (message.env.HTTP_CONNECTION == "close")
330 else
331 close = not message.env.HTTP_CONNECTION or message.env.HTTP_CONNECTION == "close"
332 end
333 -- Uncomment this to disable keep-alive
334 close = not (self.uci:get("lucittpd", "lucittpd", "keepalive") == "1")
335
336 if message.request_method == "get" or message.request_method == "head" then
337 -- Be happy
338
339 elseif message.request_method == "post" then
340 -- If we have a HTTP/1.1 client and an Expect: 100-continue header then
341 -- respond with HTTP 100 Continue message
342 if message.http_version == 1.1 and message.headers['Expect'] and
343 message.headers['Expect'] == '100-continue'
344 then
345 self:write("HTTP/1.1 100 Continue\r\n\r\n")
346 end
347
348 if message.headers['Transfer-Encoding'] and
349 message.headers['Transfer-Encoding'] ~= "identity" then
350 sourcein = function() return self:readchunk() end
351 elseif message.env.CONTENT_LENGTH then
352 sourcein = self:limitsource(
353 tonumber(message.env.CONTENT_LENGTH)
354 )
355 else
356 self:error( 411, proto.statusmsg[411] )
357 break
358 end
359 else
360 self:error( 405, proto.statusmsg[405] )
361 break
362
363 end
364
365
366 local host = self.vhosts[message.env.HTTP_HOST] or self.host
367 if not host then
368 self:error( 500, "Unable to find matching host" )
369 break;
370 end
371
372 local response, sourceout = host:process(
373 message, sourcein, sinkerr,
374 client, io.stderr
375 )
376 if not response then
377 self:error( 500, "Error processing handler" )
378 end
379
380 -- Post process response
381 if sourceout then
382 if util.instanceof(sourceout, IOResource) then
383 if not response.headers["Content-Length"] then
384 response.headers["Content-Length"] = sourceout.len
385 end
386 end
387 if not response.headers["Content-Length"] then
388 if message.http_version == 1.1 then
389 response.headers["Transfer-Encoding"] = "chunked"
390 sinkout = self:chunksink()
391 else
392 close = true
393 end
394 end
395 elseif message.request_method ~= "head" then
396 response.headers["Content-Length"] = 0
397 end
398
399 if close then
400 response.headers["Connection"] = "close"
401 end
402
403 response.headers["Date"] = date.to_http(os.time())
404
405 local header =
406 message.env.SERVER_PROTOCOL .. " " ..
407 tostring(response.status) .. " " ..
408 proto.statusmsg[response.status] .. "\r\n"
409
410 header = header .. "Server: LuCIttpd/" .. tostring(VERSION) .. "\r\n"
411
412
413 for k,v in pairs(response.headers) do
414 header = header .. k .. ": " .. v .. "\r\n"
415 end
416
417 -- Output
418 local stat, err = pcall(function()
419 self:write(header .. "\r\n")
420
421 if sourceout then
422 if util.instanceof(sourceout, IOResource) then
423 self:sendfile(sourceout.fd, sourceout.offset, sourceout.len)
424 else
425 ltn12.pump.all(sourceout, sinkout)
426 end
427 end
428
429 self:flush()
430 end)
431
432 -- Write errors
433 if not stat then
434 if err == 107 then
435 -- Remote end closed the socket, so do we
436 elseif err then
437 io.stderr:write("Error sending data: " .. err .. "\n")
438 end
439 break
440 end
441 until close
442
443 self:close()
444 end