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.
ngx_lua_waf/upload.lua

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