4c3016a8ca8cb9240cfe76048ec26c03f345516b
[project/luci.git] / libs / lucid-http / luasrc / lucid / http / server.lua
1 --[[
2 LuCId HTTP-Slave
3 (c) 2009 Steven Barth <steven@midlink.org>
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9         http://www.apache.org/licenses/LICENSE-2.0
10
11 $Id$
12 ]]--
13
14 local ipairs, pairs = ipairs, pairs
15 local tostring, tonumber = tostring, tonumber
16 local pcall, assert, type = pcall, assert, type
17 local set_memory_limit = set_memory_limit
18
19 local os = require "os"
20 local nixio = require "nixio"
21 local util = require "luci.util"
22 local ltn12 = require "luci.ltn12"
23 local proto = require "luci.http.protocol"
24 local table = require "table"
25 local date = require "luci.http.protocol.date"
26
27 module "luci.lucid.http.server"
28
29 VERSION = "1.0"
30
31 statusmsg = {
32         [200] = "OK",
33         [206] = "Partial Content",
34         [301] = "Moved Permanently",
35         [302] = "Found",
36         [304] = "Not Modified",
37         [400] = "Bad Request",
38         [401] = "Unauthorized",
39         [403] = "Forbidden",
40         [404] = "Not Found",
41         [405] = "Method Not Allowed",
42         [408] = "Request Time-out",
43         [411] = "Length Required",
44         [412] = "Precondition Failed",
45         [416] = "Requested range not satisfiable",
46         [500] = "Internal Server Error",
47         [503] = "Server Unavailable",
48 }
49
50 -- File Resource
51 IOResource = util.class()
52
53 function IOResource.__init__(self, fd, len)
54         self.fd, self.len = fd, len
55 end
56
57
58 -- Server handler implementation
59 Handler = util.class()
60
61 function Handler.__init__(self, name)
62         self.name = name or tostring(self)
63 end
64
65 -- Creates a failure reply
66 function Handler.failure(self, code, msg)       
67         return code, { ["Content-Type"] = "text/plain" }, ltn12.source.string(msg)
68 end
69
70 -- Access Restrictions
71 function Handler.restrict(self, restriction)
72         if not self.restrictions then
73                 self.restrictions = {restriction}
74         else
75                 self.restrictions[#self.restrictions+1] = restriction
76         end
77 end
78
79 -- Check restrictions
80 function Handler.checkrestricted(self, request)
81         if not self.restrictions then
82                 return
83         end
84
85         local localif, user, pass
86         
87         for _, r in ipairs(self.restrictions) do
88                 local stat = true
89                 if stat and r.interface then    -- Interface restriction
90                         if not localif then
91                                 for _, v in ipairs(request.server.interfaces) do
92                                         if v.addr == request.env.SERVER_ADDR then
93                                                 localif = v.name
94                                                 break
95                                         end
96                                 end
97                         end
98                         
99                         if r.interface ~= localif then
100                                 stat = false
101                         end
102                 end
103                 
104                 if stat and r.user then -- User restriction
105                         local rh, pwe
106                         if not user then
107                                 rh = (request.headers.Authorization or ""):match("Basic (.*)")
108                                 rh = rh and nixio.bin.b64decode(rh) or ""
109                                 user, pass = rh:match("(.*):(.*)")
110                                 pass = pass or ""
111                         end
112                         pwe = nixio.getsp and nixio.getsp(r.user) or nixio.getpw(r.user)
113                         local pwh = (user == r.user) and pwe and (pwe.pwdp or pwe.passwd)
114                         if not pwh or #pwh < 1 or nixio.crypt(pass, pwh) ~= pwh then
115                                 stat = false
116                         end
117                 end
118                 
119                 if stat then
120                         return
121                 end
122         end
123         
124         return 401, {
125                 ["WWW-Authenticate"] = ('Basic realm=%q'):format(self.name),
126                 ["Content-Type"] = 'text/plain'
127         }, ltn12.source.string("Unauthorized")
128 end
129
130 -- Processes a request
131 function Handler.process(self, request, sourcein)
132         local stat, code, hdr, sourceout
133         
134         local stat, code, msg = self:checkrestricted(request)
135         if stat then    -- Access Denied
136                 return stat, code, msg
137         end
138
139         -- Detect request Method
140         local hname = "handle_" .. request.env.REQUEST_METHOD
141         if self[hname] then
142                 -- Run the handler
143                 stat, code, hdr, sourceout = pcall(self[hname], self, request, sourcein)
144
145                 -- Check for any errors
146                 if not stat then
147                         return self:failure(500, code)
148                 end
149         else
150                 return self:failure(405, statusmsg[405])
151         end
152
153         return code, hdr, sourceout
154 end
155
156
157 VHost = util.class()
158
159 function VHost.__init__(self)
160         self.handlers = {}
161 end
162
163 function VHost.process(self, request, ...)
164         local handler
165         local hlen = -1
166         local uri = request.env.SCRIPT_NAME
167         local sc = ("/"):byte()
168
169         -- SCRIPT_NAME
170         request.env.SCRIPT_NAME = ""
171
172         -- Call URI part
173         request.env.PATH_INFO = uri
174         
175         for k, h in pairs(self.handlers) do
176                 if #k > hlen then
177                         if uri == k or (uri:sub(1, #k) == k and uri:byte(#k+1) == sc) then
178                                 handler = h
179                                 hlen = #k
180                                 request.env.SCRIPT_NAME = k
181                                 request.env.PATH_INFO   = uri:sub(#k+1)
182                         end
183                 end
184         end
185         
186         if handler then
187                 return handler:process(request, ...)
188         else
189                 return 404, nil, ltn12.source.string("No such handler")
190         end
191 end
192
193 function VHost.get_handlers(self)
194         return self.handlers
195 end
196
197 function VHost.set_handler(self, match, handler)
198         self.handlers[match] = handler
199 end
200
201
202 local function remapipv6(adr)
203         local map = "::ffff:"
204         if adr:sub(1, #map) == map then
205                 return adr:sub(#map+1)
206         else
207                 return adr
208         end 
209 end
210
211 local function chunksource(sock, buffer)
212         buffer = buffer or ""
213         return function()
214                 local output
215                 local _, endp, count = buffer:find("^([0-9a-fA-F]+);?.-\r\n")
216                 while not count and #buffer <= 1024 do
217                         local newblock, code = sock:recv(1024 - #buffer)
218                         if not newblock then
219                                 return nil, code
220                         end
221                         buffer = buffer .. newblock  
222                         _, endp, count = buffer:find("^([0-9a-fA-F]+);?.-\r\n")
223                 end
224                 count = tonumber(count, 16)
225                 if not count then
226                         return nil, -1, "invalid encoding"
227                 elseif count == 0 then
228                         return nil
229                 elseif count + 2 <= #buffer - endp then
230                         output = buffer:sub(endp+1, endp+count)
231                         buffer = buffer:sub(endp+count+3)
232                         return output
233                 else
234                         output = buffer:sub(endp+1, endp+count)
235                         buffer = ""
236                         if count - #output > 0 then
237                                 local remain, code = sock:recvall(count-#output)
238                                 if not remain then
239                                         return nil, code
240                                 end
241                                 output = output .. remain
242                                 count, code = sock:recvall(2)
243                         else
244                                 count, code = sock:recvall(count+2-#buffer+endp)
245                         end
246                         if not count then
247                                 return nil, code
248                         end
249                         return output
250                 end
251         end
252 end
253
254 local function chunksink(sock)
255         return function(chunk, err)
256                 if not chunk then
257                         return sock:writeall("0\r\n\r\n")
258                 else
259                         return sock:writeall(("%X\r\n%s\r\n"):format(#chunk, chunk))
260                 end
261         end
262 end
263
264 Server = util.class()
265
266 function Server.__init__(self)
267         self.vhosts = {}
268 end
269
270 function Server.get_vhosts(self)
271         return self.vhosts
272 end
273
274 function Server.set_vhost(self, name, vhost)
275         self.vhosts[name] = vhost
276 end
277
278 function Server.error(self, client, code, msg)
279         hcode = tostring(code)
280         
281         client:writeall( "HTTP/1.0 " .. hcode .. " " ..
282          statusmsg[code] .. "\r\n" )
283         client:writeall( "Connection: close\r\n" )
284         client:writeall( "Content-Type: text/plain\r\n\r\n" )
285
286         if msg then
287                 client:writeall( "HTTP-Error " .. code .. ": " .. msg .. "\r\n" )
288         end
289         
290         client:close()
291 end
292
293 local hdr2env = {
294         ["Content-Length"] = "CONTENT_LENGTH",
295         ["Content-Type"] = "CONTENT_TYPE",
296         ["Content-type"] = "CONTENT_TYPE",
297         ["Accept"] = "HTTP_ACCEPT",
298         ["Accept-Charset"] = "HTTP_ACCEPT_CHARSET",
299         ["Accept-Encoding"] = "HTTP_ACCEPT_ENCODING",
300         ["Accept-Language"] = "HTTP_ACCEPT_LANGUAGE",
301         ["Connection"] = "HTTP_CONNECTION",
302         ["Cookie"] = "HTTP_COOKIE",
303         ["Host"] = "HTTP_HOST",
304         ["Referer"] = "HTTP_REFERER",
305         ["User-Agent"] = "HTTP_USER_AGENT"
306 }
307
308 function Server.parse_headers(self, source)
309         local env = {}
310         local req = {env = env, headers = {}}
311         local line, err
312
313         repeat  -- Ignore empty lines
314                 line, err = source()
315                 if not line then
316                         return nil, err
317                 end
318         until #line > 0
319         
320         env.REQUEST_METHOD, env.REQUEST_URI, env.SERVER_PROTOCOL =
321                 line:match("^([A-Z]+) ([^ ]+) (HTTP/1%.[01])$")
322                 
323         if not env.REQUEST_METHOD then
324                 return nil, "invalid magic"
325         end
326         
327         local key, envkey, val
328         repeat
329                 line, err = source()
330                 if not line then
331                         return nil, err
332                 elseif #line > 0 then   
333                         key, val = line:match("^([%w-]+)%s?:%s?(.*)")
334                         if key then
335                                 req.headers[key] = val
336                                 envkey = hdr2env[key]
337                                 if envkey then
338                                         env[envkey] = val
339                                 end
340                         else
341                                 return nil, "invalid header line"
342                         end
343                 else
344                         break
345                 end
346         until false
347         
348         env.SCRIPT_NAME, env.QUERY_STRING = env.REQUEST_URI:match("([^?]*)%??(.*)")
349         return req
350 end
351
352
353 function Server.process(self, client, env)
354         local sourcein  = function() end
355         local sourcehdr = client:linesource()
356         local sinkout
357         local buffer
358         
359         local close = false
360         local stat, code, msg, message, err
361         
362         env.config.memlimit = tonumber(env.config.memlimit)
363         if env.config.memlimit and set_memory_limit then
364                 set_memory_limit(env.config.memlimit)
365         end
366
367         client:setsockopt("socket", "rcvtimeo", 5)
368         client:setsockopt("socket", "sndtimeo", 5)
369         
370         repeat
371                 -- parse headers
372                 message, err = self:parse_headers(sourcehdr)
373
374                 -- any other error
375                 if not message or err then
376                         if err == 11 then       -- EAGAIN
377                                 break
378                         else
379                                 return self:error(client, 400, err)
380                         end
381                 end
382
383                 -- Prepare sources and sinks
384                 buffer = sourcehdr(true)
385                 sinkout = client:sink()
386                 message.server = env
387                 
388                 if client:is_tls_socket() then
389                         message.env.HTTPS = "on"
390                 end
391                 
392                 -- Addresses
393                 message.env.REMOTE_ADDR = remapipv6(env.host)
394                 message.env.REMOTE_PORT = env.port
395                 
396                 local srvaddr, srvport = client:getsockname()
397                 message.env.SERVER_ADDR = remapipv6(srvaddr)
398                 message.env.SERVER_PORT = srvport
399                 
400                 -- keep-alive
401                 if message.env.SERVER_PROTOCOL == "HTTP/1.1" then
402                         close = (message.env.HTTP_CONNECTION == "close")
403                 else
404                         close = not message.env.HTTP_CONNECTION 
405                                 or message.env.HTTP_CONNECTION == "close"
406                 end
407
408                 -- Uncomment this to disable keep-alive
409                 close = close or env.config.nokeepalive
410         
411                 if message.env.REQUEST_METHOD == "GET"
412                 or message.env.REQUEST_METHOD == "HEAD" then
413                         -- Be happy
414                         
415                 elseif message.env.REQUEST_METHOD == "POST" then
416                         -- If we have a HTTP/1.1 client and an Expect: 100-continue header
417                         -- respond with HTTP 100 Continue message
418                         if message.env.SERVER_PROTOCOL == "HTTP/1.1" 
419                         and message.headers.Expect == '100-continue' then
420                                 client:writeall("HTTP/1.1 100 Continue\r\n\r\n")
421                         end
422                         
423                         if message.headers['Transfer-Encoding'] and
424                          message.headers['Transfer-Encoding'] ~= "identity" then
425                                 sourcein = chunksource(client, buffer)
426                                 buffer = nil
427                         elseif message.env.CONTENT_LENGTH then
428                                 local len = tonumber(message.env.CONTENT_LENGTH)
429                                 if #buffer >= len then
430                                         sourcein = ltn12.source.string(buffer:sub(1, len))
431                                         buffer = buffer:sub(len+1)
432                                 else
433                                         sourcein = ltn12.source.cat(
434                                                 ltn12.source.string(buffer),
435                                                 client:blocksource(nil, len - #buffer)
436                                         )
437                                 end
438                         else
439                                 return self:error(client, 411, statusmsg[411])
440                         end
441
442                         close = true
443                 else
444                         return self:error(client, 405, statusmsg[405])
445                 end
446
447
448                 local host = self.vhosts[message.env.HTTP_HOST] or self.vhosts[""]
449                 if not host then
450                         return self:error(client, 404, "No virtual host found")
451                 end
452                 
453                 local code, headers, sourceout = host:process(message, sourcein)
454                 headers = headers or {}
455                 
456                 -- Post process response
457                 if sourceout then
458                         if util.instanceof(sourceout, IOResource) then
459                                 if not headers["Content-Length"] then
460                                         headers["Content-Length"] = sourceout.len
461                                 end
462                         end
463                         if not headers["Content-Length"] then
464                                 if message.env.SERVER_PROTOCOL == "HTTP/1.1" then
465                                         headers["Transfer-Encoding"] = "chunked"
466                                         sinkout = chunksink(client)
467                                 else
468                                         close = true
469                                 end
470                         end
471                 elseif message.env.REQUEST_METHOD ~= "HEAD" then
472                         headers["Content-Length"] = 0
473                 end
474                 
475                 if close then
476                         headers["Connection"] = "close"
477                 elseif message.env.SERVER_PROTOCOL == "HTTP/1.0" then
478                         headers["Connection"] = "Keep-Alive"
479                 end 
480
481                 headers["Date"] = date.to_http(os.time())
482                 local header = {
483                         message.env.SERVER_PROTOCOL .. " " .. tostring(code) .. " " 
484                                 .. statusmsg[code],
485                         "Server: LuCId-HTTPd/" .. VERSION
486                 }
487
488                 
489                 for k, v in pairs(headers) do
490                         if type(v) == "table" then
491                                 for _, h in ipairs(v) do
492                                         header[#header+1] = k .. ": " .. h
493                                 end
494                         else
495                                 header[#header+1] = k .. ": " .. v
496                         end
497                 end
498
499                 header[#header+1] = ""
500                 header[#header+1] = ""
501                 
502                 -- Output
503                 stat, code, msg = client:writeall(table.concat(header, "\r\n"))
504
505                 if sourceout and stat then
506                         if util.instanceof(sourceout, IOResource) then
507                                 stat, code, msg = sourceout.fd:copyz(client, sourceout.len)
508                         else
509                                 stat, msg = ltn12.pump.all(sourceout, sinkout)
510                         end
511                 end
512
513
514                 -- Write errors
515                 if not stat then
516                         if msg then
517                                 nixio.syslog("err", "Error sending data to " .. env.host ..
518                                         ": " .. msg .. "\n")
519                         end
520                         break
521                 end
522                 
523                 if buffer then
524                         sourcehdr(buffer)
525                 end
526         until close
527         
528         client:shutdown()
529         client:close()
530 end