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