webmcp
view libraries/rocketcgi/rocketcgi.lua @ 201:1a10fef86d97
Added type checks to C functions of JSON library to avoid crashes due to wrong arguments
| author | jbe | 
|---|---|
| date | Thu Aug 14 05:18:20 2014 +0200 (2014-08-14) | 
| parents | 6c4a5b136074 | 
| children | 
 line source
     1 #!/usr/bin/env lua
     3 local assert         = assert
     4 local collectgarbage = collectgarbage
     5 local error          = error
     6 local getmetatable   = getmetatable
     7 local ipairs         = ipairs
     8 local next           = next
     9 local pairs          = pairs
    10 local print          = print
    11 local rawequal       = rawequal
    12 local rawget         = rawget
    13 local rawlen         = rawlen
    14 local rawset         = rawset
    15 local select         = select
    16 local setmetatable   = setmetatable
    17 local tonumber       = tonumber
    18 local tostring       = tostring
    19 local type           = type
    21 local io     = io
    22 local math   = math
    23 local os     = os
    24 local string = string
    25 local table  = table
    27 local _M = {}
    28 if _ENV then
    29   _ENV = _M
    30 else
    31   _G[...] = _M
    32   setfenv(1, _M)
    33 end
    35 data_sent = false
    37 --[[--
    38 rocketcgi.add_header(
    39   string_part1,        -- string
    40   string_part2,        -- optional second part of string to be concatted
    41   ...
    42 )
    44 Sends a header line to the browser. Multiple arguments are concatted to form a single string.
    46 --]]--
    47 function add_header(...)
    48   if data_sent then
    49     error("Can not add header after data has been sent.", 2)
    50   end
    51   io.stdout:write(...)
    52   io.stdout:write("\r\n")
    53 end
    54 --//--
    56 --[[--
    57 rocketcgi.send_data(
    58   string_part1,       -- string
    59   string_part2,       -- optional second part of string to be concatted
    60   ...
    61 )
    63 Sends document data to the browser. Multiple arguments are concatted to form a single string.
    65 --]]--
    66 function send_data(...)
    67   if not data_sent then
    68     io.stdout:write("\r\n")
    69     data_sent = true
    70   end
    71   io.stdout:write(...)
    72 end
    73 --//--
    75 --[[--
    76 rocketcgi.set_status(
    77   status               -- Status code and description, e.g. "404 Not Found"
    78 )
    80 Sends a header line to the browser, indicating a given HTTP status.
    82 --]]--
    83 function set_status(status)
    84   add_header("Status: ", status)
    85 end
    86 --//--
    88 --[[--
    89 rocketcgi.redirect(
    90   status             -- Absolute URL to redirect the browser to
    91 )
    93 Redirects the browser to the given absolute URL, using a 303 Redirect.
    95 --]]--
    96 function redirect(location)
    97   set_status("303 See Other")
    98   add_header("Location: ", location)
    99 end
   100 --//--
   102 --[[--
   103 rocketcgi.set_content_type(
   104   content_type               -- MIME content type
   105 )
   107 Sends a header line specifying the content-type to the browser.
   109 --]]--
   110 function set_content_type(content_type)
   111   add_header("Content-Type: ", content_type)
   112 end
   113 --//--
   115 --[[--
   116 rocketcgi.set_cookie{
   117   name   = name,       -- name of cookie
   118   value  = value,      -- value of cookie
   119   domain = domain,     -- domain where cookie is transmitted
   120   path   = path,       -- path where cookie is transmitted
   121   secure = secure      -- boolean, indicating if cookie should only be transmitted over HTTPS
   122 }
   124 Sends a header line setting a cookie. NOTE: Currently only session cookies are supported.
   126 --]]--
   127 function set_cookie(args)
   128   assert(string.find(args.name, "^[0-9A-Za-z%%._~-]+$"), "Illegal cookie name")
   129   assert(string.find(args.value, "^[0-9A-Za-z%%._~-]+$"), "Illegal cookie value")
   130   local parts = {"Set-Cookie: " .. args.name .. "=" .. args.value}
   131   if args.domain then
   132     assert(
   133       string.find(args.path, "^[0-9A-Za-z%%/._~-]+$"),
   134       "Illegal cookie domain"
   135     )
   136     parts[#parts+1] = "domain=" .. args.domain
   137   end
   138   if args.path then
   139     assert(
   140       string.find(args.path, "^[0-9A-Za-z%%/._~-]+$"),
   141       "Illegal cookie path"
   142     )
   143     parts[#parts+1] = "path=" .. args.path
   144   end
   145   if args.secure then
   146     parts[#parts+1] = "secure"
   147   end
   148   add_header(table.concat(parts, "; "))
   149 end
   150 --//--
   152 method = os.getenv("REQUEST_METHOD") or false
   153 query = os.getenv("QUERY_STRING") or false
   154 cookie_data = os.getenv("HTTP_COOKIE") or false
   155 post_data = io.stdin:read("*a") or false
   156 post_contenttype = os.getenv("CONTENT_TYPE") or false
   157 params = {}
   158 get_params = {}
   159 cookies = {}
   160 post_params = {}
   161 post_filenames = {}
   162 post_types = {}
   164 local urldecode
   165 do
   166   local b0 = string.byte("0")
   167   local b9 = string.byte("9")
   168   local bA = string.byte("A")
   169   local bF = string.byte("F")
   170   local ba = string.byte("a")
   171   local bf = string.byte("f")
   172   function urldecode(str)
   173     return (
   174       string.gsub(
   175         string.gsub(str, "%+", " "),
   176         "%%([0-9A-Fa-f][0-9A-Fa-f])",
   177         function(hex)
   178           local n1, n2 = string.byte(hex, 1, 2)
   179           if n1 >= b0 and n1 <= b9 then n1 = n1 - b0
   180           elseif n1 >= bA and n1 <= bF then n1 = n1 - bA + 10
   181           elseif n1 >= ba and n1 <= bf then n1 = n1 - ba + 10
   182           else return end
   183           if n2 >= b0 and n2 <= b9 then n2 = n2 - b0
   184           elseif n2 >= bA and n2 <= bF then n2 = n2 - bA + 10
   185           elseif n2 >= ba and n2 <= bf then n2 = n2 - ba + 10
   186           else return end
   187           return string.char(n1 * 16 + n2)
   188         end
   189       )
   190     )
   191   end
   192 end
   194 local function proc_param(tbl, key, value)
   195   if string.find(key, "%[%]$") then
   196     if tbl[key] then
   197       table.insert(tbl[key], value)
   198     else
   199       local list = { value }
   200       params[key] = list
   201       tbl[key] = list
   202     end
   203   else
   204     params[key] = value
   205     tbl[key] = value
   206   end
   207 end
   209 local function read_urlencoded_form(tbl, data)
   210   for rawkey, rawvalue in string.gmatch(data, "([^=&]*)=([^=&]*)") do
   211     proc_param(tbl, urldecode(rawkey), urldecode(rawvalue))
   212   end
   213 end
   215 if query then
   216   read_urlencoded_form(get_params, query)
   217 end
   219 if cookie_data then
   220   for rawkey, rawvalue in string.gmatch(cookie_data, "([^=; ]*)=([^=; ]*)") do
   221     cookies[urldecode(rawkey)] = urldecode(rawvalue)
   222   end
   223 end
   225 if post_contenttype and (
   226   post_contenttype == "application/x-www-form-urlencoded" or
   227   string.match(post_contenttype, "^application/x%-www%-form%-urlencoded[ ;]")
   228 ) then
   229   read_urlencoded_form(post_params, post_data)
   230 elseif post_contenttype then
   231   local boundary = string.match(
   232     post_contenttype,
   233     '^multipart/form%-data[ \t]*;[ \t]*boundary="([^"]+)"'
   234   ) or string.match(
   235       post_contenttype,
   236       '^multipart/form%-data[ \t]*;[ \t]*boundary=([^"; \t]+)'
   237   )
   238   if boundary then
   239     local parts = {}
   240     do
   241       local boundary = "\r\n--" .. boundary
   242       local post_data = "\r\n" .. post_data
   243       local pos1, pos2 = string.find(post_data, boundary, 1, true)
   244       while true do
   245         local ind = string.sub(post_data, pos2 + 1, pos2 + 2)
   246         if ind == "\r\n" then
   247           local pos3, pos4 = string.find(post_data, boundary, pos2 + 1, true)
   248           if pos3 then
   249             parts[#parts + 1] = string.sub(post_data, pos2 + 3, pos3 - 1)
   250           else
   251             error("Illegal POST data.")
   252           end
   253           pos1, pos2 = pos3, pos4
   254         elseif ind == "--" then
   255           break
   256         else
   257           error("Illegal POST data.")
   258         end
   259       end
   260     end
   261     for i, part in ipairs(parts) do
   262       local pos = 1
   263       local name, filename, contenttype
   264       while true do
   265         local header
   266         do
   267           local oldpos = pos
   268           pos = string.find(part, "\r\n", oldpos, true)
   269           if not pos then
   270             error("Illegal POST data.")
   271           end
   272           if pos == oldpos then break end
   273           header = string.sub(part, oldpos, pos - 1)
   274           pos = pos + 2
   275         end
   276         if string.find(
   277           string.lower(header),
   278           "^content%-disposition:[ \t]*form%-data[ \t]*;"
   279         ) then
   280           -- TODO: handle all cases correctly
   281           name = string.match(header, ';[ \t]*name="([^"]*)"') or
   282             string.match(header, ';[ \t]*name=([^"; \t]+)')
   283           filename = string.match(header, ';[ \t]*filename="([^"]*)"') or
   284             string.match(header, ';[ \t]*filename=([^"; \t]+)')
   285         else
   286           local dummy, subpos = string.find(
   287             string.lower(header),
   288             "^content%-type:[ \t]*"
   289           )
   290           if subpos then
   291             contenttype = string.sub(header, subpos + 1, #header)
   292           end
   293         end
   294       end
   295       local content = string.sub(part, pos + 2, #part)
   296       if not name then
   297         error("Illegal POST data.")
   298       end
   299       proc_param(post_params, name, content)
   300       post_filenames[name] = filename
   301       post_types[name] = contenttype
   302     end
   303   end
   304 end
   306 if post_data and #post_data > 262144 then
   307   post_data = nil
   308   collectgarbage("collect")
   309 else
   310   --post_data = nil  --allow access to "post_data" for usual requests
   311 end
   313 return _M
