#!/usr/local/bin/ruby
# -*- coding: utf-8 -*-
#
#= Atom Feed 1.0を管理するWEBアプリケーション
#
#Autohr::    Kureha Hisame (http://lunardial.sakura.ne.jp/) & Yui Naruse (http://airemix.com/)
#Version::   3.0.0.0
#Copyright:: Copyright 2009 FeedBlog Project (http://sourceforge.jp/projects/feedblog/)
#License::   GPLv3

require "cgi"
require "cgi/session"
require "erb"
require "rexml/document"
require "pstore"
require 'time'
require "date"
require "fileutils"

require "./common.rb"
require "./define.rb"

# = Feed/Entryのスーパークラス
# 
# 入出力用のメソッドなど、共通する機能を提供します
class AbstractEntry
  # 初期化メソッドです
  #
  # _hash_ :: 値を格納したhash配列。superfeed.new(CGI::Session.new(new CGI).params)のようにして使う。
  def initialize(hash)
    # 内部データ保持用のハッシュ配列です
    @attr = {}
    
    # 引数を格納します
    @paramlist.each do |key|
      val = hash[key.to_sym] || hash[key.to_s]
      if val
        val.strip!
        val.gsub!(/\r\n|\r/, "\n")
        @attr[key.to_sym] = CGI.escapeHTML(val)
      else
        # 空の場合の対策
        @attr[key.to_sym] = ""
      end
    end
    
    # 可視・不可視を示すハッシュキーを格納する配列です
    @display = {}
    
    # ハッシュキーの日本語説明を格納する配列です
    @name = []
  end
  
  # Accessor
  attr_reader :attr, :paramlist, :name, :display
  
  # 内部の@attrハッシュにアクセスする手段を提供するメソッドです
  #
  # AbstractEntry.attr[:title]にアクセスしたい場合はAbstractEntry.titleで可能になります。
  # AbstractEntry.send("title")という方法でもアクセス可能です。
  def method_missing(methname, *args)
    methname = methname.to_s
    
    if methname[-1] == ?=
      # setter
      raise ArgumentError, "wrong number of arguments (#{args.length} for 1)" unless args.length == 1
      methname.chop!
      methname = @paramlist.find{|par|par == methname}
      return @attr[methname.to_sym] = args[0] if methname
    else
      # getter
      raise ArgumentError, "wrong number of arguments (#{args.length} for 0)" unless args.empty?
      return @attr[methname.to_sym] if @attr.key?(methname.to_sym)
    end
    # attr上にキーがない値を入れようとした場合はNoMethodError
    raise NoMethodError
  end
  
  def [](key)
    @attr[key.to_sym]
  end
  
  def []=(key, value)
    @attr[key.to_sym] = value
  end
end

# = Feedクラス
#
# Feedの基礎情報を保有するクラスです
class Feed < AbstractEntry
  # 初期化メソッドです
  #
  # _hash_ :: 値を格納したhash配列。feed.new(CGI::Session.new(new CGI).params)のようにして使います。
  def initialize(hash)
    # 内部データ保持用のハッシュ配列です
    @attr = {}
    
    # 内部データの項目名を格納する配列です
    @paramlist = ["feedattr", "title", "subtitle", "self", "url", "updated", "feedid", "rights", "aname", "amail", "others"]
    
    # AbstractEntryのinitializeメソッドを呼び出し、値を@attr配列に格納します
    super(hash)
    
    # 可視・不可視を示すハッシュキーを格納する配列です
    @display = {"feedattr" => "none", "title" => "", "subtitle" => "", 
      "self" => "", "url" => "", 
      "updated" => "none", "feedid" => "", 
      "rights" => "", "aname" => "", 
      "amail" => "", "others" => "none"}
    
    # デバッグモードの場合、全ての入力要素を表示します
    if DEBUG == true
      @display.each do |key, val|
        @display[key] = ""
      end
    end
    
    # ハッシュキーの日本語説明を格納する配列です
    @name = {"feedattr" => "feedの保持している属性", "title" => "ウェブページのタイトル", "subtitle" => "ウェブページの簡単な説明", 
      "self" => "このXMLファイルのURL", "url" => "ウェブページのURL", 
      "updated" => "XMLファイルの最終更新日", "feedid" => "あなたのページ独自のID", 
      "rights" => "ページの著作権表記", "aname" => "ページの製作者", 
      "amail" => "ページ製作者のメールアドレス", "others" => "その他の要素"}
    
  end
  
  # Atom XMLファイルを読み込んで解析し、等価なFeedオブジェクトを返却するメソッドです
  #
  # _path_ :: Atom XMLファイルのパス
  def self.readxml(path)
    
    # ファイルを読み込みます
    doc = REXML::Document.new(myopen(path, "r:utf-8"){|f|f.read})
    xml = {}
    others = []
    
    # Feedの属性値を取得します
    xmlattr = []
    doc.elements["feed"].attributes.each_attribute do |attr|
      xmlattr.push("#{attr.to_string} ")
    end
    xml[:feedattr] = xmlattr.join("\n").gsub(/"/, "'")
    
    # XML解析部分です。各element毎に判定を行います
    doc.elements.each("feed") do |elm|
      elm.elements.each { |child|
        begin
          case child.name
            when "id"
            xml[:feedid] = child.text
            when "title", "subtitle", "updated", "rights"
            xml[child.name.to_sym] = child.text
            when "author"
            child.elements.each do |gchild|
              case gchild.name
                when "name"
                xml[:aname] = gchild.text
                when "email"
                xml[:amail] = gchild.text
              end
            end
            when "link"
            child.attributes.each do |k, v|
              if k == "rel"
                if v == "self"
                  xml[:self] = child.attributes["href"]
                elsif v == "alternate"
                  xml[:url] = child.attributes["href"]
                end
              end
            end
            when "entry"
            # Entry要素は無視します
          else
            # 上記判定以外の全要素は配列に格納します
            others.push(child.to_s)
          end
        rescue NoMethodError
        end
        
      }
    end
    
    # Others要素を結合して代入します
    xml[:others] = others.join("\n")
    feed = Feed.new(xml)
    return feed
  end
  
  # 内部に保持している情報を、Atom Feed1.0形式の文字列に出力するメソッドです
  def to_s
    buf = []
    
    # buf.push("<feed #{@attr[:feedattr]}>")
    buf.push("<feed xml:lang='ja-jp' xmlns='http://www.w3.org/2005/Atom'>");
    buf.push("<title type=\"text\">#{@attr[:title]}</title>")
    buf.push("<subtitle type=\"text\">#{@attr[:subtitle]}</subtitle>")
    buf.push("<link rel=\"self\" type=\"application/atom+xml\" href=\"#{@attr[:self]}\" />")
    buf.push("<link rel=\"alternate\" type=\"text/html\" href=\"#{@attr[:url]}\" />")
    buf.push("<updated>#{Time.now.iso8601}</updated>")
    buf.push("<id>#{@attr[:feedid]}</id>")
    buf.push("<rights type=\"text\">#{@attr[:rights]}</rights>")
    buf.push("<author>")
    buf.push("\t<name>#{@attr[:aname]}</name>")
    buf.push("\t<email>#{@attr[:amail]}</email>")
    buf.push("</author>")
    buf.push("#{CGI.unescapeHTML(@attr[:others])}") if @attr[:others] != ""
    
    return buf.join("\n")
  end
  
  # Feed情報更新メソッド
  def self.update(path, feed)
    entrylist = Entry.readxml(path)
    Feed.to_xml(path, feed, entrylist)
    
    true
  end
  
  # XMLファイル出力用メソッドです
  #
  # _feed_ :: Feedオブジェクト
  # _entry_ :: Entryオブジェクトの配列
  def self.to_xml(path, feed, entrylist_tmp)
    buf = []
    entrylist = entrylist_tmp.dup
    buf.push("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
    buf.push("#{feed.to_s}\n")
    entrylist.each { |entry|
      if REPLACEENTRYIDANDURL
        entry.entryid.gsub!(/^[^\?]*\?/, "")
        entry.entryid = feed.url + "?" + entry.entryid
        entry.url = feed.url + "#" + entry.entryid
      end
      buf.push("#{entry.to_s}\n")
    }
    buf.push("</feed>")
    
    myopen(path, "w") do |f|
      f.print buf.join("\n")
    end
  end
  
  # XMLファイル出力用メソッドです
  #
  # _feed_ :: Feedオブジェクト
  # _entry_ :: Entryオブジェクトの配列
  def self.to_xml_plain(path, feed, entrylist)
    buf = []
    buf.push("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
    buf.push("#{feed.to_s}\n")
    entrylist.each { |entry|
      buf.push("#{entry.to_s}\n")
    }
    buf.push("</feed>")
    
    myopen(path, "w") do |f|
      f.print buf.join("\n")
    end
  end
  
end

# = Entryクラス
#
# Entryの基礎情報を保有するクラスです
class Entry < AbstractEntry
  # 初期化メソッドです
  #
  # _hash_ :: 値を格納したhash配列。entry.new(CGI::Session.new(new CGI).params)のようにして使います。
  def initialize(hash)
    # 内部データ保持用のハッシュ配列です
    @attr = {}
    
    # 内部データの項目名を格納する配列です
    @paramlist = ["entryid", "title", "summary", "published", "updated", "url", "content", "category", "others"]
    
    # AbstractEntryのinitializeメソッドを呼び出し、値を@attr配列に格納します
    super(hash)
    
    # 可視・不可視を示すハッシュキーを格納する配列です
    @display = {"entryid" => "none", "title" => "",
      "summary" => "", "published" => "none",
      "updated" => "none", "url" => "",
      "content" => "", "category" => "", 
      "others"=>"none"}
    
    # デバッグモードの場合、全ての入力要素を表示します
    if DEBUG == true
      @display.each do |key, val|
        @display[key] = ""
      end
    end
    
    # ハッシュキーの日本語説明を格納する配列です
    @name = {"entryid" => "記事固有のID", "title" => "記事のタイトル",
      "summary" => "記事の簡単な説明", "published" => "記事の出版時刻",
      "updated" => "記事の更新時刻", "url" => "記事へのURLアドレス",
      "content" => "記事の本文", "category" => "タグ", "others"=>"その他の項目"}
  end
  
  # Atom XMLファイルを読み込んで解析し、等価なEntryオブジェクト配列を返却するメソッドです
  #
  # _path_ :: Atom XMLファイルのパス
  def self.readxml(path)
    
    # ファイルを読み込みます
    doc = REXML::Document.new(myopen(path, "r:utf-8"){|f|f.read})
    entrylist = []
    xml = {}
    
    # XML解析部分です。各element毎に判定を行います
    doc.elements.each("feed/entry") do |elm|
      xml = {}
      category = []
      others = []
      elm.elements.each do |child|
        begin
          case child.name
            when "id"
            xml[:entryid] = child.text
            when "link"
            xml[:url] = child.attributes["href"]
            when "title", "summary", "summary", "published", "updated", "content"
            xml[child.name.to_sym] = child.text
            when "category"
            category << child.to_s
          else
            # 上記判定以外の全要素は配列に格納します
            others.push(child.to_s)
          end
        rescue NoMethodError
        end
      end
      # Category要素を代入します
      xml[:category] = category.join("\n")
      # Others要素を結合して代入します
      xml[:others] = others.join("\n")
      entrylist.push(Entry.new(xml))
    end
    
    return entrylist
  end
  
  # Atom XMLファイルを読み込んで解析し、テンプレートファイルにしたがってHTMLに変換するメソッドです
  def self.to_html(xmlpath, destpath, entry_temppath, html_temppath)
    # 引数チェック - 全必須
    if xmlpath.empty? or destpath.empty? or entry_temppath.empty? or html_temppath.empty?
      raise ArgumentError
    end
    
    # 必須ファイル存在チェック
    unless File.exist?(xmlpath) and File.exist?(entry_temppath) and File.exist?(html_temppath)
      raise IOError
    end
    
    # XML読み込み
    entrylist = Entry.readxml(xmlpath)
    
    body = ''
    entrylist.each { |e|
      # Entry毎のHTML表示部分を生成
      body << e.to_template(entry_temppath)
    }
    
    # HTML全体のテンプレートを生成
    html_temp = HtmlWriter.new(html_temppath, binding)
    
    # HTMLに書き込み
    myopen(destpath, 'w:utf-8') { |f|
      f.write(CGI.pretty(html_temp.to_code))
    }
  end
  
  # Entryをテンプレートに沿って変形するメソッド
  def to_template(temppath)
    erb = HtmlWriter.new(temppath, binding)
    title = CGI.unescapeHTML(@attr[:title])
    date = @attr[:published]
    content = CGI.unescapeHTML(@attr[:content])
    erb.to_code
  end
  
  # Entry挿入メソッド
  def self.insert(path, entry)
    feed = Feed.readxml(path)
    entrylist = Entry.readxml(path)
    entrylist.unshift entry
    
    Feed.to_xml(path, feed, entrylist)
    
    true
  end
  
  # Entry更新メソッド
  def self.update(path, entry)
    feed = Feed.readxml(path)
    entrylist = Entry.readxml(path)
    
    successed = false
    entrylist.each_with_index { |e, i|
      if e.entryid == entry.entryid
        entrylist[i] = entry 
        successed = true
      end
    }
    Feed.to_xml(path, feed, entrylist) if successed
    
    successed
  end
  
  # Entryロードメソッド
  def self.select(path, entryid)
    feed = Feed.readxml(path)
    entrylist = Entry.readxml(path)
    
    entry = nil
    entrylist.each_with_index { |e, i|
      entry = entrylist[i].dup if e.entryid == entryid
    }
    
    entry
  end
  
  # Entry消去メソッド
  def self.delete(path, entryid)
    feed = Feed.readxml(path)
    entrylist = Entry.readxml(path)
    
    successed = false
    delete_index = -1
    entrylist.each_with_index { |e, i|
      if e.entryid == entryid
        delete_index = i
        successed = true
      end
    }
    
    if successed
      entrylist.delete_at delete_index
      Feed.to_xml(path, feed, entrylist)
    end
    
    successed
  end
  
  # データソースから読み取ったHTMLを、エディタで編集可能な形式に変換するメソッドです
  def content_for_generator
    str = @attr[:content].dup
    str.strip!
    str.gsub!(/(&lt;\/(?:p|h\d|div)(?:&gt;|>))\n/i, '\1')
    str.gsub!(/\n/, '&lt;br&gt;') if REPLACEBRTAG
    str.gsub!(/(&lt;(?:(?!&gt;).)*?)#{Regexp.escape(RELAYPATH)}/) { "#$1#{HOMEBASE}" }
    str
  end
  
  # エディタで編集されたCONTENT要素を、データソースに書き込める形式に変換するメソッドです
  def content_for_blog
    str = @attr[:content].dup
    str = CGI.unescapeHTML(str)
    str.strip!
    str.gsub!(/(\r\n|\n)/, "")
    str.gsub!(/<br>|<br[ ]*\/>/i, "\n") if REPLACEBRTAG
    str.gsub!(/(<br>|<br[ ]*\/>|<\/p>|<\/h\d>|<\/div>)(?=[^\n])/i) { "#$1\n" } unless REPLACEBRTAG
    str.gsub!(/(<[^>]*?)#{Regexp.escape(HOMEBASE)}/) { "#$1#{RELAYPATH}" }
    CGI.escapeHTML(str)
  end
  
  # 確認画面で表示されるCONTENT要素を生成するメソッドです
  def content_for_view
    str = @attr[:content].dup
    str = CGI.unescapeHTML(str)
    str.strip!
    str.gsub!(/<br>|<br[ ]*\/>/i, "\n") if REPLACEBRTAG
    str.gsub!(/(<[^>]*?)#{Regexp.escape(RELAYPATH)}/) { "#$1#{HOMEBASE}" }
    str
  end
  
  # 内部に保持している情報を、Atom Feed1.0形式の文字列に出力するメソッドです
  def to_s
    buf = []
    buf.push("<entry>")
    buf.push("<id>#{@attr[:entryid]}</id>")
    buf.push("<title>#{@attr[:title]}</title>")
    buf.push("<summary>#{@attr[:summary]}</summary>")
    buf.push("<published>#{@attr[:published]}</published>")
    buf.push("<updated>#{@attr[:updated]}</updated>")
    buf.push("<link href=\"#{@attr[:url]}\" />")
    buf.push("<content type=\"html\">#{@attr[:content]}</content>")
    buf.push("#{CGI.unescapeHTML(@attr[:category])}") if @attr[:category] != ""
    buf.push("#{CGI.unescapeHTML(@attr[:others])}") if @attr[:others] != ""
    buf.push("</entry>")
    
    return buf.join("\n")
  end
end

# = LogListクラス
#
# loglist.xmlにかかわる入出力を行います
class LogList
  # 初期化メソッドです
  #
  # _display_ :: 画面表示用文字の配列
  # _path_ :: XMLファイルパスの配列
  # _logpath_ :: loglist.xmlのパス
  def initialize(display, path, logpath)
    @display = display
    @path = path
    @logpath = logpath
  end
  
  attr_accessor :display, :path, :logpath
  
  # loglist.xmlファイルを読み込んで解析し、LogListオブジェクトを返却するメソッドです
  #
  # _logpath_ :: loglist.xmlへのパス
  def LogList.readxml(logpath)
    
    # ファイルを読み込みます
    lines = []
    myopen(logpath, "r:utf-8") { |f|
      lines = f.readlines
    }
    
    doc = REXML::Document.new(lines.join("\n"))
    @display = []
    @path = []
    
    doc.elements.each("list/file") { |elm|
      elm.elements.each {|child|
        case child.name
          when "display"
          @display.push(child.text)
          when "path"
          @path.push(child.text)
        else 
          # With no action
        end
      }
    }
    
    return LogList.new(@display, @path, logpath)
  end
  
  # loglist.xmlにオブジェクトの内容を反映するメソッドです
  def to_xml
    buf = []
    buf.push("<list>")
    path.each_with_index do |path, i|
      buf.push("<file>")
      buf.push("<display>#{@display[i]}</display>")
      buf.push("<path>#{@path[i]}</path>")
      buf.push("</file>")
    end
    buf.push("</list>")
    
    myopen(@logpath, "w") do |f|
      f.print buf.join("\n")
    end
  end
  
end

# = FileUploaderクラス
#
# 画像の管理を行うクラスです
class FileUploader
  # 初期化メソッドです
  def initialize
  end
  
  # ファイルの一覧を取得し、ファイル名称を格納した配列を返却します
  def filelist
    arr = Dir::entries(XMLPATH).sort
    arr.delete(".")
    arr.delete("..")
    
    return arr
  end
  
  # ファイルの消去を実行します
  #
  # _name_ :: 消去するファイル名
  def delete(name)
    File.delete(XMLPATH + name.match(/[^\/]*?$/).to_a[0])
  end
  
  # ファイルのアップロードを実行します
  #
  # _name_ :: アップロードファイルの名称
  # _img_ :: アップロードファイル
  def upload(name, img)
    open(XMLPATH + File.basename(name), "w") do |f|
      f.binmode
      f.write img
    end
  end
end

# = PluginManagerクラス
#
# プラグインの処理を行うクラスです
class PluginManager
  def self.exec(mode, module_name, filepath)
    feed = Feed.readxml(XMLPATH + filepath)
    entries = Entry.readxml(XMLPATH + filepath)
    feed.freeze
    entries.freeze
    Dir.foreach(PLUGINDIR) do |fn|
      begin 
        next unless File.extname(fn) == '.rb'
        require File.join(PLUGINDIR, fn)
        plugin_name = "#{module_name}::"
        plugin_name << File.basename(fn).gsub(/\.rb\Z/, "")
        plugin_ins = plugin_name.split(/::/).inject(Object) { |c,name| c.const_get(name) }
        plugin_ins.new.exec(mode, feed, entries)
      rescue
      end
    end
  end
end

# = Controllerクラス
#
# コントローラ部分に相当する処理を受け持つクラスです
class Controller
  def Controller.NormalForm(cgi, session, params, db)
    db.transaction do
      # modeとactionの相関図は以下のようになります。
      # [mode] + [action]
      #        + [action]
      #        + [action]
      #        + ... and more
      case params["mode"]
        # ログ選択画面
        when "logselect"
        session["filepath"] = params["logpath"]
        db["loglist"] = LogList.readxml(LISTXMLPATH)
        # 初期状態で選択されるログは「loglist.xml」の最上に位置するログになります
        session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
        db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
        db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
        
        # 新規記事追加部分
        when "newentry"
        case params["action"]
          # 確認画面
          when "confirm"
          session["target_filepath"] = params["target_filepath"]
          db["newentry"] = Entry.new(params)
          db["newentry"].content = db["newentry"].content_for_blog
          # 記事の追記を実際にファイルに反映
          when "exec"
          session["target_filepath"] = params["target_filepath"]
          successed = Entry.insert(XMLPATH + File.basename(params["target_filepath"]), Entry.new(params))
          unless successed
            session["error"] = "記事の新規追加に失敗しました。" 
            params["mode"] = "error"
          else
            # 成功時はプラグイン処理を実施する
            PluginManager.exec("newentry", "FeedGenPlugins", File.basename(session["filepath"]))
            session["info"] = "記事の新規作成が成功しました。"
          end
          # 画面を戻った際の処理
          when "back"
          session["target_filepath"] = params["target_filepath"]
          db["newentry"] = Entry.new(params)
        else 
          # New Diary - Default
          session["target_filepath"] = session["filepath"]
          db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
          db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
        end
        
        # 記事編集部分
        when "editentry"
        case params["action"]
          # 編集画面
        when "edit"
          session["target_filepath"] = params["target_filepath"]
          session["editid"] = cgi["editid"].to_s
          db["editentry"] = Entry.select(XMLPATH + File.basename(session["target_filepath"]), session["editid"])
          # 確認画面
        when "confirm"
          session["target_filepath"] = params["target_filepath"]
          session["editid"] = cgi["editid"].to_s
          db["editentry"] = Entry.new(params)
          db["editentry"].content = db["editentry"].content_for_blog
          # 記事の変更を実際にファイルに反映
        when "exec"
          session["target_filepath"] = params["target_filepath"]
          successed = Entry.update(XMLPATH + File.basename(params["target_filepath"]), Entry.new(params))
          unless successed
            session["error"] = "記事の編集処理に失敗しました。該当の日記が既に存在しない可能性があります。" 
            params["mode"] = "error"
          else
            # 成功時はプラグイン処理を実施する
            PluginManager.exec("editentry", "FeedGenPlugins", File.basename(session["filepath"]))
            session["info"] = "記事の編集が完了しました。"
            
            # 一覧を更新後の内容で更新する。
            session["target_filepath"] = session["filepath"]
            db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
            db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
          end
        when "back"
          session["target_filepath"] = params["target_filepath"]
          db["editentry"] = Entry.new(params)
        else
          # Edit Diary - Default
          session["target_filepath"] = session["filepath"]
          db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
          db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
        end
        
        # 記事削除部分
        when "delentry"
        case params["action"]
          # 確認画面
          when "confirm"
          session["target_filepath"] = params["target_filepath"]
          session["delid"] = cgi["delid"].to_s
          db["delentry"] = Entry.select(XMLPATH + File.basename(session["target_filepath"]), session["delid"])
          # 記事の削除を実際にファイルに反映
          when "exec"
          session["target_filepath"] = params["target_filepath"]
          successed = Entry.delete(XMLPATH + File.basename(params["target_filepath"]), cgi["delid"].to_s)
          unless successed
            session["error"] = "日記の編集処理に失敗しました。該当の日記が既に存在しない可能性があります。" 
            params["mode"] = "error"
          else
            # 成功時はプラグイン処理を実施する
            PluginManager.exec("delentry", "FeedGenPlugins", File.basename(session["filepath"]))
            session["info"] = "記事の削除に成功しました。"
            
            # 内容を更新する
            session["target_filepath"] = session["filepath"]
            db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
            db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
          end
          when "back"
          session["target_filepath"] = params["target_filepath"]
          db["feed"] = Feed.readxml(XMLPATH + File.basename(session["target_filepath"]))
          db["entry"] = Entry.readxml(XMLPATH + File.basename(session["target_filepath"]))
        else
          # Delete Diary - Default
          session["target_filepath"] = session["filepath"]
          db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
          db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
        end
        
        # Feed情報変更部分
        when "editfeed"
        case params["action"]
          # 確認画面
        when "confirm"
          session["target_filepath"] = params["target_filepath"]
          db["feed"] = Feed.new(params)
          # 実際にFeed情報の変更をファイルに反映
        when "back"
          session["target_filepath"] = params["target_filepath"]
          db["feed"] = Feed.new(params)
        when "exec"
          session["target_filepath"] = params["target_filepath"]
          Feed.update(XMLPATH + File.basename(params["target_filepath"]), Feed.new(params))
          session["info"] = "基本情報の更新が完了しました。"
        else
          session["target_filepath"] = session["filepath"]
          db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
        end
        
        # ログ編集モード
        when "log"
          # 必ず内部データをリフレッシュする
          db["loglist"] = LogList.readxml(LISTXMLPATH)
          case params["action"]
          # ログファイルの編集を実際にファイルに反映
          when "addexec"
            # エラーチェック。この段階のエラーは強度のエラーを発する。
            db["loglist"].path.each do |val|
              if val == cgi["logpath"]
                # 重複していた場合エラーメッセージを表示し、処理を行わない
                params["action"] = ""
                params["mode"] = "error"
                session["error"] = "ログファイルの追加中に重大なエラーが発生しました。環境を見直してください。"
                return
              end
            end
          
            # 入力された内容を保持する配列に追加
            db["loglist"].path[1, 0] = cgi["logpath"]
            db["loglist"].display[1, 0] = cgi["logdisplay"]
            db["loglist"].to_xml
            # 既存のdiary.xmlを指定された名称でコピーして保存
            FileUtils.copy_file(XMLPATH + File.basename(db["loglist"].path[0]), XMLPATH + File.basename(db["logpath"]), true)
            # 保持している情報を更新する
            db["loglist"] = LogList.readxml(LISTXMLPATH)
            db["entry"] = []
            # 新たなdiary.xmlを生成。この際保持する情報は同一のFeedオブジェクトnew()
            Feed.to_xml(XMLPATH + File.basename(db["loglist"].path[0]), db["feed"], [])
          
            session["info"] = "ログファイルの追加が完了しました。"
            
              # 現在編集中のデータを強制的に最上のファイルパスに変更
              session["filepath"] = db["loglist"].path[0]
              
              # 前月の時刻を作成します
              prevmonth = (DateTime.now << 1)
              
              # 前月の時刻を元に、ディフォルト書式を生成します
              db["logpath"] = FEEDXMLDIR + prevmonth.strftime("%Y%m") + ".xml"
              db["logdisplay"] = prevmonth.strftime("%Y年%m月").gsub("年0", "年")
          
          # 確認画面
          when "addconfirm"
          # 入力されたログが既に存在するかを確認
            db["loglist"].path.each do |val|
              if val == cgi["logpath"]
                # 重複していた場合エラーメッセージを表示し、処理を行わない
                params["action"] = ""
                session["error"] = "同一のファイルが存在します！別の名前を指定してください。"
                return
              end
            end
          
            db["logpath"] = cgi["logpath"]
            db["logdisplay"] = cgi["logdisplay"]
          
            if db["logpath"].blank? || db["logdisplay"].blank?
              params["action"] = ""
            end
          
          when "back"
          
          # 削除確認画面
          when "delconfirm"
            db["logdelindex"] = params["logdelindex"].to_i
          
            if db["logdelindex"] < 1
              session["error"] = "ログファイルの削除パラメタが不正です。"
              params["action"] = ""
            end
            
          # 削除処理
          when "delexec"
            if cgi["logdelindex"].to_i < 1
              params["action"] = ""
              params["mode"] = "error"
              session["error"] = "ログファイルの削除中に重大なエラーが発生しました。環境を見直してください。"
              return
            else
              # 記事ファイルを削除します
              File.delete(XMLPATH + File.basename(db["loglist"].path[db["logdelindex"]]))
              # ログリストから削除します
              db["loglist"].path.delete_at(cgi["logdelindex"].to_i)
              db["loglist"].display.delete_at(cgi["logdelindex"].to_i)
              db["loglist"].to_xml
              # 保持している情報を更新する
              db["loglist"] = LogList.readxml(LISTXMLPATH)
              db["entry"] = []
            
              session["info"] = "ログファイルの削除が完了しました。"
              
              # 現在編集中のデータを強制的に最上のファイルパスに変更
              session["filepath"] = db["loglist"].path[0]
              
              # 前月の時刻を作成します
              prevmonth = (DateTime.now << 1)
              
              # 前月の時刻を元に、ディフォルト書式を生成します
              db["logpath"] = FEEDXMLDIR + prevmonth.strftime("%Y%m") + ".xml"
              db["logdisplay"] = prevmonth.strftime("%Y年%m月").gsub("年0", "年")
            end
          
          # 編集画面
          when "edit"
            db["logeditindex"] = params["logdelindex"].to_i
          
            db["logpath"] = db["loglist"].path[db["logeditindex"]]
            db["logdisplay"] = db["loglist"].display[db["logeditindex"]]
          
            if db["logeditindex"] == 0
              session["error"] = "ログファイルの編集パラメタが不正です。"
              params["action"] = ""
            end
            
          # 編集確認画面
          when "editconfirm"
            checkflag = true
            db["loglist"].path.each_with_index do |val, i|
              if db["logeditindex"] != i
                if params["logpath"].to_s == db["loglist"].path[i].to_s
                  checkflag = false
                end
              end
            end
          
            if checkflag == false
              params["action"] = "edit"
              session["error"] = "同一のファイルが存在します！別の名前を指定してください。"
            else
              db["loginsertindex"] = params["loginsertindex"].to_i
            
              db["logpath"] = params["logpath"].to_s
              db["logdisplay"] = params["logdisplay"].to_s
            end
            
          # 編集実行
          when "editexec"
            checkflag = true
            db["loglist"].path.each_with_index do |val, i|
              if db["logeditindex"] != i
                if params["logpath"].to_s == db["loglist"].path[i].to_s
                  checkflag = false
                end
              end
            end
          
            if checkflag == false
              params["action"] = ""
              params["mode"] = "error"
              session["error"] = "ログファイルの編集中に重大なエラーが発生しました。環境を見直してください。"
              return
            else
              db["loginsertindex"] = params["loginsertindex"].to_i
            
              db["logpath"] = params["logpath"].to_s
              db["logdisplay"] = params["logdisplay"].to_s
            
              # ファイルを移動します
              if XMLPATH + File.basename(db["loglist"].path[db["logeditindex"]]) != XMLPATH + File.basename(db["logpath"])
                FileUtils.move(XMLPATH + File.basename(db["loglist"].path[db["logeditindex"]]), XMLPATH + File.basename(db["logpath"]))
                # ログリストを更新します
                db["loglist"].path.delete_at(db["logeditindex"])
                db["loglist"].display.delete_at(db["logeditindex"])
                db["loglist"].path.insert(db["loginsertindex"] + 1, db["logpath"])
                db["loglist"].display.insert(db["loginsertindex"] + 1, db["logdisplay"])
                db["loglist"].to_xml
              end
            
              session["info"] = "ログファイルの編集が完了しました。"
              
              # 現在編集中のデータを強制的に最上のファイルパスに変更
              session["filepath"] = db["loglist"].path[0]
              
              # 前月の時刻を作成します
              prevmonth = (DateTime.now << 1)
              
              # 前月の時刻を元に、ディフォルト書式を生成します
              db["logpath"] = FEEDXMLDIR + prevmonth.strftime("%Y%m") + ".xml"
              db["logdisplay"] = prevmonth.strftime("%Y年%m月").gsub("年0", "年")
            end
          
          # 初期表示画面
          else
            # 現在編集中のデータを強制的に最上のファイルパスに変更
            session["filepath"] = db["loglist"].path[0]
          
            # 前月の時刻を作成します
            prevmonth = (DateTime.now << 1)
          
            # 前月の時刻を元に、ディフォルト書式を生成します
            db["logpath"] = FEEDXMLDIR + prevmonth.strftime("%Y%m") + ".xml"
            db["logdisplay"] = prevmonth.strftime("%Y年%m月").gsub("年0", "年")
          end
        
        # インポートモードの場合の処理
        when "import"
        db["loglist"] = LogList.readxml(LISTXMLPATH)
        
        # リセットモードの場合の処理
        when "reset"
        case params["action"]
          # リセット実行時の処理
          when "exec"
          file = FileUploader.new
          file.filelist().each { |fname|
            file.delete(fname) if File.ftype(XMLPATH + fname) == "file"
          }
          
          # 全ファイルの初期化を実行する
          # loglist.xmlの初期化
          loglist = LogList.new(["最新の記事"], ["#{FEEDXMLDIR}#{INITIALXML}"], LISTXMLPATH)
          loglist.to_xml
          
          db["loglist"] = LogList.readxml(LISTXMLPATH)
          
          # diary.xmlの初期化
          Feed.to_xml(XMLPATH + db["loglist"].path[0].match(/[^\/]*?$/).to_a[0], Feed.new({}), [])
          
          # 初期の編集ファイルはloglist.xml上の最も上のファイル
          session["filepath"] = db["loglist"].path[0]
          db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
          db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
          
          # 画面の遷移先をトップページにする
          params["mode"] = ""
          params["action"] = ""
          # パラメタ無しの処理
        else
          # ファイル一覧を取得する
          filelist = FileUploader.new.filelist()
          
          # タイプがファイルのみリストとして取得する
          db["filelist"] = []
          filelist.each { |fname| db["filelist"] << fname if File.ftype(XMLPATH + fname) == "file"}
        end
        
        # ログイン時の画面
      else
        # loglist.xmlが存在するかチェック
        if File.exist?(LISTXMLPATH) == false
          # なかった場合はloglist.xmlを自動生成
          loglist = LogList.new(["最新の記事"], ["#{FEEDXMLDIR}#{INITIALXML}"], LISTXMLPATH)
          loglist.to_xml
          Feed.to_xml(XMLPATH + File.basename(loglist.path[0]), Feed.new({}), [])
        end
        
        db["loglist"] = LogList.readxml(LISTXMLPATH)
        
        # diary.xmlが存在するかチェック
        if File.exist?(XMLPATH + File.basename(db["loglist"].path[0])) == false
          # なかった場合はdiary.xmlを自動生成
          Feed.to_xml(XMLPATH + File.basename(db["loglist"].path[0]), Feed.new({}), [])
        end
        
        # 初期の編集ファイルはloglist.xml上の最も上のファイル
        session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
        db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
        db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
      end
    end
  end
  
  # マルチパートフォームの場合の処理です
  def Controller.MultiForm(cgi, session, params, db)
    db.transaction do
      # loglist.xmlをロードします
      db["loglist"] = LogList.readxml(LISTXMLPATH)
      case params["mode"]
        # 特定位置に挿入する場合の処理
        when "insert"
        case params["action"]
        when "exec"
          # この段階のエラーに対しては強度のエラーを発する
          checkflag = true
          db["logpath"] = Controller.get_mpart_value(cgi["logpath"])
          db["loglist"].path.each do |val|
            if val == db["logpath"]
              # 重複していた場合エラーメッセージを表示し、処理を行わない
              db["logpath"] = ""
              params["action"] = ""
              params["mode"] = "error"
              session["error"] = "ログファイルの追加中に重大なエラーが発生しました。"
              return
            end
          end
          
          db["loginsertindex"] = cgi["loginsertindex"].read.to_i
          db["logdisplay"] = Controller.get_mpart_value(cgi["logdisplay"])
          
          # 0位置への挿入防止
          if db["loginsertindex"] == 0
            params["action"] = ""
            params["mode"] = "error"
            session["error"] = "ログファイルの追加中に重大なエラーが発生しました。"
            return
          end
          
          if File.basename(db["logpath"]).blank? || db["logdisplay"].blank?
            params["action"] = ""
            params["mode"] = "error"
            session["error"] = "ログファイルの追加中に重大なエラーが発生しました。"
            return
          end
          
          # ファイルを実際にアップロードします
          file = FileUploader.new
          file.upload(db["logpath"], db["importxml"])
          
          # loglist.xmlを更新します
          db["loglist"].path[db["loginsertindex"], 0] = db["logpath"]
          db["loglist"].display[db["loginsertindex"], 0] = db["logdisplay"]
          db["loglist"].to_xml
          
          # 情報を更新します
          db["loglist"] = LogList.readxml(LISTXMLPATH)
          session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
          db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
          db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
          
          session["info"] = "ログファイルのインポート処理が完了しました。"
          
        when "confirm"
          # 入力されたログファイルパスが既に存在するかを確認
          checkflag = true
          db["logpath"] = Controller.get_mpart_value(cgi["logpath"])
          db["loglist"].path.each do |val|
            if val == db["logpath"]
              # 重複していた場合エラーメッセージを表示し、処理を行わない
              db["logpath"] = ""
              params["action"] = ""
              session["error"] = "同一のファイルが存在します！別の名前を指定してください。"
              checkflag = false
              return
            end
          end
          
          if checkflag
            db["loginsertindex"] = cgi["loginsertindex"].read.to_i
            db["logdisplay"] = Controller.get_mpart_value(cgi["logdisplay"])
            db["importxml"] = cgi["updata"].read
            
            # XMLの整合性をチェックします
            begin
              REXML::Document.new(Controller.fix_updata_enc(db["importxml"].to_s))
            rescue => exception
              session["error"] = "不正な整形のXMLです！ファイルを見直してください。<br>"
              session["error"] << CGI.escapeHTML(exception.to_s).gsub("\n", "<br>")
              params["action"] = ""
              return
            end
          end
          
          # 0位置への挿入防止
          if db["loginsertindex"] == 0
            params["action"] = ""
            session["error"] = "ラジオボックスでログの挿入位置を選択してください！"
            return
          end
          
          if db["logpath"] == FEEDXMLDIR || db["logdisplay"].blank?
            params["action"] = ""
            session["error"] = "インポートファイル、及び、ログの表示名は空欄にできません。"
            return
          end
          
        else
        end
        
        # diary.xmlを入れ替える処理
        when "replace"
        case params["action"]
        when "exec"
          # この段階のエラーに対しては強度のエラーを発する
          checkflag = true
          db["logpath"] = Controller.get_mpart_value(cgi["logpath"])
          db["loglist"].path.each do |val|
            if val == db["logpath"]
              # 重複していた場合エラーメッセージを表示し、処理を行わない
              params["action"] = ""
              params["mode"] = "error"
              session["error"] = "ログファイルの追加中に重大なエラーが発生しました。"
              return
            end
          end
          
          db["logdisplay"] = Controller.get_mpart_value(cgi["logdisplay"])
          
          if File.basename(db["logpath"]).blank? || db["logdisplay"].blank? || db["importxml"].blank?
            params["action"] = ""
            params["mode"] = "error"
            session["error"] = "ログファイルの追加中に重大なエラーが発生しました。"
            return
          end
          
          # diary.xmlを移動します
          FileUtils.move(XMLPATH + INITIALXML, XMLPATH + File.basename(db["logpath"]))
          # ファイルをアップロードします
          file = FileUploader.new
          file.upload(INITIALXML, db["importxml"])
          
          # loglist.xmlを更新します
          db["loglist"].path[1, 0] = db["logpath"]
          db["loglist"].display[1, 0] = db["logdisplay"]
          db["loglist"].to_xml
          
          # 情報を更新します
          db["loglist"] = LogList.readxml(LISTXMLPATH)
          session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
          db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
          db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
          
          session["info"] = "ログファイルのインポート処理が完了しました。"
          
        when "confirm"
          # 入力されたログファイルパスが既に存在するかを確認
          checkflag = true
          db["logpath"] = Controller.get_mpart_value(cgi["logpath"])
          db["loglist"].path.each do |val|
            if val == db["logpath"]
              # 重複していた場合エラーメッセージを表示し、処理を行わない
              params["action"] = ""
              session["error"] = "同一のファイルが存在します！別の名前を指定してください。"
              checkflag = false
              return
            end
          end
          
          if checkflag == true
            db["logdisplay"] = Controller.get_mpart_value(cgi["logdisplay"])
            db["importxml"] = cgi["updata"].read
            
            # XMLの整合性をチェックします
            begin
              REXML::Document.new(Controller.fix_updata_enc(db["importxml"].to_s))
            rescue => exception
              session["error"] = "不正な整形のXMLです！ファイルを見直してください。<br>"
              session["error"] << CGI.escapeHTML(exception.to_s).gsub("\n", "<br>")
              params["action"] = ""
              return
            end
          end
          
          if db["logpath"].blank? || db["logdisplay"].blank? || db["importxml"].blank?
            params["action"] = ""
            session["error"] = "インポートファイル、及び、ログの表示名、ログのパスは空欄にできません。"
            return
          end
          
        else
        end
        
      else
      end
    end
  end
  
  # Formのenctypeがmultypartだった場合、入力された値をrubyバージョンに左右されずに読み取るメソッドです。
  def Controller.get_mpart_value(cgi_param)
    if RUBY_VERSION >= "1.9.0"
      cgi_param[0..cgi_param.length].to_s
    else
      cgi_param.read.to_s
    end
  end
  
  # アップロードされたXMLをバージョンに左右されずに読み取るために、ruby-1.9.1以上ではエンコーディングを強制指定します
  def Controller.fix_updata_enc(file_to_s)
    if RUBY_VERSION >= "1.9.0"
      file_to_s.force_encoding("UTF-8")
    else
      file_to_s
    end
  end
  
end

def main
  # SESSION変数、パラメータなどを取得します
  cgi = CGI.new
  session = CGI::Session.new(cgi)
  params = Hash[*cgi.params.to_a.map{|k, v| [k, v[0].to_s]}.flatten]
  
  # ロガーを作成する
  logger = WebLogger.get_logger(cgi.script_name, LOG_DIR, LOG_RELEASE_MODE)
  
  # コントローラー部分
  # セッション管理
  session["info"] = ""
  session["error"] = ""
  if session["login"] != "true"
    
    # ログイン情報を確認
    LOGININFO.each {|h|
      if (cgi["loginid"] == h[:id] && cgi["password"] == h[:password])
        session["login"] = "true"
        session["name"] = h[:name]
        
        # ログを記録
        logger.info "ログインユーザ：#{h[:id]}, IPアドレス：#{cgi.remote_addr}"
        
        break
      end
    }
    
    if (session["login"] == "true")   
      # ワークフォルダの中をクリーンアップします
      filelist = Dir::entries("./work")
      # 削除条件 : 最終変更日時から1日(60*60*24sec)かつ、ファイルタイプがファイルの場合
      filelist.each do |file|
        File.delete("./work/#{file}") if Time.now - File.ctime("./work/#{file}") > 86400 && File.ftype("./work/#{file}") == "file"
      end
    end
    
    # ログイン失敗ユーザを記録
    if (session["login"] != "true" and (cgi["loginid"] != "" or cgi["password"] != ""))
      logger.info "次のアクセスがログインに失敗しました。ログインユーザ：#{cgi["loginid"]}, IPアドレス：#{cgi.remote_addr}"
    end
  end
  
  # ログアウト処理
  if params["mode"] == "logout"
    session["login"] = nil
    session.delete
  end
  
  begin
    # メインコントローラー
    # セッションが有効な場合のも実行します
    if session["login"] == "true"
      # PStore破損チェック！
      begin
        db = PStore.new("./work/#{session.session_id}.dat")
        db.transaction do
          session["error"] = ""
        end
      rescue
        # PStoreファイルを破棄する
        File.delete("./work/#{session.session_id}.dat")
        # PStoreが破損していた場合はセッション情報を破棄する
        session["login"] = nil
        session.delete
      end
      
      # フォームによって挙動を変更します
      if cgi["mode"].respond_to?(:read) && cgi["action"].respond_to?(:read)
        params["mode"] = cgi["mode"].read
        params["action"] = cgi["action"].read
        Controller.MultiForm(cgi, session, params, db)
      else
        Controller.NormalForm(cgi, session, params, db)
      end
      
      # エラー画面移行時はセッション情報を破棄する
      if params["mode"] == "error"
        session["login"] = nil
        session.delete
      end
    end
    
    # ログイン失敗ユーザを記録
    if (session["login"] != "true" and (cgi["loginid"] != "" or cgi["password"] != ""))
      session["error"] = "ログインに失敗しました。ユーザIDまたはパスワードを確認して下さい。"
      logger.info "次のアクセスがログインに失敗しました。ログインID：#{cgi["loginid"]}, IPアドレス：#{cgi.remote_addr}"
    end
    
    # ビュー部分
    # modeとactionの相関図は以下のようになります。
    # [mode] + [action]
    #        + [action]
    #        + [action]
    #        + ... and more
    if session["login"] != "true"
      # セッションが存在しない場合は強制的にトップページに遷移
      htmlwriter = HtmlWriter.new("./erbtemp/index.html.erb", binding)
    else
      case params["mode"]
        when "selectlog"
        htmlwriter = HtmlWriter.new("./erbtemp/select.html.erb", binding)
        when "newentry"
        htmlwriter = HtmlWriter.new("./erbtemp/newentry.html.erb", binding)
        when "editentry"
        htmlwriter = HtmlWriter.new("./erbtemp/editentry.html.erb", binding)
        when "delentry"
        htmlwriter = HtmlWriter.new("./erbtemp/delentry.html.erb", binding)
        when "editfeed"
        htmlwriter = HtmlWriter.new("./erbtemp/editfeed.html.erb", binding)
        when "log"
        htmlwriter = HtmlWriter.new("./erbtemp/log.html.erb", binding)
        when "insert"
        htmlwriter = HtmlWriter.new("./erbtemp/insertfeed.html.erb", binding)
        when "replace"
        htmlwriter = HtmlWriter.new("./erbtemp/replacefeed.html.erb", binding)
        when "import"
        htmlwriter = HtmlWriter.new("./erbtemp/indeximport.html.erb", binding)
        when "reset"
        htmlwriter = HtmlWriter.new("./erbtemp/reset.html.erb", binding)
        when "error"
        htmlwriter = HtmlWriter.new("./erbtemp/error.html.erb", binding)
      else
        htmlwriter = HtmlWriter.new("./erbtemp/menu.html.erb", binding)
      end
    end
    
    # ビュー部分（インクルード用ファイル読み込み）
    htmlparts = {}
    # ヘッダタグ出力
    htmlparts['headtag'] = HtmlWriter.new("./erbtemp/parts_headtag.html.erb", binding).to_code
    htmlparts['headjs_switchpost'] = HtmlWriter.new("./erbtemp/parts_headjs_switchpost.html.erb", binding).to_code
    htmlparts['headjs_tag'] = HtmlWriter.new("./erbtemp/parts_headjs_tag.html.erb", binding).to_code
    htmlparts['menu'] = HtmlWriter.new("./erbtemp/parts_menu.html.erb", binding).to_code
    htmlparts['selectfile'] = HtmlWriter.new("./erbtemp/parts_selectfile.html.erb", binding).to_code
    # インフォメーションエリア
    htmlparts['infoarea'] = HtmlWriter.new("./erbtemp/parts_infoarea.html.erb", binding).to_code
    
    # 実際にHTMLを出力します
    cgi.out{htmlwriter.to_code}
    
  rescue => exception
    # ログに記録を実施します
    logger.error(exception.to_s)
    logger.error(exception.backtrace.join "\n")
    
    # エラーが発生した場合、それを画面に表示します
    htmlwriter = HtmlWriter.new("./erbtemp/exception.html.erb", binding)
    cgi.out{htmlwriter.to_code}
  end
end

begin
  main
rescue => evar
  # エラーが発生した場合、それを画面に表示します
  detail = ("%s: %s (%s)\n" %
  [evar.backtrace[0], evar.message, evar.send('class')]) +
  evar.backtrace[1..-1].join("\n")
  puts "content-type: text/html\n\n<plaintext>\n" + detail
end
