Make LuCIttpd work OOTB
[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