# Copyright (C) 2003-2006 Kouichirou Eto, All rights reserved.
# This is free software with ABSOLUTELY NO WARRANTY.
# You can redistribute it and/or modify it under the terms of the GNU GPL 2.

$LOAD_PATH.unshift '..' unless $LOAD_PATH.include? '..'

module Qwik
  class UnknownPathException < Exception; end

  class Request
    attr_reader :sitename
    attr_accessor :base
    attr_reader :ext
    attr_accessor :plugin
    attr_reader :ext_args
    attr_accessor :path_args
    attr_reader :path_query
    attr_reader :unparsed_uri

    def parse_path(path)
      if @unparsed_uri.nil?
	@unparsed_uri = path	# for test
      end

      @sitename, @base, @ext, @plugin, @path_args, @path_query, @ext_args =
	Request.parse_path(path, @config.default_sitename)

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

    def self.parse_path(path, default_sitename)
      raise "first character must be '/'" unless path[0] == ?/

      path = remove_quote(path)

      path = normalize_path(path)	# for security

      # OBSOLETE: Patch for redirect plugin (old format).
      if /\A\/((?:http|https|ftp|file):\/.+)\z/ =~ path
	return [default_sitename, '', '', 'redirect', [$1], {}, []]
      end

      pas = path.split('/')
      pas.shift		# Drop the first null element.

      # Ad hoc: Error handling.
      if pas.length == 1
	if ! pas.first.include?('.') && /\/\z/ !~ path
	  return [default_sitename, pas.first, '', nil, [], {}, []]
	end
      end

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

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

	# Maybe sitename.
	if i == 0
	  if Request.sitename?(pa)
	    sitename = pa
	  end
	  next if sitename	# Skip.
	end

	ff = pa.split('.')

	# No end with dot.
	if ff[1].nil? || ff[1].empty?
	  if ff[0] == 'theme' || ff[0] == 'attach'
	    ff = ['', ff[0]]
	  else
	    base = ''
	    ext = ''
	    return [sitename, base, ext, plugin, path_args, path_query, ext_args]
	  end
	end

	# Start with dot -> Action plugin.
	if ff[0].empty?
	  raise 'no two action' unless plugin.nil?	# No two plugins.
	  plugin = ff[1]
	  next
	end

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

	raise 'base should be one.' if base	# base should be one.
	base = ff[0]
	ext = ff[1]
      }

      # sitename is not specified.
      sitename = 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
      }

      base.set_url_charset

      return [sitename, base, ext, plugin, path_args, path_query, ext_args]
    end

    private

    def init_path
      @sitename = nil
      @base = nil
      @ext = nil
      @plugin = nil
      @ext_args = []
      @path_args = []
      @path_query = {}
      @unparsed_uri = nil
    end

    # For Excite Translate Bug.
    def self.remove_quote(path)
      if /\A(.+)\"(.+)\"\z/ =~ path
	return $1+$2
      end
      return path
    end

    # copied from webrick/httputils.rb
    def self.normalize_path(path)
      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
      return ret
    end

    def self.sitename?(pa)
      ff = pa.split('.')
      return false if ff.first.empty? # 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
  end
end

if $0 == __FILE__
  require 'qwik/testunit'
  require 'qwik/config'
  require 'qwik/request'
  $test = true
end

if defined?($test) && $test
  require 'qwik/test-module-public'

  class TestRequestPath < Test::Unit::TestCase
    include TestModulePublic

    def test_class_method
      c = Qwik::Request
      ok_eq('a',   c.remove_quote('a'))
      ok_eq("\"a\"", c.remove_quote("\"a\""))
      ok_eq('ab',  c.remove_quote("a\"b\""))
      ok_eq("'a'", c.remove_quote("'a'"))
    end

    def test_all
      config = Qwik::Config.new
      req = Qwik::Request.new(config)

      # test_parse_path
#      t_make_public(Qwik::Request, :parse_path)
      ok_eq(['www', 'FrontPage', 'html'], req.parse_path('/'))
      ok_eq(['www', 'FrontPage', 'html'],
	    req.parse_path('/FrontPage.html'))
      ok_eq(['test', 't', 'html'], req.parse_path('/test/t.html'))
      ok_eq(['test', 'FrontPage', 'html'], req.parse_path('/test/'))
      ok_eq(['www', 'test', ''], req.parse_path('/test'))
      ok_eq(['test', 'FrontPage', 'html'],
	    req.parse_path('/test/FrontPage.html'))
      ok_eq(['example.com', 'FrontPage', 'html'],
	    req.parse_path('/example.com/'))
      ok_eq(['www.example.com', 'FrontPage', 'html'],
	    req.parse_path('/www.example.com/'))

      # test_theme_plugin
      req.parse_path('/.theme/all.css')
      ok_eq(['theme', ['all.css']], [req.plugin, req.path_args])
      ok_eq(['www', 'FrontPage', 'html'],
	    req.parse_path('/.theme/all.css'))

      req.parse_path('/.theme/qwikgreen/qwikgreen.css')
      ok_eq(['theme', ['qwikgreen', 'qwikgreen.css']],
	    [req.plugin, req.path_args])

      req.parse_path('/.login')
      ok_eq(['login', []], [req.plugin, req.path_args])

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

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

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

      ok_eq(['www', 'FrontPage', 'html'], req.parse_path('/.new'))
      ok_eq(['new', []], [req.plugin, req.path_args])

      ok_eq(['test', 'test', 'zip'], req.parse_path('/test/test.zip'))
      ok_eq('zip', req.ext)

      ok_eq(['www', 'test', 'zip'], req.parse_path('/test.zip'))
      ok_eq('zip', req.ext)

      ok_eq(['e.com', 'FrontPage', 'html'],
	    req.parse_path('/e.com/.attach/t.png'))
      ok_eq(['attach', ['t.png']],
	    [req.plugin, req.path_args])

      ok_eq(['www', 'www', 'zip'], req.parse_path('/www.zip'))
      ok_eq(['www', 'www', 'rss'], req.parse_path('/www.rss'))

      ok_eq(['www', 'favicon', 'ico'], req.parse_path('/favicon.ico'))

      # test_attach
      ok_eq(['www', 'FrontPage', 'html'],
	    req.parse_path('/.attach/s.jpg'))
      ok_eq(['attach', ['s.jpg']], [req.plugin, req.path_args])

      ok_eq(['www', 'FrontPage', 'html'],
	    req.parse_path('/.attach/thumb/s.jpg'))
      ok_eq(['attach', ['thumb', 's.jpg']],
	    [req.plugin, req.path_args])

      # test_parse_sitename
      c = Qwik::Request
      ok_eq(true,  c.sitename?('test'))
      ok_eq(true,  c.sitename?('e.com'))
      ok_eq(true,  c.sitename?('www.e.com'))
      ok_eq(false, c.sitename?('www.new'))
      ok_eq(false, c.sitename?('www.zip'))
      ok_eq(false, c.sitename?('www.rss'))
      ok_eq(false, c.sitename?('hoge.1.backup'))

      # test_ext_args
      ok_eq(['www', 'hoge', 'backup'],
	    req.parse_path('/hoge.backup'))
      ok_eq([], req.ext_args)

      ok_eq(['www', 'hoge', 'backup'],
	    req.parse_path('/hoge.1.backup'))
      ok_eq(['1'], req.ext_args)

      ok_eq(['test', 'hoge', 'backup'],
	    req.parse_path('/test/hoge.1.backup'))
      ok_eq(['1'], req.ext_args)

    end
  end
end