You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
5.0 KiB
268 lines
5.0 KiB
-- Copyright (C) Yichun Zhang (agentzh)
|
|
|
|
|
|
local sub = string.sub
|
|
local req_socket = ngx.req.socket
|
|
local null = ngx.null
|
|
local match = string.match
|
|
local setmetatable = setmetatable
|
|
local error = error
|
|
local get_headers = ngx.req.get_headers
|
|
local type = type
|
|
local print = print
|
|
|
|
|
|
local _M = { _VERSION = '0.08' }
|
|
|
|
|
|
local MAX_LINE_SIZE = 512
|
|
|
|
local STATE_BEGIN = 1
|
|
local STATE_READING_HEADER = 2
|
|
local STATE_READING_BODY = 3
|
|
local STATE_EOF = 4
|
|
|
|
|
|
local mt = { __index = _M }
|
|
|
|
local state_handlers
|
|
|
|
|
|
local function get_boundary()
|
|
local header = get_headers()["content-type"]
|
|
if not header then
|
|
return nil
|
|
end
|
|
|
|
if type(header) == "table" then
|
|
header = header[1]
|
|
end
|
|
|
|
local m = match(header, ";%s*boundary=\"([^\"]+)\"")
|
|
if m then
|
|
return m
|
|
end
|
|
|
|
return match(header, ";%s*boundary=([^\",;]+)")
|
|
end
|
|
|
|
|
|
function _M.new(self, chunk_size)
|
|
local boundary = get_boundary()
|
|
|
|
print("boundary: ", boundary)
|
|
|
|
if not boundary then
|
|
return nil, "no boundary defined in Content-Type"
|
|
end
|
|
|
|
print('boundary: "', boundary, '"')
|
|
|
|
local sock, err = req_socket()
|
|
if not sock then
|
|
return nil, err
|
|
end
|
|
|
|
local read2boundary, err = sock:receiveuntil("--" .. boundary)
|
|
if not read2boundary then
|
|
return nil, err
|
|
end
|
|
|
|
local read_line, err = sock:receiveuntil("\r\n")
|
|
if not read_line then
|
|
return nil, err
|
|
end
|
|
|
|
return setmetatable({
|
|
sock = sock,
|
|
size = chunk_size or 4096,
|
|
read2boundary = read2boundary,
|
|
read_line = read_line,
|
|
boundary = boundary,
|
|
state = STATE_BEGIN
|
|
}, mt)
|
|
end
|
|
|
|
|
|
function _M.set_timeout(self, timeout)
|
|
local sock = self.sock
|
|
if not sock then
|
|
return nil, "not initialized"
|
|
end
|
|
|
|
return sock:settimeout(timeout)
|
|
end
|
|
|
|
|
|
local function discard_line(self)
|
|
local read_line = self.read_line
|
|
|
|
local line, err = self.read_line(MAX_LINE_SIZE)
|
|
if not line then
|
|
return nil, err
|
|
end
|
|
|
|
local dummy, err = self.read_line(1)
|
|
if dummy then
|
|
return nil, "line too long: " .. line .. dummy .. "..."
|
|
end
|
|
|
|
if err then
|
|
return nil, err
|
|
end
|
|
|
|
return 1
|
|
end
|
|
|
|
|
|
local function discard_rest(self)
|
|
local sock = self.sock
|
|
local size = self.size
|
|
|
|
while true do
|
|
local dummy, err = sock:receive(size)
|
|
if err and err ~= 'closed' then
|
|
return nil, err
|
|
end
|
|
|
|
if not dummy then
|
|
return 1
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function read_body_part(self)
|
|
local read2boundary = self.read2boundary
|
|
|
|
local chunk, err = read2boundary(self.size)
|
|
if err then
|
|
return nil, nil, err
|
|
end
|
|
|
|
if not chunk then
|
|
local sock = self.sock
|
|
|
|
local data = sock:receive(2)
|
|
if data == "--" then
|
|
local ok, err = discard_rest(self)
|
|
if not ok then
|
|
return nil, nil, err
|
|
end
|
|
|
|
self.state = STATE_EOF
|
|
return "part_end"
|
|
end
|
|
|
|
if data ~= "\r\n" then
|
|
local ok, err = discard_line(self)
|
|
if not ok then
|
|
return nil, nil, err
|
|
end
|
|
end
|
|
|
|
self.state = STATE_READING_HEADER
|
|
return "part_end"
|
|
end
|
|
|
|
return "body", chunk
|
|
end
|
|
|
|
|
|
local function read_header(self)
|
|
local read_line = self.read_line
|
|
|
|
local line, err = read_line(MAX_LINE_SIZE)
|
|
if err then
|
|
return nil, nil, err
|
|
end
|
|
|
|
local dummy, err = read_line(1)
|
|
if dummy then
|
|
return nil, nil, "line too long: " .. line .. dummy .. "..."
|
|
end
|
|
|
|
if err then
|
|
return nil, nil, err
|
|
end
|
|
|
|
-- print("read line: ", line)
|
|
|
|
if line == "" then
|
|
-- after the last header
|
|
self.state = STATE_READING_BODY
|
|
return read_body_part(self)
|
|
end
|
|
|
|
local key, value = match(line, "([^: \t]+)%s*:%s*(.+)")
|
|
if not key then
|
|
return 'header', line
|
|
end
|
|
|
|
return 'header', {key, value, line}
|
|
end
|
|
|
|
|
|
local function eof()
|
|
return "eof", nil
|
|
end
|
|
|
|
|
|
function _M.read(self)
|
|
local size = self.size
|
|
|
|
local handler = state_handlers[self.state]
|
|
if handler then
|
|
return handler(self)
|
|
end
|
|
|
|
return nil, nil, "bad state: " .. self.state
|
|
end
|
|
|
|
|
|
local function read_preamble(self)
|
|
local sock = self.sock
|
|
if not sock then
|
|
return nil, nil, "not initialized"
|
|
end
|
|
|
|
local size = self.size
|
|
local read2boundary = self.read2boundary
|
|
|
|
while true do
|
|
local preamble, err = read2boundary(size)
|
|
if not preamble then
|
|
break
|
|
end
|
|
|
|
-- discard the preamble data chunk
|
|
-- print("read preamble: ", preamble)
|
|
end
|
|
|
|
local ok, err = discard_line(self)
|
|
if not ok then
|
|
return nil, nil, err
|
|
end
|
|
|
|
local read2boundary, err = sock:receiveuntil("\r\n--" .. self.boundary)
|
|
if not read2boundary then
|
|
return nil, nil, err
|
|
end
|
|
|
|
self.read2boundary = read2boundary
|
|
|
|
self.state = STATE_READING_HEADER
|
|
return read_header(self)
|
|
end
|
|
|
|
|
|
state_handlers = {
|
|
read_preamble,
|
|
read_header,
|
|
read_body_part,
|
|
eof
|
|
}
|
|
|
|
|
|
return _M
|