<commit_before>local cjson = require 'cjson'
local env = require 'resty.env'
local custom_config = env.get('APICAST_CUSTOM_CONFIG')
local configuration_parser = require 'configuration_parser'
local configuration_store = require 'configuration_store'

local oauth = require 'oauth'
local resty_url = require 'resty.url'

local assert = assert
local type = type
local pairs = pairs
local ipairs = ipairs
local next = next
local insert = table.insert

local concat = table.concat
local tostring = tostring
local format = string.format
local gsub = string.gsub
local tonumber = tonumber
local setmetatable = setmetatable
local exit = ngx.exit
local resty_resolver = require 'resty.resolver'
local dns_resolver = require 'resty.resolver.dns'
local empty = {}

local response_codes = env.enabled('APICAST_RESPONSE_CODES')
local request_logs = env.enabled('APICAST_REQUEST_LOGS')

local _M = {
  -- FIXME: this is really bad idea, this file is shared across all requests,
  -- so that means sharing something in this module would be sharing it acros all requests
  -- and in multi-tenant environment that would mean leaking information
  configuration = configuration_store.new()
}

local mt = {
  __index = _M
}

function _M.new(configuration)
  return setmetatable({
    configuration = configuration
  }, mt)
end

function _M:configure(contents)
  if self and not contents then contents = self end

  local config, err = configuration_parser.parse(contents)

  if err then
    ngx.log(ngx.WARN, 'not configured: ', err)
    ngx.log(ngx.DEBUG, 'config: ', contents)

    return nil, err
  end

  -- FIXME: using global configuration interface
  local configuration = self.configuration or _M.configuration

  if config then
    return configuration:store(config)
  end
end

function _M:configured(host)
  if not self then return nil, 'not initialized' end
  local configuration = self.configuration
  if not configuration or not configuration.find then return nil, 'not initialized' end

  local hosts = configuration:find(host)

  return next(hosts) and true
end

_M.init = function(config) return _M.configure(_M, config) end

-- Error Codes
local function error_no_credentials(service)
  ngx.log(ngx.INFO, 'no credentials provided for service ', service.id)
  ngx.var.cached_key = nil
  ngx.status = service.auth_missing_status
  ngx.header.content_type = service.auth_missing_headers
  ngx.print(service.error_auth_missing)
  ngx.exit(ngx.HTTP_OK)
end

local function error_authorization_failed(service)
  ngx.log(ngx.INFO, 'authorization failed for service ', service.id)
  ngx.var.cached_key = nil
  ngx.status = service.auth_failed_status
  ngx.header.content_type = service.auth_failed_headers
  ngx.print(service.error_auth_failed)
  ngx.exit(ngx.HTTP_OK)
end

local function error_no_match(service)
  ngx.log(ngx.INFO, 'no rules matched for service ', service.id)
  ngx.var.cached_key = nil
  ngx.status = service.no_match_status
  ngx.header.content_type = service.no_match_headers
  ngx.print(service.error_no_match)
  ngx.exit(ngx.HTTP_OK)
end

local function error_service_not_found(host)
  ngx.status = 404
  ngx.print('')
  ngx.log(ngx.WARN, 'could not find service for host: ', host)
  ngx.exit(ngx.status)
end
-- End Error Codes

local function build_querystring_formatter(fmt)
  return function (query)
    local function kvmap(f, t)
      local res = {}
      for k, v in pairs(t) do
        insert(res, f(k, v))
      end
      return res
    end

    return concat(kvmap(function(k,v) return format(fmt, k, tostring(v)) end, query or {}), "&")
  end
end

local build_querystring = build_querystring_formatter("usage[%s]=%s")
local build_query = build_querystring_formatter("%s=%s")

local function get_debug_value(service)
  return ngx.var.http_x_3scale_debug == service.backend_authentication.value
end

local function find_service_strict(self, host)
  for _,service in pairs(self.configuration:find(host)) do
    if service.id == host then
      return service
    end

    for _,_host in ipairs(service.hosts or {}) do
      if _host == host then
        return service
      end
    end
  end
  ngx.log(ngx.ERR, 'service not found for host ', host)
end

local function find_service_cascade(self, host)
  local request = ngx.var.request
  for _,service in pairs(self.configuration:find(host)) do
    for _,_host in ipairs(service.hosts or {}) do
      if _host == host then
        local name = service.system_name or service.id
        ngx.log(ngx.DEBUG, 'service ', name, ' matched host ', _host)
        local usage, matched_patterns = service:extract_usage(request)

        if next(usage) and matched_patterns ~= '' then
          ngx.log(ngx.DEBUG, 'service ', name, ' matched patterns ', matched_patterns)
          return service
        end
      end
    end
  end

  return find_service_strict(host)
end

if configuration_store.path_routing then
  ngx.log(ngx.WARN, 'apicast experimental path routing enabled')
  _M.find_service = find_service_cascade
else
  _M.find_service = find_service_strict
end

local http = {
  get = function(url)
    ngx.log(ngx.INFO, '[http] requesting ', url)
    local backend_upstream = ngx.ctx.backend_upstream
    local previous_real_url = ngx.var.real_url
    ngx.log(ngx.DEBUG, '[ctx] copying backend_upstream of size: ', #backend_upstream)
    local res = ngx.location.capture(assert(url), { share_all_vars = true, ctx = { backend_upstream = backend_upstream } })

    local real_url = ngx.var.real_url

    if real_url ~= previous_real_url then
      ngx.log(ngx.INFO, '[http] ', real_url, ' (',res.status, ')')
    else
      ngx.log(ngx.INFO, '[http] status: ', res.status)
    end<commit_msg>Fix missing 'self' parameter<commit_after>local cjson = require 'cjson'
local env = require 'resty.env'
local custom_config = env.get('APICAST_CUSTOM_CONFIG')
local configuration_parser = require 'configuration_parser'
local configuration_store = require 'configuration_store'

local oauth = require 'oauth'
local resty_url = require 'resty.url'

local assert = assert
local type = type
local pairs = pairs
local ipairs = ipairs
local next = next
local insert = table.insert

local concat = table.concat
local tostring = tostring
local format = string.format
local gsub = string.gsub
local tonumber = tonumber
local setmetatable = setmetatable
local exit = ngx.exit
local resty_resolver = require 'resty.resolver'
local dns_resolver = require 'resty.resolver.dns'
local empty = {}

local response_codes = env.enabled('APICAST_RESPONSE_CODES')
local request_logs = env.enabled('APICAST_REQUEST_LOGS')

local _M = {
  -- FIXME: this is really bad idea, this file is shared across all requests,
  -- so that means sharing something in this module would be sharing it acros all requests
  -- and in multi-tenant environment that would mean leaking information
  configuration = configuration_store.new()
}

local mt = {
  __index = _M
}

function _M.new(configuration)
  return setmetatable({
    configuration = configuration
  }, mt)
end

function _M:configure(contents)
  if self and not contents then contents = self end

  local config, err = configuration_parser.parse(contents)

  if err then
    ngx.log(ngx.WARN, 'not configured: ', err)
    ngx.log(ngx.DEBUG, 'config: ', contents)

    return nil, err
  end

  -- FIXME: using global configuration interface
  local configuration = self.configuration or _M.configuration

  if config then
    return configuration:store(config)
  end
end

function _M:configured(host)
  if not self then return nil, 'not initialized' end
  local configuration = self.configuration
  if not configuration or not configuration.find then return nil, 'not initialized' end

  local hosts = configuration:find(host)

  return next(hosts) and true
end

_M.init = function(config) return _M.configure(_M, config) end

-- Error Codes
local function error_no_credentials(service)
  ngx.log(ngx.INFO, 'no credentials provided for service ', service.id)
  ngx.var.cached_key = nil
  ngx.status = service.auth_missing_status
  ngx.header.content_type = service.auth_missing_headers
  ngx.print(service.error_auth_missing)
  ngx.exit(ngx.HTTP_OK)
end

local function error_authorization_failed(service)
  ngx.log(ngx.INFO, 'authorization failed for service ', service.id)
  ngx.var.cached_key = nil
  ngx.status = service.auth_failed_status
  ngx.header.content_type = service.auth_failed_headers
  ngx.print(service.error_auth_failed)
  ngx.exit(ngx.HTTP_OK)
end

local function error_no_match(service)
  ngx.log(ngx.INFO, 'no rules matched for service ', service.id)
  ngx.var.cached_key = nil
  ngx.status = service.no_match_status
  ngx.header.content_type = service.no_match_headers
  ngx.print(service.error_no_match)
  ngx.exit(ngx.HTTP_OK)
end

local function error_service_not_found(host)
  ngx.status = 404
  ngx.print('')
  ngx.log(ngx.WARN, 'could not find service for host: ', host)
  ngx.exit(ngx.status)
end
-- End Error Codes

local function build_querystring_formatter(fmt)
  return function (query)
    local function kvmap(f, t)
      local res = {}
      for k, v in pairs(t) do
        insert(res, f(k, v))
      end
      return res
    end

    return concat(kvmap(function(k,v) return format(fmt, k, tostring(v)) end, query or {}), "&")
  end
end

local build_querystring = build_querystring_formatter("usage[%s]=%s")
local build_query = build_querystring_formatter("%s=%s")

local function get_debug_value(service)
  return ngx.var.http_x_3scale_debug == service.backend_authentication.value
end

local function find_service_strict(self, host)
  for _,service in pairs(self.configuration:find(host)) do
    if service.id == host then
      return service
    end

    for _,_host in ipairs(service.hosts or {}) do
      if _host == host then
        return service
      end
    end
  end
  ngx.log(ngx.ERR, 'service not found for host ', host)
end

local function find_service_cascade(self, host)
  local request = ngx.var.request
  for _,service in pairs(self.configuration:find(host)) do
    for _,_host in ipairs(service.hosts or {}) do
      if _host == host then
        local name = service.system_name or service.id
        ngx.log(ngx.DEBUG, 'service ', name, ' matched host ', _host)
        local usage, matched_patterns = service:extract_usage(request)

        if next(usage) and matched_patterns ~= '' then
          ngx.log(ngx.DEBUG, 'service ', name, ' matched patterns ', matched_patterns)
          return service
        end
      end
    end
  end

  return find_service_strict(self, host)
end

if configuration_store.path_routing then
  ngx.log(ngx.WARN, 'apicast experimental path routing enabled')
  _M.find_service = find_service_cascade
else
  _M.find_service = find_service_strict
end

local http = {
  get = function(url)
    ngx.log(ngx.INFO, '[http] requesting ', url)
    local backend_upstream = ngx.ctx.backend_upstream
    local previous_real_url = ngx.var.real_url
    ngx.log(ngx.DEBUG, '[ctx] copying backend_upstream of size: ', #backend_upstream)
    local res = ngx.location.capture(assert(url), { share_all_vars = true, ctx = { backend_upstream = backend_upstream } })

    local real_url = ngx.var.real_url

    if real_url ~= previous_real_url then
      ngx.log(ngx.INFO, '[http] ', real_url, ' (',res.status, ')')
    else
      ngx.log(ngx.INFO, '[http] status: ', res.status)
    end
{'fraction_non_alphanumeric': 0.09371418495988716, 'fraction_numerical': 0.0007052807899144847, 'mean_word_length': 4.039537983118614, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4293388', 'n_tokens_mistral': 3662, 'n_tokens_neox': 3619, 'n_words': 1011}