-- Common functions for all classes. function _check_required(arg, argtype) if (type(arg) == 'nil') then error('required argument left out', 3) else _check_optional(arg, argtype) end end function _check_optional(arg, argtype) if (type(arg) ~= 'nil') then if (type(argtype) == 'string') then if (type(arg) ~= argtype) then error(argtype .. ' argument expected, got ' .. type(arg), 3) end elseif (type(argtype) == 'table') then local b = false for _, t in ipairs(argtype) do if (type(arg) == t) then b = true end end if (b == false) then error(argtype .. ' argument expected, got ' .. type(arg), 3) end end end end function _extract_mailboxes(messages) local t = {} for _, v in ipairs(messages) do b, _ = unpack(v) t[b] = true end return t end function _extract_messages(mailbox, messages) local t = {} for _, v in ipairs(messages) do b, m = unpack(v) if mailbox == b then table.insert(t, m) end end return t end function _make_range(messages) for _, m in ipairs(messages) do if type(m) ~= 'number' then return messages end end table.sort(messages) local t = {} local a, z for _, m in ipairs(messages) do if a == nil or z == nil then a = m z = m else if m == z + 1 then z = m else if a == z then table.insert(t, tostring(a)) else table.insert(t, a .. ':' .. z) end a = m z = m end end end if a == z then table.insert(t, tostring(a)) else table.insert(t, a .. ':' .. z) end return t end function _make_query(criteria) local s = 'ALL ' if (criteria.invert ~= true) then for ka, va in ipairs(criteria) do if (type(va) == 'string') then s = s .. '' .. '(' .. va .. ')' .. ' ' elseif (type(va) == 'table') then for i = 1, #va - 1 do s = s .. 'OR ' end for ko, vo in ipairs(va) do if (type(vo) ~= 'string') then error('filter rule not a string', 2) end s = s .. '(' .. vo .. ') ' end else error('filter element not a string or table', 2) end end else for i = 1, #criteria - 1 do s = s .. 'OR ' end for ko, vo in ipairs(criteria) do if (type(vo) == 'string') then s = s .. '' .. '(' .. vo .. ')' .. ' ' elseif (type(vo) == 'table') then s = s .. '(' for ka, va in ipairs(vo) do if (type(va) ~= 'string') then error('filter rule not a string', 2) end s = s .. va .. ' ' end s = string.gsub(s, '(.+) ', '%1') s = s .. ') ' else error('filter rule not a string or table', 2) end end end s = string.gsub(s, '(.+) ', '%1') return s end function _parse_structure(b) local bs = _parse_body(b) if not bs then error(b.i .. ':' .. b.s) end return _parse_normalize(bs) end function _parse_normalize(bs, key, val) if not key or not val then if #bs == 0 then return { ['1'] = bs } else for k, v in pairs(bs) do if type(k) ~= 'number' then bs[k] = nil end end for k, v in ipairs(bs) do _parse_normalize(bs, k, v) bs[tostring(k)] = v bs[k] = nil end end return bs else for k, v in ipairs(val) do local new = tostring(key) .. '.' .. tostring(k) bs[new] = v _parse_normalize(bs, new, v) val[k] = nil end end end function _parse_body(b) if not _parse_lpar(b) then return end local bp if _parse_lpar(b, true) then bp = _parse_mpart(b) else bp = _parse_1part(b) end if not _parse_rpar(b) then return end return bp end function _parse_1part(b) local i = b.i local t = _parse_string(b) _parse_space(b) local s = _parse_string(b) if t and t:lower() == 'message' and s and s:lower() == 'rfc822' then return _parse_message(b) else b.i = i return _parse_basic(b) end end function _parse_basic(b) local bp = {} local s s = _parse_string(b) if not s then return end bp['type'] = s _parse_space(b) s = _parse_string(b) if not s then return end bp['type'] = bp['type'] .. '/' .. s _parse_space(b) s = _parse_param(b, 'name') if s then bp['name'] = s end _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_string(b) _parse_space(b) bp['size'] = _parse_number(b) if bp['type']:sub(1, 5):lower() == 'text/' then _parse_space(b) _parse_number(b) end if _parse_space(b) then _parse_nstring(b) end if _parse_space(b) then s = _parse_dsp(b) if s then bp['name'] = s end end if _parse_space(b) then _parse_lang(b) end if _parse_space(b) then _parse_nstring(b) end while _parse_space(b) and b.i <= #b.s do _parse_extension(b) end return bp end function _parse_mpart(b) local bp = {} local i = 1 local s bp['type'] = 'multipart' while _parse_lpar(b, true) and b.i <= #b.s do bp[i] = _parse_body(b) i = i + 1 end _parse_space(b) s = _parse_string(b) if not s then return end bp['type'] = bp['type'] .. '/' .. s if _parse_space(b) then s = _parse_param(b, 'name') if s then bp['name'] = s end end if _parse_space(b) then s = _parse_dsp(b) if s then bp['name'] = s end end if _parse_space(b) then _parse_lang(b) end if _parse_space(b) then _parse_nstring(b) end while _parse_space(b) and b.i < #b.s do _parse_extension(b) end return bp end function _parse_message(b) local bp = {} local s bp['type'] = 'message/rfc822' _parse_space(b) s = _parse_param(b, 'name') if s then bp['name'] = s end _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_string(b) _parse_space(b) bp['size'] = _parse_number(b) _parse_space(b) _parse_envelope(b) _parse_space(b) local p = _parse_body(b) if not p then return end if #p == 0 then bp[1] = p else for k, v in pairs(p) do if type(k) == 'number' then bp[k] = v end end end _parse_space(b) _parse_number(b) if _parse_space(b) then _parse_nstring(b) end if _parse_space(b) then s = _parse_dsp(b) if s then bp['name'] = s end end if _parse_space(b) then _parse_lang(b) end if _parse_space(b) then _parse_nstring(b) end while _parse_space(b) and b.i <= #b.s do _parse_extension(b) end return bp end function _parse_envelope(b) _parse_lpar(b) _parse_nstring(b) _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_address(b) _parse_space(b) _parse_address(b) _parse_space(b) _parse_address(b) _parse_space(b) _parse_address(b) _parse_space(b) _parse_address(b) _parse_space(b) _parse_address(b) _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_nstring(b) _parse_rpar(b) end function _parse_address(b) if _parse_lpar(b) then while _parse_lpar(b) and b.i <= #b.s do _parse_nstring(b) _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_nstring(b) _parse_rpar(b) end _parse_rpar(b) elseif _parse_nil(b) then end end function _parse_lang(b) if _parse_lpar(b) then local lang = {} repeat table.insert(lang, _parse_string(b)) until not _parse_space(b) or b.i > #b.s _parse_rpar(b) return lang else return _parse_nstring(b) end end function _parse_dsp(b) local r if _parse_lpar(b) then _parse_string(b) _parse_space(b) r = _parse_param(b, 'filename') _parse_rpar(b) elseif _parse_nil(b) then end return r end function _parse_param(b, key) local r if _parse_lpar(b) then repeat local s = _parse_string(b) _parse_space(b) if s and s:lower() == key then r = _parse_string(b) else _parse_string(b) end until not _parse_space(b) or b.i > #b.s _parse_rpar(b) elseif _parse_nil(b) then end return r end function _parse_extension(b) if _parse_nstring(b) then elseif _parse_number(b) then elseif _parse_lpar(b) then _parse_extension(b) while _parse_space(b) and b.i <= #b.s do _parse_extension(b) end _parse_rpar(b) else end end function _parse_space(b, peek) if b.s:sub(b.i, b.i) == ' ' then if not peek then b.i = b.i + 1 end return true else return false end end function _parse_lpar(b, peek) if b.s:sub(b.i, b.i) == '(' then if not peek then b.i = b.i + 1 end return true else return false end end function _parse_rpar(b, peek) if b.s:sub(b.i, b.i) == ')' then if not peek then b.i = b.i + 1 end return true else return false end end function _parse_nil(b) if b.s:sub(b.i, b.i + 2):upper() == 'NIL' then b.i = b.i + 3 return true else return false end end function _parse_string(b) local i = b.i if b.s:sub(i, i) == '"' then i = i + 1 else return end local j = i local n = 0 while true do n = b.s:find('"', i + n) if not n then return end if b.s:sub(n - 1, n - 1) ~= '\\' then i = n + 1 b.i = i return b.s:sub(j, n - 1) else return end end end function _parse_nstring(b) local i = b.i if b.s:sub(i, i) == '"' then i = i + 1 elseif _parse_nil(b) then return 'NIL' else return end local j = i local n = 0 while true do n = b.s:find('"', i + n) if not n then return end if b.s:sub(n - 1, n - 1) ~= '\\' then i = n + 1 b.i = i return b.s:sub(j, n - 1) else return end end end function _parse_number(b) local j = b.i local n = b.s:find('[^0-9]', b.i) if not n then return end b.i = n return tonumber(b.s:sub(j, n - 1)) end