# Copyright (C) 2003-2005 Kouichirou Eto, All rights reserved.

$LOAD_PATH << "../../lib" unless $LOAD_PATH.include?("../../lib")
require "qwik/config"

module Qwik
  class Request
    DEFAULT_ACCEPT_LANGUAGE = ["en"]

    def initialize(config)
      @config = config
      @start_time = Time.now
      if $debug
	@start_time = Time.at(0)
	#qp "debug", @start_time
      end
      @default_hostname = @config.default_hostname.dup
      clear
    end
    #attr_reader :start_time
    attr_accessor :start_time # for test
    attr_reader :sitename
    attr_reader :ext

    attr_accessor :base

    attr_accessor :path_args
    attr_reader :path_query
    attr_reader :ext_args
    attr_reader :cookies, :query
    attr_reader :domain, :pass
    attr_reader :request_method
    attr_accessor :user
    attr_accessor :auth
    attr_accessor :plugin
    attr_reader :useragent
    attr_accessor :sessionid
    attr_reader :org_host
    attr_reader :unparsed_uri
    attr_accessor :accept_language
    attr_reader :fromhost
    attr_reader :request_line

    def clear
      @sitename = nil
      @base = nil
      @ext = nil
      @plugin = nil
      @path_args = []
      @path_query = {}
      @ext_args = []

      @domain = nil
      @org_host = nil
      @request_method = nil
      @cookies = {}
      @header = {}
      @query = {}
      @user = @pass = nil
      @auth = nil
      @useragent = UserAgent.new(self)
      @sessionid = nil

      @unparsed_uri = nil
      @accept_language = DEFAULT_ACCEPT_LANGUAGE
      @fromhost = nil
      @request_line = nil
    end
    attr_reader :header # for test

    def is_post?
      @request_method == "POST"
    end

    def [](k)
      return nil if @header.nil?
      v = @header[k.downcase]
      (v == nil || v.empty?) ? nil : v.join(", ")
    end

    def parse_webrick(request) # WEBrick::HTTPRequest
      host = request.request_uri ? request.request_uri.host : @default_hostname

      @unparsed_uri = request.unparsed_uri

      parse_host(host)
      parse_path(request.path)

      @header = request.header.dup if request.header

      @useragent = UserAgent.new(self)

      #pp request.accept_language
      if request.accept_language && ! request.accept_language.empty?
	@accept_language = request.accept_language
      elsif @useragent.mobile
	@accept_language = ["ja"]
      end

      request.cookies.each {|c| @cookies[c.name] = c.value }
      @query = request.query.dup
      @request_method = request.request_method

      @user = @pass = nil

      @fromhost = get_fromhost(request)
      @request_line = get_request_line(request)
    end

    def get_fromhost(request)
      forwarded_from = self["x-forwarded-for"]
      if forwarded_from
	forwarded_from.gsub!(/, /, ",")
	return forwarded_from
      end
      peer = request.peeraddr
      return peer[2] if peer
      return "127.0.0.1"
    end

    def get_request_line(request)
      return "" if request.request_line.nil?
      request_line = request.request_line.sub(/\x0d?\x0a\z/o, '')
      request_line = request_line.sub(/ HTTP\/1\..\z/o, '')
      return request_line
    end

    def parse_host(h)
      # IP address or host name?
      @org_host = h
      h = @default_hostname if h =~ /^([.0-9]+)$/ || !h.include?(".")
      @domain = h.downcase
    end

    def parse_path(u)
      raise "first character must be '/'" unless u[0] == ?/

      @sitename = @base = @ext = @plugin = nil
      @path_args = []
      @path_query = {}
      @ext_args = []

      if @unparsed_uri.nil?
	@unparsed_uri = u
      end

      u = normalize_path(u)

      if /\A\/((?:http|https|ftp|file):\/.+)\z/ =~ u
	@sitename = "www" # default
	@base = ""
	@ext = ""
	@plugin = "redirect"
	@path_args = [$1]
	return [@sitename, @base, @ext]
      end

      pas = u.split("/")
      pas.shift # drop the first null element

      if pas.length == 1 # adhoc error handling
	if !pas.first.include?(".") && /\/\z/ !~ u
	  @sitename = "www"
	  @base = pas.first
	  @ext = ""
	  return [@sitename, @base, @ext] # only for test
	end
      end

      pas.each_with_index {|pa, i|
	if @plugin || @ext
	  @path_args << pa # catch all
	  next
	end

	if i == 0 # may be sitename
	  get_sitename(pa)
	  next if @sitename
	end

	ff = pa.split(".")

	if ff[1].nil? || ff[1].empty? # no end with dot
	  if ff[0] == "theme" || ff[0] == "attach"
	    ff = ["", ff[0]]
	  else
	    raise "no end with dot : "+[u, ff].inspect
	  end
	end

	if ff[0].empty? # start with dot means action plugin
	  raise unless @plugin.nil? # no two plugins
	  @plugin = ff[1]
	  next
	end

	if 2 < ff.length # accept only two
	  @base = ff.shift
	  @ext = ff.pop
	  @ext_args = ff
	  next
	end

	raise unless @base.nil?
	@base = ff[0]
	@ext = ff[1]
      }
      @sitename = @config.default_sitename if @sitename.nil?

      if @base.nil?
	@base, @ext = ["FrontPage", "html"]
      end

      @path_args.each {|pa|
	ff = pa.split("=")
	if ff.length == 2
	  @path_query[ff.first] = ff.last
	end
      }

      [@sitename, @base, @ext] # only for test
    end

    def get_sitename(pa)
      @sitename = pa if sitename?(pa)
    end

    def sitename?(pa)
      ff = pa.split(".")
      return false if ff.first == "" # action
      len = ff.length
      return true if len == 1
      # contain dot, and last is com or jp is external site
      return true if 1 < len && %w(com jp).include?(ff.last)
      return false
    end

    def normalize_path(path) # copied from webrick/httputils.rb
      raise "abnormal path `#{path}'" if path[0] != ?/
      ret = path.dup

      ret.gsub!(%r{/+}o, '/')                    # //      => /
      while ret.sub!(%r:/\.(/|\z):o, '/'); end   # /.      => /
      begin                                      # /foo/.. => /foo
        match = ret.sub!(%r{/([^/]+)/\.\.(/|\z)}o){
          if $1 == ".."
            raise "abnormal path `#{path}'"
          else
            "/"
          end
        }
      end while match

      raise "abnormal path `#{path}'" if %r{/\.\.(/|\z)} =~ ret
      ret
    end
  end

  class UserAgent
    def initialize(req)
      @req = req
      @mobile = nil
      @serial = nil
      parse(@req)
    end
    attr_reader :mobile, :serial

    def parse(req)
      ua = req["user-agent"]
      case ua
      when /\ADoCoMo/ # docomo
	@mobile = "docomo"
	if /\/ser(...........)/ =~ ua
	  @serial = $1
	end

      when /\AKDDI\-/ # ezweb
	@mobile = "ezweb"
	@serial = req["x-up-subno"]
      when /\AUP\.Browser/ # ezweb
	@mobile = "ezweb"
	@serial = req["x-up-subno"]
      end

    end
  end
end

$debug = true if $0 == __FILE__
if $debug
  require "test/unit"

  class TC_Request < Test::Unit::TestCase
    def setup
      @config = Qwik::Config.new(true)
      @req = Qwik::Request.new(@config)
    end

    def test_all
      # test_parse_host
      assert_equal("example.com", @req.parse_host("example.com"))
      assert_equal("example.com", @req.parse_host("test"))
      assert_equal("example.com", @req.parse_host("127.0.0.1"))

      # test_parse_path
      assert_equal(["www", "FrontPage", "html"], @req.parse_path("/"))
      assert_equal(["www", "FrontPage", "html"],
		   @req.parse_path("/FrontPage.html"))
      assert_equal(["test", "t", "html"], @req.parse_path("/test/t.html"))
      assert_equal(["test", "FrontPage", "html"], @req.parse_path("/test/"))
      assert_equal(["www", "test", ""], @req.parse_path("/test"))
      assert_equal(["test", "FrontPage", "html"],
		   @req.parse_path("/test/FrontPage.html"))
      assert_equal(["example.com", "FrontPage", "html"],
		   @req.parse_path("/example.com/"))
      assert_equal(["www.example.com", "FrontPage", "html"],
		   @req.parse_path("/www.example.com/"))

      # test_theme_plugin
      @req.parse_path("/.theme/all.css")
      assert_equal(["theme", ["all.css"]], [@req.plugin, @req.path_args])
      assert_equal(["www", "FrontPage", "html"],
		   @req.parse_path("/.theme/all.css"))

      @req.parse_path("/.theme/qwikgreen/qwikgreen.css")
      assert_equal(["theme", ["qwikgreen", "qwikgreen.css"]],
		   [@req.plugin, @req.path_args])

      @req.parse_path("/.login")
      assert_equal(["login", []], [@req.plugin, @req.path_args])

      @req.parse_path("/.login/user@e.com/44484125/")
      assert_equal(["login", ["user@e.com", "44484125"]],
		   [@req.plugin, @req.path_args])

      assert_equal(["www", "FrontPage", "html"],
		   @req.parse_path("/FrontPage.html/sid=000/"))
      assert_equal(["sid=000"], @req.path_args)
      assert_equal({"sid"=>"000"}, @req.path_query)

      # test_parse_plugin
      assert_raise(RuntimeError){ @req.parse_path("test/") }
      assert_equal(["test", "FrontPage", "html"],
		   @req.parse_path("/test/.attach/t.txt"))
      assert_equal(["attach", ["t.txt"]], [@req.plugin, @req.path_args])
      assert_equal(["www", "FrontPage", "html"],
		   @req.parse_path("/.attach/s.jpg"))
      assert_equal(["attach", ["s.jpg"]], [@req.plugin, @req.path_args])

      assert_equal(["www", "FrontPage", "html"], @req.parse_path("/.new"))
      assert_equal(["new", []], [@req.plugin, @req.path_args])

      assert_equal(["test", "test", "zip"], @req.parse_path("/test/test.zip"))
      assert_equal("zip", @req.ext)

      assert_equal(["www", "test", "zip"], @req.parse_path("/test.zip"))
      assert_equal("zip", @req.ext)

      assert_equal(["e.com", "FrontPage", "html"],
		   @req.parse_path("/e.com/.attach/qrcode-e.com.png"))
      assert_equal(["attach", ["qrcode-e.com.png"]],
		   [@req.plugin, @req.path_args])

      assert_equal(["www", "www", "zip"], @req.parse_path("/www.zip"))
      assert_equal(["www", "www", "rss"], @req.parse_path("/www.rss"))

      assert_equal(["www", "favicon", "ico"], @req.parse_path("/favicon.ico"))

      # test_attach
      assert_equal(["www", "FrontPage", "html"],
		   @req.parse_path("/.attach/s.jpg"))
      assert_equal(["attach", ["s.jpg"]], [@req.plugin, @req.path_args])

      assert_equal(["www", "FrontPage", "html"],
		   @req.parse_path("/.attach/thumb/s.jpg"))
      assert_equal(["attach", ["thumb", "s.jpg"]],
		   [@req.plugin, @req.path_args])

      # test_parse_sitename
      assert_equal(true, @req.sitename?("test"))
      assert_equal(true, @req.sitename?("e.com"))
      assert_equal(true, @req.sitename?("www.e.com"))
      assert_equal(false, @req.sitename?("www.new"))
      assert_equal(false, @req.sitename?("www.zip"))
      assert_equal(false, @req.sitename?("www.rss"))
      assert_equal(false, @req.sitename?("hoge.1.backup"))

      # test_ext_args
      assert_equal(["www", "hoge", "backup"],
		   @req.parse_path("/hoge.backup"))
      assert_equal([], @req.ext_args)

      assert_equal(["www", "hoge", "backup"],
		   @req.parse_path("/hoge.1.backup"))
      assert_equal(["1"], @req.ext_args)

      assert_equal(["test", "hoge", "backup"],
		   @req.parse_path("/test/hoge.1.backup"))
      assert_equal(["1"], @req.ext_args)

      # test_user_agent
      @req.instance_eval {
	@header["user-agent"] = ["DoCoMo/1.0/N504i/c10/TB"]
      }
      ua = Qwik::UserAgent.new(@req)
      assert_equal("docomo", ua.mobile)
      assert_equal(nil, ua.serial)
      @req.instance_eval {
	@header["user-agent"] = ["DoCoMo/1.0/N504i/c10/TB/serNMAIA000001"]
      }
      ua = Qwik::UserAgent.new(@req)
      assert_equal("docomo", ua.mobile)
      assert_equal("NMAIA000001", ua.serial)

      # test_start_time
      assert_equal(0, @req.start_time.to_i)
    end
  end
end
