-- 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