# frozen_string_literal: true require 'uri' module Jellyfish class URLMap def initialize mapped_not_chomped mapped = transform_keys(mapped_not_chomped){ |k| k.sub(%r{/+\z}, '') } keys = mapped.keys @no_host = !keys.any?{ |k| match?(k, %r{\Ahttps?://}) } string = sort_keys(keys). map{ |k| build_regexp(k) }. join('|') @mapped = mapped @routes = %r{\A(?:#{string})(?:/|\z)} end def call env path_info = env['PATH_INFO'] if @no_host if matched = @routes.match(path_info) cut_path = matched.to_s.chomp('/') script_name = key = cut_path.squeeze('/') end else host = (env['HTTP_HOST'] || env['SERVER_NAME']).to_s.downcase if matched = @routes.match("#{host}/#{path_info}") cut_path = matched.to_s[host.size + 1..-1].chomp('/') script_name = cut_path.squeeze('/') key = if matched[:host] host_with_path = if script_name.empty? host else File.join(host, script_name) end "http://#{host_with_path}" else script_name end end end if app = @mapped[key] app.call(env.merge( 'SCRIPT_NAME' => env['SCRIPT_NAME'] + script_name, 'PATH_INFO' => path_info[cut_path.size..-1])) else [404, {}, []] end end private def build_regexp path if @no_host regexp_path(path) elsif matched = path.match(%r{\Ahttps?://([^/]+)(/?.*)}) # We only need to know if we're matching against a host, # therefore just an empty group is sufficient. "(?)#{matched[1]}/#{regexp_path(matched[2])}" else "[^/]*/#{regexp_path(path)}" end end def regexp_path path Regexp.escape(path).gsub('/', '/+') end def transform_keys hash, &block if hash.respond_to?(:transform_keys) hash.transform_keys(&block) else hash.inject({}) do |result, (key, value)| result[yield(key)] = value result end end end def match? string, regexp if string.respond_to?(:match?) string.match?(regexp) else string =~ regexp end end def sort_keys keys keys.sort_by do |k| uri = URI.parse(k) [-uri.path.to_s.size, -uri.host.to_s.size] end end end end