-- I hereby assign copyright in this code to the lua-resty-core project, -- to be licensed under the same terms as the rest of the code. local base = require "resty.core.base" local ffi = require 'ffi' local bit = require "bit" local core_regex = require "resty.core.regex" if core_regex.no_pcre then error("no support for 'ngx.re' module: OpenResty was " .. "compiled without PCRE support", 3) end local C = ffi.C local ffi_str = ffi.string local sub = string.sub local error = error local type = type local band = bit.band local new_tab = base.new_tab local tostring = tostring local math_max = math.max local math_min = math.min local is_regex_cache_empty = core_regex.is_regex_cache_empty local re_match_compile = core_regex.re_match_compile local destroy_compiled_regex = core_regex.destroy_compiled_regex local get_string_buf = base.get_string_buf local get_size_ptr = base.get_size_ptr local FFI_OK = base.FFI_OK local subsystem = ngx.config.subsystem local MAX_ERR_MSG_LEN = 128 local FLAG_DFA = 0x02 local PCRE_ERROR_NOMATCH = -1 local DEFAULT_SPLIT_RES_SIZE = 4 local split_ctx = new_tab(0, 1) local ngx_lua_ffi_set_jit_stack_size local ngx_lua_ffi_exec_regex if subsystem == 'http' then ffi.cdef[[ int ngx_http_lua_ffi_set_jit_stack_size(int size, unsigned char *errstr, size_t *errstr_size); ]] ngx_lua_ffi_exec_regex = C.ngx_http_lua_ffi_exec_regex ngx_lua_ffi_set_jit_stack_size = C.ngx_http_lua_ffi_set_jit_stack_size elseif subsystem == 'stream' then ffi.cdef[[ int ngx_stream_lua_ffi_set_jit_stack_size(int size, unsigned char *errstr, size_t *errstr_size); ]] ngx_lua_ffi_exec_regex = C.ngx_stream_lua_ffi_exec_regex ngx_lua_ffi_set_jit_stack_size = C.ngx_stream_lua_ffi_set_jit_stack_size end local _M = { version = base.version } local function re_split_helper(subj, compiled, compile_once, flags, ctx) local rc do local pos = math_max(ctx.pos, 0) rc = ngx_lua_ffi_exec_regex(compiled, flags, subj, #subj, pos) end if rc == PCRE_ERROR_NOMATCH then return nil, nil, nil end if rc < 0 then if not compile_once then destroy_compiled_regex(compiled) end return nil, nil, nil, "pcre_exec() failed: " .. rc end if rc == 0 then if band(flags, FLAG_DFA) == 0 then if not compile_once then destroy_compiled_regex(compiled) end return nil, nil, nil, "capture size too small" end rc = 1 end local caps = compiled.captures local ncaps = compiled.ncaptures local from = caps[0] local to = caps[1] if from < 0 or to < 0 then return nil, nil, nil end if from == to then -- empty match, skip to next char ctx.pos = to + 1 else ctx.pos = to end -- convert to Lua string indexes from = from + 1 to = to + 1 -- retrieve the first sub-match capture if any if ncaps > 0 and rc > 1 then return from, to, sub(subj, caps[2] + 1, caps[3]) end return from, to end function _M.split(subj, regex, opts, ctx, max, res) -- we need to cast this to strings to avoid exceptions when they are -- something else. -- needed because of further calls to string.sub in this function. subj = tostring(subj) if not ctx then ctx = split_ctx ctx.pos = 1 -- set or reset upvalue field elseif not ctx.pos then -- ctx provided by user but missing pos field ctx.pos = 1 end max = max or 0 if not res then -- limit the initial arr_n size of res to a reasonable value -- 0 < narr <= DEFAULT_SPLIT_RES_SIZE local narr = DEFAULT_SPLIT_RES_SIZE if max > 0 then -- the user specified a valid max limiter if max > 0 narr = math_min(narr, max) end res = new_tab(narr, 0) elseif type(res) ~= "table" then error("res is not a table", 2) end local len = #subj if ctx.pos > len then res[1] = nil return res end -- compile regex local compiled, compile_once, flags = re_match_compile(regex, opts) if compiled == nil then -- compiled_once holds the error string return nil, compile_once end local sub_idx = ctx.pos local res_idx = 0 local last_empty_match -- update to split_helper PCRE indexes ctx.pos = sub_idx - 1 -- splitting: with and without a max limiter if max > 0 then local count = 1 while count < max do local from, to, capture, err = re_split_helper(subj, compiled, compile_once, flags, ctx) if err then return nil, err end if not from then break end if last_empty_match then sub_idx = last_empty_match end if from == to then last_empty_match = from end if from > sub_idx or not last_empty_match then count = count + 1 res_idx = res_idx + 1 res[res_idx] = sub(subj, sub_idx, from - 1) if capture then res_idx = res_idx + 1 res[res_idx] = capture end sub_idx = to if sub_idx > len then break end end end else while true do local from, to, capture, err = re_split_helper(subj, compiled, compile_once, flags, ctx) if err then return nil, err end if not from then break end if last_empty_match then sub_idx = last_empty_match end if from == to then last_empty_match = from end if from > sub_idx or not last_empty_match then res_idx = res_idx + 1 res[res_idx] = sub(subj, sub_idx, from - 1) if capture then res_idx = res_idx + 1 res[res_idx] = capture end sub_idx = to if sub_idx > len then break end end end end if not compile_once then destroy_compiled_regex(compiled) end -- trailing nil for non-cleared res tables -- delete empty trailing ones (without max) if max <= 0 and sub_idx > len then for ety_idx = res_idx, 1, -1 do if res[ety_idx] ~= "" then res_idx = ety_idx break end res[ety_idx] = nil end else res_idx = res_idx + 1 res[res_idx] = sub(subj, sub_idx) end res[res_idx + 1] = nil return res end function _M.opt(option, value) if option == "jit_stack_size" then if not is_regex_cache_empty() then error("changing jit stack size is not allowed when some " .. "regexs have already been compiled and cached", 2) end local errbuf = get_string_buf(MAX_ERR_MSG_LEN) local sizep = get_size_ptr() sizep[0] = MAX_ERR_MSG_LEN local rc = ngx_lua_ffi_set_jit_stack_size(value, errbuf, sizep) if rc == FFI_OK then return end error(ffi_str(errbuf, sizep[0]), 2) end error("unrecognized option name", 2) end return _M