aboutsummaryrefslogtreecommitdiff
path: root/httpd.lua
blob: de578c5b64305505f1d1bf8de22bb7a5e86496ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
local httpd = { }

local socket
local process

local function send_and_close(sock, data)
    sock:send(data, function(sock) sock:close() end)
end

local function default(method, path, headers)
    if method == 'GET' and path == '/' then
        return 200, 'OK', { }, 'OK, good!\n'
    else
        return 404, 'Not Found', { }, 'Not found!\n'
    end
end

local function response(sock, code, status, headers, content)
    local resp = { string.format('HTTP/1.1 %d %s', code, status) }
    headers['Connection'] = 'close'
    for k, v in pairs(headers) do
        table.insert(resp, string.format('%s: %s', k, v))
    end
    table.insert(resp, '')
    table.insert(resp, content)
    resp = table.concat(resp, '\r\n')
    send_and_close(sock, resp)
    return true
end

local function listenFunc(sock)
    local buf = ''
    local method
    local path
    local headers = { }
    local header_done
    local content_length = 0
    local function recv(sock, c)
        local function bad_request()
            send_and_close(sock, 'HTTP/1.1 400 Bad request\r\n')
        end
        buf = buf .. c
        while true do
            if not header_done then
                local s = buf:find('\r\n', 1, true)
                if s then
                    local line = buf:sub(1, s - 1)
                    buf = buf:sub(s + 2)
                    if not method then
                        method, path = line:match('^(%u+) (%S+) HTTP/1%.1$')
                        if not method then
                            bad_request()
                            break
                        end
                    elseif not header_done then
                        if line == '' then
                            header_done = true
                        else
                            local h, v = line:match('^([-a-zA-Z]+): (.*)$')
                            if h then
                                h = h:lower()
                                headers[h] = v
                                if h == 'content-length' then
                                    content_length = tonumber(v)
                                end
                            else
                                bad_request()
                                break
                            end
                        end
                    end
                else
                    break
                end
            elseif content_length == #buf then
                local code, status, headers, content
                    = process(method, path, headers, buf)
                if not code then
                    code, status, headers, content
                        = default(method, path, headers, buf)
                end
                response(sock, code, status, headers, content)
                break
            else
                break
            end
        end
    end
    sock:on('receive', recv)
end

function httpd.init(processFunc)
    socket = net.createServer()
    socket:listen(80, listenFunc)
    process = processFunc
end

function httpd.dump(method, path, headers, content)
    local c = { method, path }
    for k, v in pairs(headers) do
        table.insert(c, string.format('%s: %s', k, v))
    end
    table.insert(c, content)
    return 200, 'OK', { }, table.concat(c, '\n')
end

function httpd.upload(method, path, headers, content)
    if method == 'POST' and path:sub(1, 8) == '/upload/' then
        local fname = path:sub(9)
        file.open(fname, "w")
        file.write(content)
        file.close()
        return 200, 'OK', { }, 'OK, file uploaded.\n'
    end
end

function httpd.parse_form(form)
    local r = { }
    local function a(p)
        local k, v = p:match('(.-)=(.*)')
        if k and v then
            v = v:gsub('%%3A', ':')
            r[k] = v
        end
    end
    local s = form:find('&')
    while s do
        a(form:sub(1, s - 1))
        form = form:sub(s + 1)
        s = form:find('&')
    end
    a(form)
    return r
end

return httpd