#
# httpservlet.rb -- HTTPServlet Module
#
# Author: IPR -- Internet Programming with Ruby -- writers
# Copyright (C) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
#
# $IPR: filehandler.rb,v 1.13 2002/02/13 20:02:53 gotoyuzo Exp $

require 'thread'

require 'webrick/htmlutils'
require 'webrick/httputils'
require 'webrick/httpstatus'
require 'webrick/httpdate'

module WEBrick
  module HTTPServlet

    class FileHandler < AbstractServlet
      HandlerTable = Hash.new

      def self.add_handler(suffix, handler)
        HandlerTable[suffix] = handler
      end

      def self.remove_handler(suffix)
        HandlerTable.delete(suffix)
      end

      def initialize(config, root, show_dir=false)
        super
        @root       = root
        @show_dir   = show_dir
      end

      def do_GET(req, res)
        raise HTTPStatus::NotFound, "`#{req.path}' not found" unless @root
        handler_info = search_handler(req, res)
        if handler_info
          exec_handler(req, res, handler_info)
        else
          set_local_file(req, res)
        end
      end

      def do_POST(req, res)
        raise HTTPStatus::NotFound, "`#{req.path}' not found" unless @root
        handler_info = search_handler(req, res)
        if handler_info
          exec_handler(req, res, handler_info)
        else
          raise HTTPStatus::NotFound, "`#{req.path}' not found."
        end
      end

      def search_handler(req, res)
        handler = nil
        filename = @root.dup
        script_name = ""
        path_info = req.path_info.scan(%r|/[^/]*|)

        while name = path_info.shift
          script_name << name
          filename << name
          begin
            st = File::stat(filename)
          rescue
            raise HTTPStatus::NotFound, "`#{req.path}' not found."
          end
          raise HTTPStatus::Forbidden,
            "no access permission to `#{req.path}'." unless st.readable?
          next if st.directory?
          suffix = /\.(\w+)$/ =~ name && $1
          handler = HandlerTable[suffix]
          break
        end

        handler ?  [ handler, filename, script_name, path_info.join ] : nil
      end

      def exec_handler(req, res, handler_info)
        handler, filename, script_name, path_info = handler_info
        req.script_name << script_name
        req.path_info = path_info
        hi = handler.get_instance(@config, filename)
        hi.service(req, res)
      end

      def set_local_file(req, res)
        local_path = @root + req.path_info
        st = File::stat(local_path)

        if st.directory?
          unless local_path[-1] == ?/
            res.set_redirect(HTTPStatus::MovedPermanently, req.path + "/")
          end
          @config[:DirectoryIndex].each{|index|
            idx_path = local_path + index
            if FileTest::readable?(idx_path)
              res.set_redirect(HTTPStatus::Found, req.path + index)
            end
          }
          raise HTTPStatus::Forbidden,
            "no access permission to `#{req.path}'" unless @show_dir
          set_dir_list(req, res)
        else
          mtime = st.mtime
          mtype = HTTPUtils::mime_type(local_path, @config[:MimeTypes])
          res['content-type'] = mtype
          res['last-modified'] = HTTPDate::time2s(mtime)

          ifmod = req['if-modified-since']
          if ifmod && HTTPDate::s2time(ifmod) >= mtime
            res.body = ''
            raise HTTPStatus::NotModified
          else
            res.body = open(local_path){|io| io.read }
          end
        end
      end

      def set_dir_list(req, res)
        local_path = @root + req.path_info
        dir = Dir::open(local_path)
        list = dir.collect{|name|
          if name[0] == "."[0]
            nil
          else
            st = File::stat(local_path + name)
            if st.directory?
              [ name + "/", st.mtime, -1 ]
            else
              [ name, st.mtime, st.size ]
            end
          end
        }
        dir.close
        list.compact!

        if    d0 = req.query["N"]; idx = 0
        elsif d0 = req.query["M"]; idx = 1
        elsif d0 = req.query["S"]; idx = 2
        else  d0 = "A"           ; idx = 0
        end
        d1 = (d0 == "A") ? "D" : "A"

        if d0 == "A"
          list.sort!{|a,b| a[idx] <=> b[idx] }
        else
          list.sort!{|a,b| b[idx] <=> a[idx] }
        end

        res['content-type'] = "text/html"

        res.body = <<-_end_of_html_
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
  <HEAD><TITLE>Index of #{HTMLUtils::escape(req.path)}</TITLE></HEAD>
  <BODY>
    <H1>Index of #{HTMLUtils::escape(req.path)}</H1>
        _end_of_html_

        res.body << "<PRE>\n"
        res.body << " <A HREF=\"?N=#{d1}\">Name</A>                          "
        res.body << "<A HREF=\"?M=#{d1}\">Last modified</A>         "
        res.body << "<A HREF=\"?S=#{d1}\">Size</A>\n"
        res.body << "<HR>\n"
       
        list.unshift [ "..", File::mtime(local_path+".."), -1 ]
        list.each{ |name, time, size|
          if name == ".."
            dname = "Parent Directory"
          elsif name.size > 25
            dname = name.sub(/^(.{23})(.*)/){ $1 + ".." }
          else
            dname = name
          end
          s =  " <A HREF=\"#{HTTPUtils::escape(name)}\">#{dname}</A>"
          s << " " * (30 - dname.size)
          s << time.strftime("%Y/%m/%d %H:%M      ")
          s << (size > 0 ? size.to_s : "-") << "\n"
          res.body << s
        }
        res.body << "</PRE><HR>"

        res.body << <<-_end_of_html_    
    <ADDRESS>
     #{HTMLUtils::escape(@config[:ServerSoftware])}<BR>
     at #{@config[:ServerName]}:#{@config[:Port]}
    </ADDRESS>
  </BODY>
</HTML>
        _end_of_html_
      end

    end

  end
end
