| rev | 
   line source | 
| 
jbe/bsw@0
 | 
     1 #!/usr/bin/env lua
 | 
| 
jbe/bsw@0
 | 
     2 
 | 
| 
jbe/bsw@0
 | 
     3 local assert       = assert
 | 
| 
jbe/bsw@0
 | 
     4 local error        = error
 | 
| 
jbe/bsw@0
 | 
     5 local getfenv      = getfenv
 | 
| 
jbe/bsw@0
 | 
     6 local getmetatable = getmetatable
 | 
| 
jbe/bsw@0
 | 
     7 local ipairs       = ipairs
 | 
| 
jbe/bsw@0
 | 
     8 local next         = next
 | 
| 
jbe/bsw@0
 | 
     9 local pairs        = pairs
 | 
| 
jbe/bsw@0
 | 
    10 local pcall        = pcall
 | 
| 
jbe/bsw@0
 | 
    11 local print        = print
 | 
| 
jbe/bsw@0
 | 
    12 local rawequal     = rawequal
 | 
| 
jbe/bsw@0
 | 
    13 local rawget       = rawget
 | 
| 
jbe/bsw@0
 | 
    14 local rawset       = rawset
 | 
| 
jbe/bsw@0
 | 
    15 local select       = select
 | 
| 
jbe/bsw@0
 | 
    16 local setfenv      = setfenv
 | 
| 
jbe/bsw@0
 | 
    17 local setmetatable = setmetatable
 | 
| 
jbe/bsw@0
 | 
    18 local tonumber     = tonumber
 | 
| 
jbe/bsw@0
 | 
    19 local tostring     = tostring
 | 
| 
jbe/bsw@0
 | 
    20 local type         = type
 | 
| 
jbe/bsw@0
 | 
    21 local unpack       = unpack
 | 
| 
jbe/bsw@0
 | 
    22 local xpcall       = xpcall
 | 
| 
jbe/bsw@0
 | 
    23 
 | 
| 
jbe/bsw@0
 | 
    24 local io     = io
 | 
| 
jbe/bsw@0
 | 
    25 local math   = math
 | 
| 
jbe/bsw@0
 | 
    26 local os     = os
 | 
| 
jbe/bsw@0
 | 
    27 local string = string
 | 
| 
jbe/bsw@0
 | 
    28 local table  = table
 | 
| 
jbe/bsw@0
 | 
    29 
 | 
| 
jbe/bsw@0
 | 
    30 module(...)
 | 
| 
jbe/bsw@0
 | 
    31 
 | 
| 
jbe/bsw@0
 | 
    32 data_sent = false
 | 
| 
jbe/bsw@0
 | 
    33 
 | 
| 
jbe/bsw@0
 | 
    34 function add_header(...)
 | 
| 
jbe/bsw@0
 | 
    35   if data_sent then
 | 
| 
jbe/bsw@0
 | 
    36     error("Can not add header after data has been sent.", 2)
 | 
| 
jbe/bsw@0
 | 
    37   end
 | 
| 
jbe/bsw@0
 | 
    38   io.stdout:write(...)
 | 
| 
jbe/bsw@0
 | 
    39   io.stdout:write("\r\n")
 | 
| 
jbe/bsw@0
 | 
    40 end
 | 
| 
jbe/bsw@0
 | 
    41 
 | 
| 
jbe/bsw@0
 | 
    42 function send_data(...)
 | 
| 
jbe/bsw@0
 | 
    43   if not data_sent then
 | 
| 
jbe/bsw@0
 | 
    44     io.stdout:write("\r\n")
 | 
| 
jbe/bsw@0
 | 
    45     data_sent = true
 | 
| 
jbe/bsw@0
 | 
    46   end
 | 
| 
jbe/bsw@0
 | 
    47   io.stdout:write(...)
 | 
| 
jbe/bsw@0
 | 
    48 end
 | 
| 
jbe/bsw@0
 | 
    49 
 | 
| 
jbe/bsw@0
 | 
    50 function set_status(status)
 | 
| 
jbe/bsw@0
 | 
    51   add_header("Status: ", status)
 | 
| 
jbe/bsw@0
 | 
    52 end
 | 
| 
jbe/bsw@0
 | 
    53 
 | 
| 
jbe/bsw@0
 | 
    54 function redirect(location)
 | 
| 
jbe/bsw@0
 | 
    55   set_status("303 See Other")
 | 
| 
jbe/bsw@0
 | 
    56   add_header("Location: ", location)
 | 
| 
jbe/bsw@0
 | 
    57 end
 | 
| 
jbe/bsw@0
 | 
    58 
 | 
| 
jbe/bsw@0
 | 
    59 function set_content_type(content_type)
 | 
| 
jbe/bsw@0
 | 
    60   add_header("Content-Type: ", content_type)
 | 
| 
jbe/bsw@0
 | 
    61 end
 | 
| 
jbe/bsw@0
 | 
    62 
 | 
| 
jbe/bsw@0
 | 
    63 method = os.getenv("REQUEST_METHOD") or false
 | 
| 
jbe/bsw@0
 | 
    64 query = os.getenv("QUERY_STRING") or false
 | 
| 
jbe/bsw@0
 | 
    65 cookie_data = os.getenv("HTTP_COOKIE") or false
 | 
| 
jbe/bsw@0
 | 
    66 post_data = io.stdin:read("*a") or false
 | 
| 
jbe/bsw@0
 | 
    67 post_contenttype = os.getenv("CONTENT_TYPE") or false
 | 
| 
jbe/bsw@0
 | 
    68 params = {}
 | 
| 
jbe/bsw@0
 | 
    69 get_params = {}
 | 
| 
jbe/bsw@0
 | 
    70 cookies = {}
 | 
| 
jbe/bsw@0
 | 
    71 post_params = {}
 | 
| 
jbe/bsw@0
 | 
    72 post_filenames = {}
 | 
| 
jbe/bsw@0
 | 
    73 post_types = {}
 | 
| 
jbe/bsw@0
 | 
    74 
 | 
| 
jbe/bsw@0
 | 
    75 local urldecode
 | 
| 
jbe/bsw@0
 | 
    76 do
 | 
| 
jbe/bsw@0
 | 
    77   local b0 = string.byte("0")
 | 
| 
jbe/bsw@0
 | 
    78   local b9 = string.byte("9")
 | 
| 
jbe/bsw@0
 | 
    79   local bA = string.byte("A")
 | 
| 
jbe/bsw@0
 | 
    80   local bF = string.byte("F")
 | 
| 
jbe/bsw@0
 | 
    81   local ba = string.byte("a")
 | 
| 
jbe/bsw@0
 | 
    82   local bf = string.byte("f")
 | 
| 
jbe/bsw@0
 | 
    83   function urldecode(str)
 | 
| 
jbe/bsw@0
 | 
    84     return (
 | 
| 
jbe/bsw@0
 | 
    85       string.gsub(
 | 
| 
jbe/bsw@0
 | 
    86         string.gsub(str, "%+", " "),
 | 
| 
jbe/bsw@0
 | 
    87         "%%([0-9A-Fa-f][0-9A-Fa-f])",
 | 
| 
jbe/bsw@0
 | 
    88         function(hex)
 | 
| 
jbe/bsw@0
 | 
    89           local n1, n2 = string.byte(hex, 1, 2)
 | 
| 
jbe/bsw@0
 | 
    90           if n1 >= b0 and n1 <= b9 then n1 = n1 - b0
 | 
| 
jbe/bsw@0
 | 
    91           elseif n1 >= bA and n1 <= bF then n1 = n1 - bA + 10
 | 
| 
jbe/bsw@0
 | 
    92           elseif n1 >= ba and n1 <= bf then n1 = n1 - ba + 10
 | 
| 
jbe/bsw@0
 | 
    93           else return end
 | 
| 
jbe/bsw@0
 | 
    94           if n2 >= b0 and n2 <= b9 then n2 = n2 - b0
 | 
| 
jbe/bsw@0
 | 
    95           elseif n2 >= bA and n2 <= bF then n2 = n2 - bA + 10
 | 
| 
jbe/bsw@0
 | 
    96           elseif n2 >= ba and n2 <= bf then n2 = n2 - ba + 10
 | 
| 
jbe/bsw@0
 | 
    97           else return end
 | 
| 
jbe/bsw@0
 | 
    98           return string.char(n1 * 16 + n2)
 | 
| 
jbe/bsw@0
 | 
    99         end
 | 
| 
jbe/bsw@0
 | 
   100       )
 | 
| 
jbe/bsw@0
 | 
   101     )
 | 
| 
jbe/bsw@0
 | 
   102   end
 | 
| 
jbe/bsw@0
 | 
   103 end
 | 
| 
jbe/bsw@0
 | 
   104 
 | 
| 
jbe/bsw@0
 | 
   105 local function proc_param(tbl, key, value)
 | 
| 
jbe/bsw@0
 | 
   106   if string.find(key, "%[%]$") then
 | 
| 
jbe/bsw@0
 | 
   107     if tbl[key] then
 | 
| 
jbe/bsw@0
 | 
   108       table.insert(tbl[key], value)
 | 
| 
jbe/bsw@0
 | 
   109     else
 | 
| 
jbe/bsw@0
 | 
   110       local list = { value }
 | 
| 
jbe/bsw@0
 | 
   111       params[key] = list
 | 
| 
jbe/bsw@0
 | 
   112       tbl[key] = list
 | 
| 
jbe/bsw@0
 | 
   113     end
 | 
| 
jbe/bsw@0
 | 
   114   else
 | 
| 
jbe/bsw@0
 | 
   115     params[key] = value
 | 
| 
jbe/bsw@0
 | 
   116     tbl[key] = value
 | 
| 
jbe/bsw@0
 | 
   117   end
 | 
| 
jbe/bsw@0
 | 
   118 end
 | 
| 
jbe/bsw@0
 | 
   119 
 | 
| 
jbe/bsw@0
 | 
   120 local function read_urlencoded_form(tbl, data)
 | 
| 
jbe/bsw@0
 | 
   121   for rawkey, rawvalue in string.gmatch(data, "([^=&]*)=([^=&]*)") do
 | 
| 
jbe/bsw@0
 | 
   122     proc_param(tbl, urldecode(rawkey), urldecode(rawvalue))
 | 
| 
jbe/bsw@0
 | 
   123   end
 | 
| 
jbe/bsw@0
 | 
   124 end
 | 
| 
jbe/bsw@0
 | 
   125 
 | 
| 
jbe/bsw@0
 | 
   126 if query then
 | 
| 
jbe/bsw@0
 | 
   127   read_urlencoded_form(get_params, query)
 | 
| 
jbe/bsw@0
 | 
   128 end
 | 
| 
jbe/bsw@0
 | 
   129 
 | 
| 
jbe/bsw@0
 | 
   130 if cookie_data then
 | 
| 
jbe/bsw@0
 | 
   131   for rawkey, rawvalue in string.gmatch(cookie_data, "([^=; ]*)=([^=; ]*)") do
 | 
| 
jbe/bsw@0
 | 
   132     cookies[urldecode(rawkey)] = urldecode(rawvalue)
 | 
| 
jbe/bsw@0
 | 
   133   end
 | 
| 
jbe/bsw@0
 | 
   134 end
 | 
| 
jbe/bsw@0
 | 
   135 
 | 
| 
jbe/bsw@0
 | 
   136 if post_contenttype == "application/x-www-form-urlencoded" then
 | 
| 
jbe/bsw@0
 | 
   137   read_urlencoded_form(post_params, post_data)
 | 
| 
jbe/bsw@0
 | 
   138 elseif post_contenttype then
 | 
| 
jbe/bsw@0
 | 
   139   local boundary = string.match(
 | 
| 
jbe/bsw@0
 | 
   140     post_contenttype,
 | 
| 
jbe/bsw@0
 | 
   141     '^multipart/form%-data[ \t]*;[ \t]*boundary="([^"]+)"'
 | 
| 
jbe/bsw@0
 | 
   142   ) or string.match(
 | 
| 
jbe/bsw@0
 | 
   143       post_contenttype,
 | 
| 
jbe/bsw@0
 | 
   144       '^multipart/form%-data[ \t]*;[ \t]*boundary=([^"; \t]+)'
 | 
| 
jbe/bsw@0
 | 
   145   )
 | 
| 
jbe/bsw@0
 | 
   146   if boundary then
 | 
| 
jbe/bsw@0
 | 
   147     local parts = {}
 | 
| 
jbe/bsw@0
 | 
   148     do
 | 
| 
jbe/bsw@0
 | 
   149       local boundary = "\r\n--" .. boundary
 | 
| 
jbe/bsw@0
 | 
   150       local post_data = "\r\n" .. post_data
 | 
| 
jbe/bsw@0
 | 
   151       local pos1, pos2 = string.find(post_data, boundary, 1, true)
 | 
| 
jbe/bsw@0
 | 
   152       while true do
 | 
| 
jbe/bsw@0
 | 
   153         local ind = string.sub(post_data, pos2 + 1, pos2 + 2)
 | 
| 
jbe/bsw@0
 | 
   154         if ind == "\r\n" then
 | 
| 
jbe/bsw@0
 | 
   155           local pos3, pos4 = string.find(post_data, boundary, pos2 + 1, true)
 | 
| 
jbe/bsw@0
 | 
   156           if pos3 then
 | 
| 
jbe/bsw@0
 | 
   157             parts[#parts + 1] = string.sub(post_data, pos2 + 3, pos3 - 1)
 | 
| 
jbe/bsw@0
 | 
   158           else
 | 
| 
jbe/bsw@0
 | 
   159             error("Illegal POST data.")
 | 
| 
jbe/bsw@0
 | 
   160           end
 | 
| 
jbe/bsw@0
 | 
   161           pos1, pos2 = pos3, pos4
 | 
| 
jbe/bsw@0
 | 
   162         elseif ind == "--" then
 | 
| 
jbe/bsw@0
 | 
   163           break
 | 
| 
jbe/bsw@0
 | 
   164         else
 | 
| 
jbe/bsw@0
 | 
   165           error("Illegal POST data.")
 | 
| 
jbe/bsw@0
 | 
   166         end
 | 
| 
jbe/bsw@0
 | 
   167       end
 | 
| 
jbe/bsw@0
 | 
   168     end
 | 
| 
jbe/bsw@0
 | 
   169     for i, part in ipairs(parts) do
 | 
| 
jbe/bsw@0
 | 
   170       local pos = 1
 | 
| 
jbe/bsw@0
 | 
   171       local name, filename, contenttype
 | 
| 
jbe/bsw@0
 | 
   172       while true do
 | 
| 
jbe/bsw@0
 | 
   173         local header
 | 
| 
jbe/bsw@0
 | 
   174         do
 | 
| 
jbe/bsw@0
 | 
   175           local oldpos = pos
 | 
| 
jbe/bsw@0
 | 
   176           pos = string.find(part, "\r\n", oldpos, true)
 | 
| 
jbe/bsw@0
 | 
   177           if not pos then
 | 
| 
jbe/bsw@0
 | 
   178             error("Illegal POST data.")
 | 
| 
jbe/bsw@0
 | 
   179           end
 | 
| 
jbe/bsw@0
 | 
   180           if pos == oldpos then break end
 | 
| 
jbe/bsw@0
 | 
   181           header = string.sub(part, oldpos, pos - 1)
 | 
| 
jbe/bsw@0
 | 
   182           pos = pos + 2
 | 
| 
jbe/bsw@0
 | 
   183         end
 | 
| 
jbe/bsw@0
 | 
   184         if string.find(
 | 
| 
jbe/bsw@0
 | 
   185           string.lower(header),
 | 
| 
jbe/bsw@0
 | 
   186           "^content%-disposition:[ \t]*form%-data[ \t]*;"
 | 
| 
jbe/bsw@0
 | 
   187         ) then
 | 
| 
jbe/bsw@0
 | 
   188           -- TODO: handle all cases correctly
 | 
| 
jbe/bsw@0
 | 
   189           name = string.match(header, ';[ \t]*name="([^"]*)"') or
 | 
| 
jbe/bsw@0
 | 
   190             string.match(header, ';[ \t]*name=([^"; \t]+)')
 | 
| 
jbe/bsw@0
 | 
   191           filename = string.match(header, ';[ \t]*filename="([^"]*)"') or
 | 
| 
jbe/bsw@0
 | 
   192             string.match(header, ';[ \t]*filename=([^"; \t]+)')
 | 
| 
jbe/bsw@0
 | 
   193         else
 | 
| 
jbe/bsw@0
 | 
   194           local dummy, subpos = string.find(
 | 
| 
jbe/bsw@0
 | 
   195             string.lower(header),
 | 
| 
jbe/bsw@0
 | 
   196             "^content%-type:[ \t]*"
 | 
| 
jbe/bsw@0
 | 
   197           )
 | 
| 
jbe/bsw@0
 | 
   198           if subpos then
 | 
| 
jbe/bsw@0
 | 
   199             contenttype = string.sub(header, subpos + 1, #header)
 | 
| 
jbe/bsw@0
 | 
   200           end
 | 
| 
jbe/bsw@0
 | 
   201         end
 | 
| 
jbe/bsw@0
 | 
   202       end
 | 
| 
jbe/bsw@0
 | 
   203       local content = string.sub(part, pos + 2, #part)
 | 
| 
jbe/bsw@0
 | 
   204       if not name then
 | 
| 
jbe/bsw@0
 | 
   205         error("Illegal POST data.")
 | 
| 
jbe/bsw@0
 | 
   206       end
 | 
| 
jbe/bsw@0
 | 
   207       proc_param(post_params, name, content)
 | 
| 
jbe/bsw@0
 | 
   208       post_filenames[name] = filename
 | 
| 
jbe/bsw@0
 | 
   209       post_types[name] = contenttype
 | 
| 
jbe/bsw@0
 | 
   210     end
 | 
| 
jbe/bsw@0
 | 
   211   end
 | 
| 
jbe/bsw@0
 | 
   212 end
 | 
| 
jbe/bsw@0
 | 
   213 
 | 
| 
jbe/bsw@0
 | 
   214 if post_data and #post_data > 262144 then
 | 
| 
jbe/bsw@0
 | 
   215   post_data = nil
 | 
| 
jbe/bsw@0
 | 
   216   collectgarbage("collect")
 | 
| 
jbe/bsw@0
 | 
   217 else
 | 
| 
jbe/bsw@0
 | 
   218   post_data = nil
 | 
| 
jbe/bsw@0
 | 
   219 end
 |