[前][次][番号順一覧][スレッド一覧]

ruby-changes:7751

From: xibbar <ko1@a...>
Date: Tue, 9 Sep 2008 22:10:11 +0900 (JST)
Subject: [ruby-changes:7751] Ruby:r19272 (trunk): * lib/cgi*: split cgi.rb into four files.

xibbar	2008-09-09 22:09:56 +0900 (Tue, 09 Sep 2008)

  New Revision: 19272

  http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=19272

  Log:
    * lib/cgi*: split cgi.rb into four files. [ruby-dev:36041]

  Added files:
    trunk/lib/cgi/cookie.rb
    trunk/lib/cgi/core.rb
    trunk/lib/cgi/html.rb
    trunk/lib/cgi/util.rb
  Modified files:
    trunk/ChangeLog
    trunk/lib/cgi.rb

Index: ChangeLog
===================================================================
--- ChangeLog	(revision 19271)
+++ ChangeLog	(revision 19272)
@@ -1,3 +1,7 @@
+Tue Sep  9 21:59:48 2008  Takeyuki Fujioka  <xibbar@r...>
+
+	* lib/cgi*: split cgi.rb into four files. [ruby-dev:36041]
+
 Tue Sep  9 21:17:00 2008  Tanaka Akira  <akr@f...>
 
 	* include/ruby/encoding.h (ECONV_DECORATOR_MASK): merge
Index: lib/cgi.rb
===================================================================
--- lib/cgi.rb	(revision 19271)
+++ lib/cgi.rb	(revision 19272)
@@ -30,10 +30,8 @@
 # See http://www.w3.org/CGI/ for more information on the CGI
 # protocol.
 
-raise "Please, use ruby 1.5.4 or later." if RUBY_VERSION < "1.5.4"
+raise "Please, use ruby 1.9.0 or later." if RUBY_VERSION < "1.9.0"
 
-require 'English'
-
 # CGI class.  See documentation for the file cgi.rb for an overview
 # of the CGI protocol.
 #
@@ -271,2060 +269,6 @@
 #   CGI.new("html4Tr")  # html4.01 Transitional
 #   CGI.new("html4Fr")  # html4.01 Frameset
 #
-class CGI
-
-  # :stopdoc:
-
-  # String for carriage return
-  CR  = "\015"
-
-  # String for linefeed
-  LF  = "\012"
-
-  # Standard internet newline sequence
-  EOL = CR + LF
-
-  REVISION = '$Id$' #:nodoc:
-
-  NEEDS_BINMODE = true if /WIN/i.match(RUBY_PLATFORM) 
-
-  # Path separators in different environments.
-  PATH_SEPARATOR = {'UNIX'=>'/', 'WINDOWS'=>'\\', 'MACINTOSH'=>':'}
-
-  # HTTP status codes.
-  HTTP_STATUS = {
-    "OK"                  => "200 OK",
-    "PARTIAL_CONTENT"     => "206 Partial Content",
-    "MULTIPLE_CHOICES"    => "300 Multiple Choices",
-    "MOVED"               => "301 Moved Permanently",
-    "REDIRECT"            => "302 Found",
-    "NOT_MODIFIED"        => "304 Not Modified",
-    "BAD_REQUEST"         => "400 Bad Request",
-    "AUTH_REQUIRED"       => "401 Authorization Required",
-    "FORBIDDEN"           => "403 Forbidden",
-    "NOT_FOUND"           => "404 Not Found",
-    "METHOD_NOT_ALLOWED"  => "405 Method Not Allowed",
-    "NOT_ACCEPTABLE"      => "406 Not Acceptable",
-    "LENGTH_REQUIRED"     => "411 Length Required",
-    "PRECONDITION_FAILED" => "412 Rrecondition Failed",
-    "SERVER_ERROR"        => "500 Internal Server Error",
-    "NOT_IMPLEMENTED"     => "501 Method Not Implemented",
-    "BAD_GATEWAY"         => "502 Bad Gateway",
-    "VARIANT_ALSO_VARIES" => "506 Variant Also Negotiates"
-  }
-
-  # Abbreviated day-of-week names specified by RFC 822
-  RFC822_DAYS = %w[ Sun Mon Tue Wed Thu Fri Sat ]
-
-  # Abbreviated month names specified by RFC 822
-  RFC822_MONTHS = %w[ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ]
-
-  # :startdoc:
-
-  def env_table 
-    ENV
-  end
-
-  def stdinput
-    $stdin
-  end
-
-  def stdoutput
-    $DEFAULT_OUTPUT
-  end
-
-  private :env_table, :stdinput, :stdoutput
-
-  # URL-encode a string.
-  #   url_encoded_string = CGI::escape("'Stop!' said Fred")
-  #      # => "%27Stop%21%27+said+Fred"
-  def CGI::escape(string)
-    string.gsub(/([^ a-zA-Z0-9_.-]+)/) do
-      '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
-    end.tr(' ', '+')
-  end
-
-
-  # URL-decode a string.
-  #   string = CGI::unescape("%27Stop%21%27+said+Fred")
-  #      # => "'Stop!' said Fred"
-  def CGI::unescape(string)
-    enc = string.encoding
-    string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/) do
-      [$1.delete('%')].pack('H*').force_encoding(enc)
-    end
-  end
-
-  TABLE_FOR_ESCAPE_HTML__ = {
-    '&' => '&amp;',
-    '"' => '&quot;',
-    '<' => '&lt;',
-    '>' => '&gt;',
-  }
-
-  # Escape special characters in HTML, namely &\"<>
-  #   CGI::escapeHTML('Usage: foo "bar" <baz>')
-  #      # => "Usage: foo &quot;bar&quot; &lt;baz&gt;"
-  def CGI::escapeHTML(string)
-    string.gsub(/[&\"<>]/, TABLE_FOR_ESCAPE_HTML__)
-  end
-
-
-  # Unescape a string that has been HTML-escaped
-  #   CGI::unescapeHTML("Usage: foo &quot;bar&quot; &lt;baz&gt;")
-  #      # => "Usage: foo \"bar\" <baz>"
-  def CGI::unescapeHTML(string)
-    enc = string.encoding
-    if [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE].include?(enc)
-      return string.gsub(Regexp.new('&(amp|quot|gt|lt|#[0-9]+|#x[0-9A-Fa-f]+);'.encode(enc))) do
-	case $1.encode("US-ASCII")
-	when 'amp'                 then '&'.encode(enc)
-	when 'quot'                then '"'.encode(enc)
-	when 'gt'                  then '>'.encode(enc)
-	when 'lt'                  then '<'.encode(enc)
-	when /\A#0*(\d+)\z/        then $1.to_i.chr(enc)
-	when /\A#x([0-9a-f]+)\z/i  then $1.hex.chr(enc)
-	end
-      end
-    end
-    asciicompat = Encoding.compatible?(string, "a")
-    string.gsub(/&(amp|quot|gt|lt|\#[0-9]+|\#x[0-9A-Fa-f]+);/) do
-      match = $1.dup
-      case match
-      when 'amp'                 then '&'
-      when 'quot'                then '"'
-      when 'gt'                  then '>'
-      when 'lt'                  then '<'
-      when /\A#0*(\d+)\z/
-        n = $1.to_i
-	if enc == Encoding::UTF_8 or
-          enc == Encoding::ISO_8859_1 && n < 256 or
-          asciicompat && n < 128
-	  n.chr(enc)
-	else
-	  "&##{$1};"
-	end
-      when /\A#x([0-9a-f]+)\z/i
-        n = $1.hex
-	if enc == Encoding::UTF_8 or
-          enc == Encoding::ISO_8859_1 && n < 256 or
-          asciicompat && n < 128
-	  n.chr(enc)
-	else
-	  "&#x#{$1};"
-	end
-      else
-	"&#{match};"
-      end
-    end
-  end
-  def CGI::escape_html(str)
-    escapeHTML(str)
-  end
-  def CGI::unescape_html(str)
-    unescapeHTML(str)
-  end
-
-  # Escape only the tags of certain HTML elements in +string+.
-  #
-  # Takes an element or elements or array of elements.  Each element
-  # is specified by the name of the element, without angle brackets.
-  # This matches both the start and the end tag of that element.
-  # The attribute list of the open tag will also be escaped (for
-  # instance, the double-quotes surrounding attribute values).
-  #
-  #   print CGI::escapeElement('<BR><A HREF="url"></A>', "A", "IMG")
-  #     # "<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt"
-  #
-  #   print CGI::escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"])
-  #     # "<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt"
-  def CGI::escapeElement(string, *elements)
-    elements = elements[0] if elements[0].kind_of?(Array)
-    unless elements.empty?
-      string.gsub(/<\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?>/i) do
-        CGI::escapeHTML($&)
-      end
-    else
-      string
-    end
-  end
-
-
-  # Undo escaping such as that done by CGI::escapeElement()
-  #
-  #   print CGI::unescapeElement(
-  #           CGI::escapeHTML('<BR><A HREF="url"></A>'), "A", "IMG")
-  #     # "&lt;BR&gt;<A HREF="url"></A>"
-  # 
-  #   print CGI::unescapeElement(
-  #           CGI::escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"])
-  #     # "&lt;BR&gt;<A HREF="url"></A>"
-  def CGI::unescapeElement(string, *elements)
-    elements = elements[0] if elements[0].kind_of?(Array)
-    unless elements.empty?
-      string.gsub(/&lt;\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?&gt;/i) do
-        CGI::unescapeHTML($&)
-      end
-    else
-      string
-    end
-  end
-  def CGI::escape_element(str)
-    escapeElement(str)
-  end
-  def CGI::unescape_element(str)
-    unescapeElement(str)
-  end
-
-  # Format a +Time+ object as a String using the format specified by RFC 1123.
-  #
-  #   CGI::rfc1123_date(Time.now)
-  #     # Sat, 01 Jan 2000 00:00:00 GMT
-  def CGI::rfc1123_date(time)
-    t = time.clone.gmtime
-    return format("%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
-                RFC822_DAYS[t.wday], t.day, RFC822_MONTHS[t.month-1], t.year,
-                t.hour, t.min, t.sec)
-  end
-
-
-  # Create an HTTP header block as a string.
-  #
-  # Includes the empty line that ends the header block.
-  #
-  # +options+ can be a string specifying the Content-Type (defaults
-  # to text/html), or a hash of header key/value pairs.  The following
-  # header keys are recognized:
-  #
-  # type:: the Content-Type header.  Defaults to "text/html"
-  # charset:: the charset of the body, appended to the Content-Type header.
-  # nph:: a boolean value.  If true, prepend protocol string and status code, and
-  #       date; and sets default values for "server" and "connection" if not
-  #       explicitly set.
-  # status:: the HTTP status code, returned as the Status header.  See the
-  #          list of available status codes below.
-  # server:: the server software, returned as the Server header.
-  # connection:: the connection type, returned as the Connection header (for 
-  #              instance, "close".
-  # length:: the length of the content that will be sent, returned as the
-  #          Content-Length header.
-  # language:: the language of the content, returned as the Content-Language
-  #            header.
-  # expires:: the time on which the current content expires, as a +Time+
-  #           object, returned as the Expires header.
-  # cookie:: a cookie or cookies, returned as one or more Set-Cookie headers.
-  #          The value can be the literal string of the cookie; a CGI::Cookie
-  #          object; an Array of literal cookie strings or Cookie objects; or a 
-  #          hash all of whose values are literal cookie strings or Cookie objects.
-  #          These cookies are in addition to the cookies held in the
-  #          @output_cookies field.
-  #
-  # Other header lines can also be set; they are appended as key: value.
-  # 
-  #   header
-  #     # Content-Type: text/html
-  # 
-  #   header("text/plain")
-  #     # Content-Type: text/plain
-  # 
-  #   header("nph"        => true,
-  #          "status"     => "OK",  # == "200 OK"
-  #            # "status"     => "200 GOOD",
-  #          "server"     => ENV['SERVER_SOFTWARE'],
-  #          "connection" => "close",
-  #          "type"       => "text/html",
-  #          "charset"    => "iso-2022-jp",
-  #            # Content-Type: text/html; charset=iso-2022-jp
-  #          "length"     => 103,
-  #          "language"   => "ja",
-  #          "expires"    => Time.now + 30,
-  #          "cookie"     => [cookie1, cookie2],
-  #          "my_header1" => "my_value"
-  #          "my_header2" => "my_value")
-  # 
-  # The status codes are:
-  # 
-  #   "OK"                  --> "200 OK"
-  #   "PARTIAL_CONTENT"     --> "206 Partial Content"
-  #   "MULTIPLE_CHOICES"    --> "300 Multiple Choices"
-  #   "MOVED"               --> "301 Moved Permanently"
-  #   "REDIRECT"            --> "302 Found"
-  #   "NOT_MODIFIED"        --> "304 Not Modified"
-  #   "BAD_REQUEST"         --> "400 Bad Request"
-  #   "AUTH_REQUIRED"       --> "401 Authorization Required"
-  #   "FORBIDDEN"           --> "403 Forbidden"
-  #   "NOT_FOUND"           --> "404 Not Found"
-  #   "METHOD_NOT_ALLOWED"  --> "405 Method Not Allowed"
-  #   "NOT_ACCEPTABLE"      --> "406 Not Acceptable"
-  #   "LENGTH_REQUIRED"     --> "411 Length Required"
-  #   "PRECONDITION_FAILED" --> "412 Precondition Failed"
-  #   "SERVER_ERROR"        --> "500 Internal Server Error"
-  #   "NOT_IMPLEMENTED"     --> "501 Method Not Implemented"
-  #   "BAD_GATEWAY"         --> "502 Bad Gateway"
-  #   "VARIANT_ALSO_VARIES" --> "506 Variant Also Negotiates"
-  # 
-  # This method does not perform charset conversion. 
-  #
-  def header(options = "text/html")
-
-    buf = ""
-
-    case options
-    when String
-      options = { "type" => options }
-    when Hash
-      options = options.dup
-    end
-
-    unless options.has_key?("type")
-      options["type"] = "text/html"
-    end
-
-    if options.has_key?("charset")
-      options["type"] += "; charset=" + options.delete("charset")
-    end
-
-    options.delete("nph") if defined?(MOD_RUBY)
-    if options.delete("nph") or
-        (/IIS\/(\d+)/.match(env_table['SERVER_SOFTWARE']) and $1.to_i < 5)
-      buf += (env_table["SERVER_PROTOCOL"] or "HTTP/1.0")  + " " +
-             (HTTP_STATUS[options["status"]] or options["status"] or "200 OK") +
-             EOL +
-             "Date: " + CGI::rfc1123_date(Time.now) + EOL
-
-      unless options.has_key?("server")
-        options["server"] = (env_table['SERVER_SOFTWARE'] or "")
-      end
-
-      unless options.has_key?("connection")
-        options["connection"] = "close"
-      end
-
-      options.delete("status")
-    end
-
-    if options.has_key?("status")
-      buf += "Status: " +
-             (HTTP_STATUS[options["status"]] or options["status"]) + EOL
-      options.delete("status")
-    end
-
-    if options.has_key?("server")
-      buf += "Server: " + options.delete("server") + EOL
-    end
-
-    if options.has_key?("connection")
-      buf += "Connection: " + options.delete("connection") + EOL
-    end
-
-    buf += "Content-Type: " + options.delete("type") + EOL
-
-    if options.has_key?("length")
-      buf += "Content-Length: " + options.delete("length").to_s + EOL
-    end
-
-    if options.has_key?("language")
-      buf += "Content-Language: " + options.delete("language") + EOL
-    end
-
-    if options.has_key?("expires")
-      buf += "Expires: " + CGI::rfc1123_date( options.delete("expires") ) + EOL
-    end
-
-    if options.has_key?("cookie")
-      if options["cookie"].kind_of?(String) or
-           options["cookie"].kind_of?(Cookie)
-        buf += "Set-Cookie: " + options.delete("cookie").to_s + EOL
-      elsif options["cookie"].kind_of?(Array)
-        options.delete("cookie").each{|cookie|
-          buf += "Set-Cookie: " + cookie.to_s + EOL
-        }
-      elsif options["cookie"].kind_of?(Hash)
-        options.delete("cookie").each_value{|cookie|
-          buf += "Set-Cookie: " + cookie.to_s + EOL
-        }
-      end
-    end
-    if @output_cookies
-      for cookie in @output_cookies
-        buf += "Set-Cookie: " + cookie.to_s + EOL
-      end
-    end
-
-    options.each{|key, value|
-      buf += key + ": " + value.to_s + EOL
-    }
-
-    if defined?(MOD_RUBY)
-      table = Apache::request.headers_out
-      buf.scan(/([^:]+): (.+)#{EOL}/){ |name, value|
-        warn sprintf("name:%s value:%s\n", name, value) if $DEBUG
-        case name
-        when 'Set-Cookie'
-          table.add(name, value)
-        when /^status$/i
-          Apache::request.status_line = value
-          Apache::request.status = value.to_i
-        when /^content-type$/i
-          Apache::request.content_type = value
-        when /^content-encoding$/i
-          Apache::request.content_encoding = value
-        when /^location$/i
-	  if Apache::request.status == 200
-	    Apache::request.status = 302
-	  end
-          Apache::request.headers_out[name] = value
-        else
-          Apache::request.headers_out[name] = value
-        end
-      }
-      Apache::request.send_http_header
-      ''
-    else
-      buf + EOL
-    end
-
-  end # header()
-
-
-  # Print an HTTP header and body to $DEFAULT_OUTPUT ($>)
-  #
-  # The header is provided by +options+, as for #header().
-  # The body of the document is that returned by the passed-
-  # in block.  This block takes no arguments.  It is required.
-  #
-  #   cgi = CGI.new
-  #   cgi.out{ "string" }
-  #     # Content-Type: text/html
-  #     # Content-Length: 6
-  #     #
-  #     # string
-  # 
-  #   cgi.out("text/plain") { "string" }
-  #     # Content-Type: text/plain
-  #     # Content-Length: 6
-  #     #
-  #     # string
-  # 
-  #   cgi.out("nph"        => true,
-  #           "status"     => "OK",  # == "200 OK"
-  #           "server"     => ENV['SERVER_SOFTWARE'],
-  #           "connection" => "close",
-  #           "type"       => "text/html",
-  #           "charset"    => "iso-2022-jp",
-  #             # Content-Type: text/html; charset=iso-2022-jp
-  #           "language"   => "ja",
-  #           "expires"    => Time.now + (3600 * 24 * 30),
-  #           "cookie"     => [cookie1, cookie2],
-  #           "my_header1" => "my_value",
-  #           "my_header2" => "my_value") { "string" }
-  # 
-  # Content-Length is automatically calculated from the size of
-  # the String returned by the content block.
-  #
-  # If ENV['REQUEST_METHOD'] == "HEAD", then only the header
-  # is outputted (the content block is still required, but it
-  # is ignored).
-  # 
-  # If the charset is "iso-2022-jp" or "euc-jp" or "shift_jis" then
-  # the content is converted to this charset, and the language is set 
-  # to "ja".
-  def out(options = "text/html") # :yield:
-
-    options = { "type" => options } if options.kind_of?(String)
-    content = yield
-    options["length"] = content.bytesize.to_s
-    output = stdoutput
-    output.binmode if defined? output.binmode
-    output.print header(options)
-    output.print content unless "HEAD" == env_table['REQUEST_METHOD']
-  end
-
-
-  # Print an argument or list of arguments to the default output stream
-  #
-  #   cgi = CGI.new
-  #   cgi.print    # default:  cgi.print == $DEFAULT_OUTPUT.print
-  def print(*options)
-    stdoutput.print(*options)
-  end
-
-  # Class representing an HTTP cookie.
-  #
-  # In addition to its specific fields and methods, a Cookie instance
-  # is a delegator to the array of its values.
-  #
-  # See RFC 2965.
-  #
-  # == Examples of use
-  #   cookie1 = CGI::Cookie::new("name", "value1", "value2", ...)
-  #   cookie1 = CGI::Cookie::new("name" => "name", "value" => "value")
-  #   cookie1 = CGI::Cookie::new('name'    => 'name',
-  #                              'value'   => ['value1', 'value2', ...],
-  #                              'path'    => 'path',   # optional
-  #                              'domain'  => 'domain', # optional
-  #                              'expires' => Time.now, # optional
-  #                              'secure'  => true      # optional
-  #                             )
-  # 
-  #   cgi.out("cookie" => [cookie1, cookie2]) { "string" }
-  # 
-  #   name    = cookie1.name
-  #   values  = cookie1.value
-  #   path    = cookie1.path
-  #   domain  = cookie1.domain
-  #   expires = cookie1.expires
-  #   secure  = cookie1.secure
-  # 
-  #   cookie1.name    = 'name'
-  #   cookie1.value   = ['value1', 'value2', ...]
-  #   cookie1.path    = 'path'
-  #   cookie1.domain  = 'domain'
-  #   cookie1.expires = Time.now + 30
-  #   cookie1.secure  = true
-  class Cookie < Array
-
-    # Create a new CGI::Cookie object.
-    #
-    # The contents of the cookie can be specified as a +name+ and one
-    # or more +value+ arguments.  Alternatively, the contents can
-    # be specified as a single hash argument.  The possible keywords of
-    # this hash are as follows:
-    #
-    # name:: the name of the cookie.  Required.
-    # value:: the cookie's value or list of values.
-    # path:: the path for which this cookie applies.  Defaults to the
-    #        base directory of the CGI script.
-    # domain:: the domain for which this cookie applies.
-    # expires:: the time at which this cookie expires, as a +Time+ object.
-    # secure:: whether this cookie is a secure cookie or not (default to
-    #          false).  Secure cookies are only transmitted to HTTPS 
-    #          servers.
-    #
-    # These keywords correspond to attributes of the cookie object.
-    def initialize(name = "", *value)
-      if name.kind_of?(String)
-        @name = name
-        @value = value
-        %r|^(.*/)|.match(ENV["SCRIPT_NAME"])
-        @path = ($1 or "")
-        @secure = false
-        return super(@value)
-      end
-
-      options = name
-      unless options.has_key?("name")
-        raise ArgumentError, "`name' required"
-      end
-
-      @name = options["name"]
-      @value = Array(options["value"])
-      # simple support for IE
-      if options["path"]
-        @path = options["path"]
-      else
-        %r|^(.*/)|.match(ENV["SCRIPT_NAME"])
-        @path = ($1 or "")
-      end
-      @domain = options["domain"]
-      @expires = options["expires"]
-      @secure = options["secure"] == true ? true : false
-
-      super(@value)
-    end
-
-    attr_accessor("name", "value", "path", "domain", "expires")
-    attr_reader("secure")
-
-    # Set whether the Cookie is a secure cookie or not.
-    #
-    # +val+ must be a boolean.
-    def secure=(val)
-      @secure = val if val == true or val == false
-      @secure
-    end
-
-    # Convert the Cookie to its string representation.
-    def to_s
-      buf = ""
-      buf += @name + '='
-
-      if @value.kind_of?(String)
-        buf += CGI::escape(@value)
-      else
-        buf += @value.collect{|v| CGI::escape(v) }.join("&")
-      end
-
-      if @domain
-        buf += '; domain=' + @domain
-      end
-
-      if @path
-        buf += '; path=' + @path
-      end
-
-      if @expires
-        buf += '; expires=' + CGI::rfc1123_date(@expires)
-      end
-
-      if @secure == true
-        buf += '; secure'
-      end
-
-      buf
-    end
-
-  end # class Cookie
-
-
-  # Parse a raw cookie string into a hash of cookie-name=>Cookie
-  # pairs.
-  #
-  #   cookies = CGI::Cookie::parse("raw_cookie_string")
-  #     # { "name1" => cookie1, "name2" => cookie2, ... }
-  #
-  def Cookie::parse(raw_cookie)
-    cookies = Hash.new([])
-    return cookies unless raw_cookie
-
-    raw_cookie.split(/[;,]\s?/).each do |pairs|
-      name, values = pairs.split('=',2)
-      next unless name and values
-      name = CGI::unescape(name)
-      values ||= ""
-      values = values.split('&').collect{|v| CGI::unescape(v) }
-      if cookies.has_key?(name)
-        values = cookies[name].value + values
-      end
-      cookies[name] = Cookie::new(name, *values)
-    end
-
-    cookies
-  end
-
-  # Parse an HTTP query string into a hash of key=>value pairs.
-  #
-  #   params = CGI::parse("query_string")
-  #     # {"name1" => ["value1", "value2", ...],
-  #     #  "name2" => ["value1", "value2", ...], ... }
-  #
-  def CGI::parse(query)
-    params = Hash.new([].freeze)
-
-    query.split(/[&;]/).each do |pairs|
-      key, value = pairs.split('=',2).collect{|v| CGI::unescape(v) }
-      if params.has_key?(key)
-        params[key].push(value)
-      else
-        params[key] = [value]
-      end
-    end
-
-    params
-  end
-
-  # Mixin module. It provides the follow functionality groups:
-  #
-  # 1. Access to CGI environment variables as methods.  See 
-  #    documentation to the CGI class for a list of these variables.
-  #
-  # 2. Access to cookies, including the cookies attribute.
-  #
-  # 3. Access to parameters, including the params attribute, and overloading
-  #    [] to perform parameter value lookup by key.
-  #
-  # 4. The initialize_query method, for initialising the above
-  #    mechanisms, handling multipart forms, and allowing the
-  #    class to be used in "offline" mode.
-  #
-  module QueryExtension
-
-    %w[ CONTENT_LENGTH SERVER_PORT ].each do |env|
-      define_method(env.sub(/^HTTP_/, '').downcase) do
-        (val = env_table[env]) && Integer(val)
-      end
-    end
-
-    %w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO
-        PATH_TRANSLATED QUERY_STRING REMOTE_ADDR REMOTE_HOST
-        REMOTE_IDENT REMOTE_USER REQUEST_METHOD SCRIPT_NAME
-        SERVER_NAME SERVER_PROTOCOL SERVER_SOFTWARE
-
-        HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
-        HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST
-        HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
-      define_method(env.sub(/^HTTP_/, '').downcase) do
-        env_table[env]
-      end
-    end
-
-    # Get the raw cookies as a string.
-    def raw_cookie
-      env_table["HTTP_COOKIE"]
-    end
-
-    # Get the raw RFC2965 cookies as a string.
-    def raw_cookie2
-      env_table["HTTP_COOKIE2"]
-    end
-
-    # Get the cookies as a hash of cookie-name=>Cookie pairs.
-    attr_accessor :cookies
-
-    # Get the parameters as a hash of name=>values pairs, where
-    # values is an Array.
-    attr_reader :params
-
-    # Set all the parameters.
-    def params=(hash)
-      @params.clear
-      @params.update(hash)
-    end
-
-    def read_multipart(boundary, content_length)
-      params = Hash.new([])
-      boundary = "--" + boundary
-      quoted_boundary = Regexp.quote(boundary)
-      buf = ""
-      bufsize = 10 * 1024
-      boundary_end=""
-
-      # start multipart/form-data
-      stdinput.binmode if defined? stdinput.binmode
-      boundary_size = boundary.bytesize + EOL.bytesize
-      content_length -= boundary_size
-      status = stdinput.read(boundary_size)
-      if nil == status
-        raise EOFError, "no content body"
-      elsif boundary + EOL != status
-        raise EOFError, "bad content body"
-      end
-
-      loop do
-        head = nil
-        body = MorphingBody.new
-
-        until head and /#{quoted_boundary}(?:#{EOL}|--)/.match(buf)
-          if (not head) and /#{EOL}#{EOL}/.match(buf)
-            buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/) do
-              head = $1.dup
-              ""
-            end
-            next
-          end
-
-          if head and ( (EOL + boundary + EOL).bytesize < buf.bytesize )
-            body.print buf[0 ... (buf.bytesize - (EOL + boundary + EOL).bytesize)]
-            buf[0 ... (buf.bytesize - (EOL + boundary + EOL).bytesize)] = ""
-          end
-
-          c = if bufsize < content_length
-                stdinput.read(bufsize)
-              else
-                stdinput.read(content_length)
-              end
-          if c.nil? || c.empty?
-            raise EOFError, "bad content body"
-          end
-          buf.concat(c)
-          content_length -= c.bytesize
-        end
-
-        buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/) do
-          body.print $1
-          if "--" == $2
-            content_length = -1
-          end
-          boundary_end = $2.dup
-          ""
-        end
-
-        body.rewind
-
-        /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;\s]*))/i.match(head)
-	filename = ($1 or $2 or "")
-	if /Mac/i.match(env_table['HTTP_USER_AGENT']) and
-	    /Mozilla/i.match(env_table['HTTP_USER_AGENT']) and
-	    (not /MSIE/i.match(env_table['HTTP_USER_AGENT']))
-	  filename = CGI::unescape(filename)
-	end
-        
-        /Content-Type: ([^\s]*)/i.match(head)
-        content_type = ($1 or "")
-
-        (class << body; self; end).class_eval do
-          alias local_path path
-          define_method(:original_filename) {filename.dup.taint}
-          define_method(:content_type) {content_type.dup.taint}
-        end
-
-        /Content-Disposition:.* name="?([^\";\s]*)"?/i.match(head)
-        name = ($1 || "").dup
-
-        if params.has_key?(name)
-          params[name].push(body)
-        else
-          params[name] = [body]
-        end
-        break if buf.bytesize == 0
-        break if content_length == -1
-      end
-      raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
-
-      params
-    end # read_multipart
-    private :read_multipart
-
-    # offline mode. read name=value pairs on standard input.
-    def read_from_cmdline
-      require "shellwords"
-
-      string = unless ARGV.empty?
-        ARGV.join(' ')
-      else
-        if STDIN.tty?
-          STDERR.print(
-            %|(offline mode: enter name=value pairs on standard input)\n|
-          )
-        end
-        readlines.join(' ').gsub(/\n/, '')
-      end.gsub(/\\=/, '%3D').gsub(/\\&/, '%26')
-
-      words = Shellwords.shellwords(string)
-
-      if words.find{|x| /=/.match(x) }
-        words.join('&')
-      else
-        words.join('+')
-      end
-    end
-    private :read_from_cmdline
-
-    # A wrapper class to use a StringIO object as the body and switch
-    # to a TempFile when the passed threshold is passed.
-    class MorphingBody
-      begin
-        require "stringio"
-        @@small_buffer = lambda{StringIO.new}
-      rescue LoadError
-        require "tempfile"
-        @@small_buffer = lambda{
-          n = Tempfile.new("CGI")
-          n.binmode
-          n
-        }
-      end
-
-      def initialize(morph_threshold = 10240)
-        @threshold = morph_threshold
-        @body = @@small_buffer.call
-        @cur_size = 0
-        @morph_check = true
-      end
-
-      def print(data)
-        if @morph_check && (@cur_size + data.bytesize > @threshold)
-          convert_body
-        end
-        @body.print data
-      end
-      def rewind
-        @body.rewind
-      end
-      def path
-        @body.path
-      end
-
-      # returns the true body object.
-      def extract
-        @body
-      end
-
-      private
-      def convert_body
-        new_body = TempFile.new("CGI")
-        new_body.binmode if defined? @body.binmode
-        new_body.binmode if defined? new_body.binmode
-
-        @body.rewind
-        new_body.print @body.read
-        @body = new_body
-        @morph_check = false
-      end
-    end
-
-    # Initialize the data from the query.
-    #
-    # Handles multipart forms (in particular, forms that involve file uploads).
-    # Reads query parameters in the @params field, and cookies into @cookies.
-    def initialize_query()
-      if ("POST" == env_table['REQUEST_METHOD']) and
-         %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|.match(env_table['CONTENT_TYPE'])
-        boundary = $1.dup
-        @multipart = true
-        @params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH']))
-      else
-        @multipart = false
-        @params = CGI::parse(
-                    case env_table['REQUEST_METHOD']
-                    when "GET", "HEAD"
-                      if defined?(MOD_RUBY)
-                        Apache::request.args or ""
-                      else
-                        env_table['QUERY_STRING'] or ""
-                      end
-                    when "POST"
-                      stdinput.binmode if defined? stdinput.binmode
-                      stdinput.read(Integer(env_table['CONTENT_LENGTH'])) or ''
-                    else
-                      read_from_cmdline
-                    end
-                  )
-      end
-
-      @cookies = CGI::Cookie::parse((env_table['HTTP_COOKIE'] or env_table['COOKIE']))
-    end
-    private :initialize_query
-
-    def multipart?
-      @multipart
-    end
-
-    # Get the value for the parameter with a given key.
-    #
-    # If the parameter has multiple values, only the first will be 
-    # retrieved; use #params() to get the array of values.
-    def [](key)
-      params = @params[key]
-      return '' unless params
-      value = params[0]
-      if @multipart
-        if value
-          return value
-        elsif defined? StringIO
-          StringIO.new("")
-        else
-          Tempfile.new("CGI")
-        end
-      else
-        str = if value then value.dup else "" end
-        str
-      end
-    end
-
-    # Return all parameter keys as an array.
-    def keys(*args)
-      @params.keys(*args)
-    end
-
-    # Returns true if a given parameter key exists in the query.
-    def has_key?(*args)
-      @params.has_key?(*args)
-    end
-    alias key? has_key?
-    alias include? has_key?
-
-  end # QueryExtension
-
-
-  # Prettify (indent) an HTML string.
-  #
-  # +string+ is the HTML string to indent.  +shift+ is the indentation
-  # unit to use; it defaults to two spaces.
-  #
-  #   print CGI::pretty("<HTML><BODY></BODY></HTML>")
-  #     # <HTML>
-  #     #   <BODY>
-  #     #   </BODY>
-  #     # </HTML>
-  # 
-  #   print CGI::pretty("<HTML><BODY></BODY></HTML>", "\t")
-  #     # <HTML>
-  #     #         <BODY>
-  #     #         </BODY>
-  #     # </HTML>
-  #
-  def CGI::pretty(string, shift = "  ")
-    lines = string.gsub(/(?!\A)<(?:.|\n)*?>/, "\n\\0").gsub(/<(?:.|\n)*?>(?!\n)/, "\\0\n")
-    end_pos = 0
-    while end_pos = lines.index(/^<\/(\w+)/, end_pos)
-      element = $1.dup
-      start_pos = lines.rindex(/^\s*<#{element}/i, end_pos)
-      lines[start_pos ... end_pos] = "__" + lines[start_pos ... end_pos].gsub(/\n(?!\z)/, "\n" + shift) + "__"
-    end
-    lines.gsub(/^((?:#{Regexp::quote(shift)})*)__(?=<\/?\w)/, '\1')
-  end
-
-
-  # Base module for HTML-generation mixins.
-  #
-  # Provides methods for code generation for tags following
-  # the various DTD element types.
-  module TagMaker # :nodoc:
-
-    # Generate code for an element with required start and end tags.
-    #
-    #   - -
-    def nn_element_def(element)
-      nOE_element_def(element, <<-END)
-          if block_given?
-            yield.to_s
-          else
-            ""
-          end +
-          "</#{element.upcase}>"
-      END
-    end
-
-    # Generate code for an empty element.
-    #
-    #   - O EMPTY
-    def nOE_element_def(element, append = nil)
-      s = <<-END
-          attributes={attributes=>nil} if attributes.kind_of?(String)
-          "<#{element.upcase}" + attributes.collect{|name, value|
-            next unless value
-            " " + CGI::escapeHTML(name) +
-            if true == value
-              ""
-            else
-              '="' + CGI::escapeHTML(value) + '"'
-            end
-          }.join + ">"
-      END
-      s.sub!(/\Z/, " +") << append if append
-      s
-    end
-
-    # Generate code for an element for which the end (and possibly the
-    # start) tag is optional.
-    #
-    #   O O or - O
-    def nO_element_def(element)
-      nOE_element_def(element, <<-END)
-          if block_given?
-            yield.to_s + "</#{element.upcase}>"
-          else
-            ""
-          end
-      END
-    end
-
-  end # TagMaker
-
-
-  #
-  # Mixin module providing HTML generation methods.
-  #
-  # For example,
-  #   cgi.a("http://www.example.com") { "Example" }
-  #     # => "<A HREF=\"http://www.example.com\">Example</A>"
-  #
-  # Modules Http3, Http4, etc., contain more basic HTML-generation methods
-  # (:title, :center, etc.).
-  #
-  # See class CGI for a detailed example. 
-  #
-  module HtmlExtension
-
-
-    # Generate an Anchor element as a string.
-    #
-    # +href+ can either be a string, giving the URL
-    # for the HREF attribute, or it can be a hash of
-    # the element's attributes.
-    #
-    # The body of the element is the string returned by the no-argument
-    # block passed in.
-    #
-    #   a("http://www.example.com") { "Example" }
-    #     # => "<A HREF=\"http://www.example.com\">Example</A>"
-    #
-    #   a("HREF" => "http://www.example.com", "TARGET" => "_top") { "Example" }
-    #     # => "<A HREF=\"http://www.example.com\" TARGET=\"_top\">Example</A>"
-    #
-    def a(href = "") # :yield:
-      attributes = if href.kind_of?(String)
-                     { "HREF" => href }
-                   else
-                     href
-                   end
-      if block_given?
-        super(attributes){ yield }
-      else
-        super(attributes)
-      end
-    end
-
-    # Generate a Document Base URI element as a String. 
-    #
-    # +href+ can either by a string, giving the base URL for the HREF
-    # attribute, or it can be a has of the element's attributes.
-    #
-    # The passed-in no-argument block is ignored.
-    #
-    #   base("http://www.example.com/cgi")
-    #     # => "<BASE HREF=\"http://www.example.com/cgi\">"
-    def base(href = "") # :yield:
-      attributes = if href.kind_of?(String)
-                     { "HREF" => href }
-                   else
-                     href
-                   end
-      if block_given?
-        super(attributes){ yield }
-      else
-        super(attributes)
-      end
-    end
-
-    # Generate a BlockQuote element as a string.
-    #
-    # +cite+ can either be a string, give the URI for the source of
-    # the quoted text, or a hash, giving all attributes of the element,
-    # or it can be omitted, in which case the element has no attributes.
-    #
-    # The body is provided by the passed-in no-argument block
-    #
-    #   blockquote("http://www.example.com/quotes/foo.html") { "Foo!" }
-    #     #=> "<BLOCKQUOTE CITE=\"http://www.example.com/quotes/foo.html\">Foo!</BLOCKQUOTE>
-    def blockquote(cite = {})  # :yield:
-      attributes = if cite.kind_of?(String)
-                     { "CITE" => cite }
-                   else
-                     cite
-                   end
-      if block_given?
-        super(attributes){ yield }
-      else
-        super(attributes)
-      end
-    end
-
-
-    # Generate a Table Caption element as a string.
-    #
-    # +align+ can be a string, giving the alignment of the caption
-    # (one of top, bottom, left, or right).  It can be a hash of
-    # all the attributes of the element.  Or it can be omitted.
-    #
-    # The body of the element is provided by the passed-in no-argument block.
-    #
-    #   caption("left") { "Capital Cities" }
-    #     # => <CAPTION ALIGN=\"left\">Capital Cities</CAPTION>
-    def caption(align = {}) # :yield:
-      attributes = if align.kind_of?(String)
-                     { "ALIGN" => align }
-                   else
-                     align
-                   end
-      if block_given?
-        super(attributes){ yield }
-      else
-        super(attributes)
-      end
-    end
-
-
-    # Generate a Checkbox Input element as a string.
-    #
-    # The attributes of the element can be specified as three arguments,
-    # +name+, +value+, and +checked+.  +checked+ is a boolean value;
-    # if true, the CHECKED attribute will be included in the element.
-    #
-    # Alternatively, the attributes can be specified as a hash.
-    #
-    #   checkbox("name")
-    #     # = checkbox("NAME" => "name")
-    # 
-    #   checkbox("name", "value")
-    #     # = checkbox("NAME" => "name", "VALUE" => "value")
-    # 
-    #   checkbox("name", "value", true)
-    #     # = checkbox("NAME" => "name", "VALUE" => "value", "CHECKED" => true)
-    def checkbox(name = "", value = nil, checked = nil)
-      attributes = if name.kind_of?(String)
-                     { "TYPE" => "checkbox", "NAME" => name,
-                       "VALUE" => value, "CHECKED" => checked }
-                   else
-                     name["TYPE"] = "checkbox"
-                     name
-                   end
-      input(attributes)
-    end
-
-    # Generate a sequence of checkbox elements, as a String.
-    #
-    # The checkboxes will all have the same +name+ attribute.
-    # Each checkbox is followed by a label.
-    # There will be one checkbox for each value.  Each value
-    # can be specified as a String, which will be used both
-    # as the value of the VALUE attribute and as the label
-    # for that checkbox.  A single-element array has the
-    # same effect.
-    #
-    # Each value can also be specified as a three-element array.
-    # The first element is the VALUE attribute; the second is the
-    # label; and the third is a boolean specifying whether this
-    # checkbox is CHECKED.
-    #
-    # Each value can also be specified as a two-element
-    # array, by omitting either the value element (defaults
-    # to the same as the label), or the boolean checked element
-    # (defaults to false).
-    #
-    #   checkbox_group("name", "foo", "bar", "baz")
-    #     # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo
-    #     # <INPUT TYPE="checkbox" NAME="name" VALUE="bar">bar
-    #     # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz
-    # 
-    #   checkbox_group("name", ["foo"], ["bar", true], "baz")
-    #     # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo
-    #     # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="bar">bar
-    #     # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz
-    # 
-    #   checkbox_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz")
-    #     # <INPUT TYPE="checkbox" NAME="name" VALUE="1">Foo
-    #     # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="2">Bar
-    #     # <INPUT TYPE="checkbox" NAME="name" VALUE="Baz">Baz
-    # 
-    #   checkbox_group("NAME" => "name",
-    #                    "VALUES" => ["foo", "bar", "baz"])
-    # 
-    #   checkbox_group("NAME" => "name",
-    #                    "VALUES" => [["foo"], ["bar", true], "baz"])
-    # 
-    #   checkbox_group("NAME" => "name",
-    #                    "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])
-    def checkbox_group(name = "", *values)
-      if name.kind_of?(Hash)
-        values = name["VALUES"]
-        name = name["NAME"]
-      end
-      values.collect{|value|
-        if value.kind_of?(String)
-          checkbox(name, value) + value
-        else
-          if value[value.bytesize - 1] == true
-            checkbox(name, value[0], true) +
-            value[value.bytesize - 2]
-          else
-            checkbox(name, value[0]) +
-            value[value.bytesize - 1]
-          end
-        end
-      }.join
-    end
-
-
-    # Generate an File Upload Input element as a string.
-    #
-    # The attributes of the element can be specified as three arguments,
-    # +name+, +size+, and +maxlength+.  +maxlength+ is the maximum length
-    # of the file's _name_, not of the file's _contents_.
-    #
-    # Alternatively, the attributes can be specified as a hash.
-    #
-    # See #multipart_form() for forms that include file uploads.
-    #
-    #   file_field("name")
-    #     # <INPUT TYPE="file" NAME="name" SIZE="20">
-    # 
-    #   file_field("name", 40)
-    #     # <INPUT TYPE="file" NAME="name" SIZE="40">
-    # 
-    #   file_field("name", 40, 100)
-    #     # <INPUT TYPE="file" NAME="name" SIZE="40" MAXLENGTH="100">
-    # 
-    #   file_field("NAME" => "name", "SIZE" => 40)
-    #     # <INPUT TYPE="file" NAME="name" SIZE="40">
-    def file_field(name = "", size = 20, maxlength = nil)
-      attributes = if name.kind_of?(String)
-                     { "TYPE" => "file", "NAME" => name,
-                       "SIZE" => size.to_s }
-                   else
-                     name["TYPE"] = "file"
-                     name
-                   end
-      attributes["MAXLENGTH"] = maxlength.to_s if maxlength
-      input(attributes)
-    end
-
-
-    # Generate a Form element as a string.
-    #
-    # +method+ should be either "get" or "post", and defaults to the latter.
-    # +action+ defaults to the current CGI script name.  +enctype+
-    # defaults to "application/x-www-form-urlencoded".  
-    #
-    # Alternatively, the attributes can be specified as a hash.
-    #
-    # See also #multipart_form() for forms that include file uploads.
-    #
-    #   form{ "string" }
-    #     # <FORM METHOD="post" ENCTYPE="application/x-www-form-urlencoded">string</FORM>
-    # 
-    #   form("get") { "string" }
-    #     # <FORM METHOD="get" ENCTYPE="application/x-www-form-urlencoded">string</FORM>
-    # 
-    #   form("get", "url") { "string" }
-    #     # <FORM METHOD="get" ACTION="url" ENCTYPE="application/x-www-form-urlencoded">string</FORM>
-    # 
-    #   form("METHOD" => "post", "ENCTYPE" => "enctype") { "string" }
-    #     # <FORM METHOD="post" ENCTYPE="enctype">string</FORM>
-    def form(method = "post", action = script_name, enctype = "application/x-www-form-urlencoded")
-      attributes = if method.kind_of?(String)
-                     { "METHOD" => method, "ACTION" => action,
-                       "ENCTYPE" => enctype } 
-                   else
-                     unless method.has_key?("METHOD")
-                       method["METHOD"] = "post"
-                     end
-                     unless method.has_key?("ENCTYPE")
-                       method["ENCTYPE"] = enctype
-                     end
-                     method
-                   end
-      if block_given?
-        body = yield
-      else
-        body = ""
-      end
-      if @output_hidden
-        body += @output_hidden.collect{|k,v|
-          "<INPUT TYPE=\"HIDDEN\" NAME=\"#{k}\" VALUE=\"#{v}\">"
-        }.join
-      end
-      super(attributes){body}
-    end
-
-    # Generate a Hidden Input element as a string.
-    #
-    # The attributes of the element can be specified as two arguments,
-    # +name+ and +value+.
-    #
-    # Alternatively, the attributes can be specified as a hash.
-    #
-    #   hidden("name")
-    #     # <INPUT TYPE="hidden" NAME="name">
-    # 
-    #   hidden("name", "value")
-    #     # <INPUT TYPE="hidden" NAME="name" VALUE="value">
-    # 
-    #   hidden("NAME" => "name", "VALUE" => "reset", "ID" => "foo")
-    #     # <INPUT TYPE="hidden" NAME="name" VALUE="value" ID="foo">
-    def hidden(name = "", value = nil)
-      attributes = if name.kind_of?(String)
-                     { "TYPE" => "hidden", "NAME" => name, "VALUE" => value }
-                   else
-                     name["TYPE"] = "hidden"
-                     name
-                   end
-      input(attributes)
-    end
-
-    # Generate a top-level HTML element as a string.
-    #
-    # The attributes of the element are specified as a hash.  The
-    # pseudo-attribute "PRETTY" can be used to specify that the generated
-    # HTML string should be indented.  "PRETTY" can also be specified as
-    # a string as the sole argument to this method.  The pseudo-attribute
-    # "DOCTYPE", if given, is used as the leading DOCTYPE SGML tag; it
-    # should include the entire text of this tag, including angle brackets.
-    #
-    # The body of the html element is supplied as a block.
-    # 
-    #   html{ "string" }
-    #     # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML>string</HTML>
-    # 
-    #   html("LANG" => "ja") { "string" }
-    #     # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML LANG="ja">string</HTML>
-    # 
-    #   html("DOCTYPE" => false) { "string" }
-    #     # <HTML>string</HTML>
-    # 
-    #   html("DOCTYPE" => '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">') { "string" }
-    #     # <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"><HTML>string</HTML>
-    # 
-    #   html("PRETTY" => "  ") { "<BODY></BODY>" }
-    #     # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
-    #     # <HTML>
-    #     #   <BODY>
-    #     #   </BODY>
-    #     # </HTML>
-    # 
-    #   html("PRETTY" => "\t") { "<BODY></BODY>" }
-    #     # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
-    #     # <HTML>
-    #     #         <BODY>
-    #     #         </BODY>
-    #     # </HTML>
-    # 
-    #   html("PRETTY") { "<BODY></BODY>" }
-    #     # = html("PRETTY" => "  ") { "<BODY></BODY>" }
-    # 
-    #   html(if $VERBOSE then "PRETTY" end) { "HTML string" }
-    #
-    def html(attributes = {}) # :yield:
-      if nil == attributes
-        attributes = {}
-      elsif "PRETTY" == attributes
-        attributes = { "PRETTY" => true }
-      end
-      pretty = attributes.delete("PRETTY")
-      pretty = "  " if true == pretty
-      buf = ""
-
-      if attributes.has_key?("DOCTYPE")
-        if attributes["DOCTYPE"]
-          buf += attributes.delete("DOCTYPE")
-        else
-          attributes.delete("DOCTYPE")
-        end
-      else
-        buf += doctype
-      end
-
-      if block_given?
-        buf += super(attributes){ yield }
-      else
-        buf += super(attributes)
-      end
-
-      if pretty
-        CGI::pretty(buf, pretty)
-      else
-        buf
-      end
-
-    end
-
-    # Generate an Image Button Input element as a string.
-    #
-    # +src+ is the URL of the image to use for the button.  +name+ 
-    # is the input name.  +alt+ is the alternative text for the image.
-    #
-    # Alternatively, the attributes can be specified as a hash.
-    # 
-    #   image_button("url")
-    #     # <INPUT TYPE="image" SRC="url">
-    # 
-    #   image_button("url", "name", "string")
-    #     # <INPUT TYPE="image" SRC="url" NAME="name" ALT="string">
-    # 
-    #   image_button("SRC" => "url", "ATL" => "strng")
-    #     # <INPUT TYPE="image" SRC="url" ALT="string">
-    def image_button(src = "", name = nil, alt = nil)
-      attributes = if src.kind_of?(String)
-                     { "TYPE" => "image", "SRC" => src, "NAME" => name,
-                       "ALT" => alt }
-                   else
-                     src["TYPE"] = "image"
-                     src["SRC"] ||= ""
-                     src
-                   end
-      input(attributes)
-    end
-
-
-    # Generate an Image element as a string.
-    #
-    # +src+ is the URL of the image.  +alt+ is the alternative text for
-    # the image.  +width+ is the width of the image, and +height+ is
-    # its height.
-    #
-    # Alternatively, the attributes can be specified as a hash.
-    #
-    #   img("src", "alt", 100, 50)
-    #     # <IMG SRC="src" ALT="alt" WIDTH="100" HEIGHT="50">
-    # 
-    #   img("SRC" => "src", "ALT" => "alt", "WIDTH" => 100, "HEIGHT" => 50)
-    #     # <IMG SRC="src" ALT="alt" WIDTH="100" HEIGHT="50">
-    def img(src = "", alt = "", width = nil, height = nil)
-      attributes = if src.kind_of?(String)
-                     { "SRC" => src, "ALT" => alt }
-                   else
-                     src
-                   end
-      attributes["WIDTH"] = width.to_s if width
-      attributes["HEIGHT"] = height.to_s if height
-      super(attributes)
-    end
-
-
-    # Generate a Form element with multipart encoding as a String.
-    #
-    # Multipart encoding is used for forms that include file uploads.
-    #
-    # +action+ is the action to perform.  +enctype+ is the encoding
-    # type, which defaults to "multipart/form-data".
-    #
-    # Alternatively, the attributes can be specified as a hash.
-    #
-    #   multipart_form{ "string" }
-    #     # <FORM METHOD="post" ENCTYPE="multipart/form-data">string</FORM>
-    # 
-    #   multipart_form("url") { "string" }
-    #     # <FORM METHOD="post" ACTION="url" ENCTYPE="multipart/form-data">string</FORM>
-    def multipart_form(action = nil, enctype = "multipart/form-data")
-      attributes = if action == nil
-                     { "METHOD" => "post", "ENCTYPE" => enctype } 
-                   elsif action.kind_of?(String)
-                     { "METHOD" => "post", "ACTION" => action,
-                       "ENCTYPE" => enctype } 
-                   else
-                     unless action.has_key?("METHOD")
-                       action["METHOD"] = "post"
-                     end
-                     unless action.has_key?("ENCTYPE")
-                       action["ENCTYPE"] = enctype
-                     end
-                     action
-                   end
-      if block_given?
-        form(attributes){ yield }
-      else
-        form(attributes)
-      end
-    end
-
-
-    # Generate a Password Input element as a string.
-    #
-    # +name+ is the name of the input field.  +value+ is its default
-    # value.  +size+ is the size of the input field display.  +maxlength+
-    # is the maximum length of the inputted password.
-    #
-    # Alternatively, attributes can be specified as a hash.
-    #
-    #   password_field("name")
-    #     # <INPUT TYPE="password" NAME="name" SIZE="40">
-    # 
-    #   password_field("name", "value")
-    #     # <INPUT TYPE="password" NAME="name" VALUE="value" SIZE="40">
-    # 
-    #   password_field("password", "value", 80, 200)
-    #     # <INPUT TYPE="password" NAME="name" VALUE="value" SIZE="80" MAXLENGTH="200">
-    # 
-    #   password_field("NAME" => "name", "VALUE" => "value")
-    #     # <INPUT TYPE="password" NAME="name" VALUE="value">
-    def password_field(name = "", value = nil, size = 40, maxlength = nil)
-      attributes = if name.kind_of?(String)
-                     { "TYPE" => "password", "NAME" => name,
-                       "VALUE" => value, "SIZE" => size.to_s }
-                   else
-                     name["TYPE"] = "password"
-                     name
-                   end
-      attributes["MAXLENGTH"] = maxlength.to_s if maxlength
-      input(attributes)
-    end
-
-    # Generate a Select element as a string.
-    #
-    # +name+ is the name of the element.  The +values+ are the options that
-    # can be selected from the Select menu.  Each value can be a String or
-    # a one, two, or three-element Array.  If a String or a one-element
-    # Array, this is both the value of that option and the text displayed for
-    # it.  If a three-element Array, the elements are the option value, displayed
-    # text, and a boolean value specifying whether this option starts as selected.
-    # The two-element version omits either the option value (defaults to the same
-    # as the display text) or the boolean selected specifier (defaults to false).
-    #
-    # The attributes and options can also be specified as a hash.  In this
-    # case, options are specified as an array of values as described above,
-    # with the hash key of "VALUES".
-    #
-    #   popup_menu("name", "foo", "bar", "baz")
-    #     # <SELECT NAME="name">
-    #     #   <OPTION VALUE="foo">foo</OPTION>
-    #     #   <OPTION VALUE="bar">bar</OPTION>
-    #     #   <OPTION VALUE="baz">baz</OPTION>
-    #     # </SELECT>
-    # 
-    #   popup_menu("name", ["foo"], ["bar", true], "baz")
-    #     # <SELECT NAME="name">
-    #     #   <OPTION VALUE="foo">foo</OPTION>
-    #     #   <OPTION VALUE="bar" SELECTED>bar</OPTION>
-    #     #   <OPTION VALUE="baz">baz</OPTION>
-    #     # </SELECT>
-    # 
-    #   popup_menu("name", ["1", "Foo"], ["2", "Bar", true], "Baz")
-    #     # <SELECT NAME="name">
-    #     #   <OPTION VALUE="1">Foo</OPTION>
-    #     #   <OPTION SELECTED VALUE="2">Bar</OPTION>
-    #     #   <OPTION VALUE="Baz">Baz</OPTION>
-    #     # </SELECT>
-    # 
-    #   popup_menu("NAME" => "name", "SIZE" => 2, "MULTIPLE" => true,
-    #               "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])
-    #     # <SELECT NAME="name" MULTIPLE SIZE="2">
-    #     #   <OPTION VALUE="1">Foo</OPTION>
-    #     #   <OPTION SELECTED VALUE="2">Bar</OPTION>
-    #     #   <OPTION VALUE="Baz">Baz</OPTION>
-    #     # </SELECT>
-    def popup_menu(name = "", *values)
-
-      if name.kind_of?(Hash)
-        values   = name["VALUES"]
-        size     = name["SIZE"].to_s if name["SIZE"]
-        multiple = name["MULTIPLE"]
-        name     = name["NAME"]
-      else
-        size = nil
-        multiple = nil
-      end
-
-      select({ "NAME" => name, "SIZE" => size,
-               "MULTIPLE" => multiple }){
-        values.collect{|value|
-          if value.kind_of?(String)
-            option({ "VALUE" => value }){ value }
-          else
-            if value[value.bytesize - 1] == true
-              option({ "VALUE" => value[0], "SELECTED" => true }){
-                value[value.bytesize - 2]
-              }
-            else
-              option({ "VALUE" => value[0] }){
-                value[value.bytesize - 1]
-              }
-            end
-          end
-        }.join
-      }
-
-    end
-
-    # Generates a radio-button Input element.
-    #
-    # +name+ is the name of the input field.  +value+ is the value of
-    # the field if checked.  +checked+ specifies whether the field
-    # starts off checked.
-    #
-    # Alternatively, the attributes can be specified as a hash.
-    #
-    #   radio_button("name", "value")
-    #     # <INPUT TYPE="radio" NAME="name" VALUE="value">
-    # 
-    #   radio_button("name", "value", true)
-    #     # <INPUT TYPE="radio" NAME="name" VALUE="value" CHECKED>
-    # 
-    #   radio_button("NAME" => "name", "VALUE" => "value", "ID" => "foo")
-    #     # <INPUT TYPE="radio" NAME="name" VALUE="value" ID="foo">
-    def radio_button(name = "", value = nil, checked = nil)
-      attributes = if name.kind_of?(String)
-                     { "TYPE" => "radio", "NAME" => name,
-                       "VALUE" => value, "CHECKED" => checked }
-                   else
-                     name["TYPE"] = "radio"
-                     name
-                   end
-      input(attributes)
-    end
-
-    # Generate a sequence of radio button Input elements, as a String.
-    #
-    # This works the same as #checkbox_group().  However, it is not valid
-    # to have more than one radiobutton in a group checked.
-    # 
-    #   radio_group("name", "foo", "bar", "baz")
-    #     # <INPUT TYPE="radio" NAME="name" VALUE="foo">foo
-    #     # <INPUT TYPE="radio" NAME="name" VALUE="bar">bar
-    #     # <INPUT TYPE="radio" NAME="name" VALUE="baz">baz
-    # 
-    #   radio_group("name", ["foo"], ["bar", true], "baz")
-    #     # <INPUT TYPE="radio" NAME="name" VALUE="foo">foo
-    #     # <INPUT TYPE="radio" CHECKED NAME="name" VALUE="bar">bar
-    #     # <INPUT TYPE="radio" NAME="name" VALUE="baz">baz
-    # 
-    #   radio_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz")
-    #     # <INPUT TYPE="radio" NAME="name" VALUE="1">Foo
-    #     # <INPUT TYPE="radio" CHECKED NAME="name" VALUE="2">Bar
-    #     # <INPUT TYPE="radio" NAME="name" VALUE="Baz">Baz
-    # 
-    #   radio_group("NAME" => "name",
-    #                 "VALUES" => ["foo", "bar", "baz"])
-    # 
-    #   radio_group("NAME" => "name",
-    #                 "VALUES" => [["foo"], ["bar", true], "baz"])
-    # 
-    #   radio_group("NAME" => "name",
-    #                 "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])
-    def radio_group(name = "", *values)
-      if name.kind_of?(Hash)
-        values = name["VALUES"]
-        name = name["NAME"]
-      end
-      values.collect{|value|
-        if value.kind_of?(String)
-          radio_button(name, value) + value
-        else
-          if value[value.bytesize - 1] == true
-            radio_button(name, value[0], true) +
-            value[value.bytesize - 2]
-          else
-            radio_button(name, value[0]) +
-            value[value.bytesize - 1]
-          end
-        end
-      }.join
-    end
-
-    # Generate a reset button Input element, as a String.
-    #
-    # This resets the values on a form to their initial values.  +value+
-    # is the text displayed on the button. +name+ is the name of this button.
-    #
-    # Alternatively, the attributes can be specified as a hash.
-    #
-    #   reset
-    #     # <INPUT TYPE="reset">
-    # 
-    #   reset("reset")
-    #     # <INPUT TYPE="reset" VALUE="reset">
-    # 
-    #   reset("VALUE" => "reset", "ID" => "foo")
-    #     # <INPUT TYPE="reset" VALUE="reset" ID="foo">
-    def reset(value = nil, name = nil)
-      attributes = if (not value) or value.kind_of?(String)
-                     { "TYPE" => "reset", "VALUE" => value, "NAME" => name }
-                   else
-                     value["TYPE"] = "reset"
-                     value
-                   end
-      input(attributes)
-    end
-
-    alias scrolling_list popup_menu
-
-    # Generate a submit button Input element, as a String.
-    #
-    # +value+ is the text to display on the button.  +name+ is the name
-    # of the input.
-    #
-    # Alternatively, the attributes can be specified as a hash.
-    #
-    #   submit
-    #     # <INPUT TYPE="submit">
-    # 
-    #   submit("ok")
-    #     # <INPUT TYPE="submit" VALUE="ok">
-    # 
-    #   submit("ok", "button1")
-    #     # <INPUT TYPE="submit" VALUE="ok" NAME="button1">
-    # 
-    #   submit("VALUE" => "ok", "NAME" => "button1", "ID" => "foo")
-    #     # <INPUT TYPE="submit" VALUE="ok" NAME="button1" ID="foo">
-    def submit(value = nil, name = nil)
-      attributes = if (not value) or value.kind_of?(String)
-                     { "TYPE" => "submit", "VALUE" => value, "NAME" => name }
-                   else
-                     value["TYPE"] = "submit"
-                     value
-                   end
-      input(attributes)
-    end
-
-    # Generate a text field Input element, as a String.
-    #
-    # +name+ is the name of the input field.  +value+ is its initial
-    # value.  +size+ is the size of the input area.  +maxlength+
-    # is the maximum length of input accepted.
-    #
-    # Alternatively, the attributes can be specified as a hash.
-    #
-    #   text_field("name")
-    #     # <INPUT TYPE="text" NAME="name" SIZE="40">
-    # 
-    #   text_field("name", "value")
-    #     # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="40">
-    # 
-    #   text_field("name", "value", 80)
-    #     # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="80">
-    # 
-    #   text_field("name", "value", 80, 200)
-    #     # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="80" MAXLENGTH="200">
-    # 
-    #   text_field("NAME" => "name", "VALUE" => "value")
-    #     # <INPUT TYPE="text" NAME="name" VALUE="value">
-    def text_field(name = "", value = nil, size = 40, maxlength = nil)
-      attributes = if name.kind_of?(String)
-                     { "TYPE" => "text", "NAME" => name, "VALUE" => value,
-                       "SIZE" => size.to_s }
-                   else
-                     name["TYPE"] = "text"
-                     name
-                   end
-      attributes["MAXLENGTH"] = maxlength.to_s if maxlength
-      input(attributes)
-    end
-
-    # Generate a TextArea element, as a String.
-    #
-    # +name+ is the name of the textarea.  +cols+ is the number of
-    # columns and +rows+ is the number of rows in the display.
-    #
-    # Alternatively, the attributes can be specified as a hash.
-    #
-    # The body is provided by the passed-in no-argument block
-    #
-    #   textarea("name")
-    #      # = textarea("NAME" => "name", "COLS" => 70, "ROWS" => 10)
-    #
-    #   textarea("name", 40, 5)
-    #      # = textarea("NAME" => "name", "COLS" => 40, "ROWS" => 5)
-    def textarea(name = "", cols = 70, rows = 10)  # :yield:
-      attributes = if name.kind_of?(String)
-                     { "NAME" => name, "COLS" => cols.to_s,
-                       "ROWS" => rows.to_s }
-                   else
-                     name
-                   end
-      if block_given?
-        super(attributes){ yield }
-      else
-        super(attributes)
-      end
-    end
-
-  end # HtmlExtension
-
-
-  # Mixin module for HTML version 3 generation methods.
-  module Html3 # :nodoc:
-
-    # The DOCTYPE declaration for this version of HTML
-    def doctype
-      %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">|
-    end
-
-    # Initialise the HTML generation methods for this version.
-    def element_init
-      extend TagMaker
-      methods = ""
-      # - -
-      for element in %w[ A TT I B U STRIKE BIG SMALL SUB SUP EM STRONG
-          DFN CODE SAMP KBD VAR CITE FONT ADDRESS DIV center MAP
-          APPLET PRE XMP LISTING DL OL UL DIR MENU SELECT table TITLE
-          STYLE SCRIPT H1 H2 H3 H4 H5 H6 TEXTAREA FORM BLOCKQUOTE
-          CAPTION ]
-        methods += <<-BEGIN + nn_element_def(element) + <<-END
-          def #{element.downcase}(attributes = {})
-        BEGIN
-          end
-        END
-      end
-
-      # - O EMPTY
-      for element in %w[ IMG BASE BASEFONT BR AREA LINK PARAM HR INPUT
-          ISINDEX META ]
-        methods += <<-BEGIN + nOE_element_def(element) + <<-END
-          def #{element.downcase}(attributes = {})
-        BEGIN
-          end
-        END
-      end
-
-      # O O or - O
-      for element in %w[ HTML HEAD BODY P PLAINTEXT DT DD LI OPTION tr
-          th td ]
-        methods += <<-BEGIN + nO_element_def(element) + <<-END
-          def #{element.downcase}(attributes = {})
-        BEGIN
-          end
-        END
-      end
-      eval(methods)
-    end
-
-  end # Html3
-
-
-  # Mixin module for HTML version 4 generation methods.
-  module Html4 # :nodoc:
-
-    # The DOCTYPE declaration for this version of HTML
-    def doctype
-      %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">|
-    end
-
-    # Initialise the HTML generation methods for this version.
-    def element_init
-      extend TagMaker
-      methods = ""
-      # - -
-      for element in %w[ TT I B BIG SMALL EM STRONG DFN CODE SAMP KBD
-        VAR CITE ABBR ACRONYM SUB SUP SPAN BDO ADDRESS DIV MAP OBJECT
-        H1 H2 H3 H4 H5 H6 PRE Q INS DEL DL OL UL LABEL SELECT OPTGROUP
-        FIELDSET LEGEND BUTTON TABLE TITLE STYLE SCRIPT NOSCRIPT
-        TEXTAREA FORM A BLOCKQUOTE CAPTION ]
-        methods += <<-BEGIN + nn_element_def(element) + <<-END
-          def #{element.downcase}(attributes = {})
-        BEGIN
-          end
-        END
-      end
-
-      # - O EMPTY
-      for element in %w[ IMG BASE BR AREA LINK PARAM HR INPUT COL META ]
-        methods += <<-BEGIN + nOE_element_def(element) + <<-END
-          def #{element.downcase}(attributes = {})
-        BEGIN
-          end
-        END
-      end
-
-      # O O or - O
-      for element in %w[ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY
-          COLGROUP TR TH TD HEAD]
-        methods += <<-BEGIN + nO_element_def(element) + <<-END
-          def #{element.downcase}(attributes = {})
-        BEGIN
-          end
-        END
-      end
-      eval(methods)
-    end
-
-  end # Html4
-
-
-  # Mixin module for HTML version 4 transitional generation methods.
-  module Html4Tr # :nodoc:
-
-    # The DOCTYPE declaration for this version of HTML
-    def doctype
-      %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">|
-    end
-
-    # Initialise the HTML generation methods for this version.
-    def element_init
-      extend TagMaker
-      methods = ""
-      # - -
-      for element in %w[ TT I B U S STRIKE BIG SMALL EM STRONG DFN
-          CODE SAMP KBD VAR CITE ABBR ACRONYM FONT SUB SUP SPAN BDO
-          ADDRESS DIV CENTER MAP OBJECT APPLET H1 H2 H3 H4 H5 H6 PRE Q
-          INS DEL DL OL UL DIR MENU LABEL SELECT OPTGROUP FIELDSET
-          LEGEND BUTTON TABLE IFRAME NOFRAMES TITLE STYLE SCRIPT
-          NOSCRIPT TEXTAREA FORM A BLOCKQUOTE CAPTION ]
-        methods += <<-BEGIN + nn_element_def(element) + <<-END
-          def #{element.downcase}(attributes = {})
-        BEGIN
-          end
-        END
-      end
-
-      # - O EMPTY
-      for element in %w[ IMG BASE BASEFONT BR AREA LINK PARAM HR INPUT
-          COL ISINDEX META ]
-        methods += <<-BEGIN + nOE_element_def(element) + <<-END
-          def #{element.downcase}(attributes = {})
-        BEGIN
-          end
-        END
-      end
-
-      # O O or - O
-      for element in %w[ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY
-          COLGROUP TR TH TD HEAD ]
-        methods += <<-BEGIN + nO_element_def(element) + <<-END
-          def #{element.downcase}(attributes = {})
-        BEGIN
-          end
-        END
-      end
-      eval(methods)
-    end
-
-  end # Html4Tr
-
-
-  # Mixin module for generating HTML version 4 with framesets.
-  module Html4Fr # :nodoc:
-
-    # The DOCTYPE declaration for this version of HTML
-    def doctype
-      %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">|
-    end
-
-    # Initialise the HTML generation methods for this version.
-    def element_init
-      methods = ""
-      # - -
-      for element in %w[ FRAMESET ]
-        methods += <<-BEGIN + nn_element_def(element) + <<-END
-          def #{element.downcase}(attributes = {})
-        BEGIN
-          end
-        END
-      end
-
-      # - O EMPTY
-      for element in %w[ FRAME ]
-        methods += <<-BEGIN + nOE_element_def(element) + <<-END
-          def #{element.downcase}(attributes = {})
-        BEGIN
-          end
-        END
-      end
-      eval(methods)
-    end
-
-  end # Html4Fr
-
-
-  # Creates a new CGI instance.
-  #
-  # +type+ specifies which version of HTML to load the HTML generation
-  # methods for.  The following versions of HTML are supported:
-  #
-  # html3:: HTML 3.x
-  # html4:: HTML 4.0
-  # html4Tr:: HTML 4.0 Transitional
-  # html4Fr:: HTML 4.0 with Framesets
-  #
-  # If not specified, no HTML generation methods will be loaded.
-  #
-  # If the CGI object is not created in a standard CGI call environment
-  # (that is, it can't locate REQUEST_METHOD in its environment), then
-  # it will run in "offline" mode.  In this mode, it reads its parameters
-  # from the command line or (failing that) from standard input.  Otherwise,
-  # cookies and other parameters are parsed automatically from the standard
-  # CGI locations, which varies according to the REQUEST_METHOD.
-  def initialize(type = "query")
-    if defined?(MOD_RUBY) && !ENV.key?("GATEWAY_INTERFACE")
-      Apache.request.setup_cgi_env
-    end
-
-    extend QueryExtension
-    @multipart = false
-
-    initialize_query()  # set @params, @cookies
-    @output_cookies = nil
-    @output_hidden = nil
-
-    case type
-    when "html3"
-      extend Html3
-      element_init()
-      extend HtmlExtension
-    when "html4"
-      extend Html4
-      element_init()
-      extend HtmlExtension
-    when "html4Tr"
-      extend Html4Tr
-      element_init()
-      extend HtmlExtension
-    when "html4Fr"
-      extend Html4Tr
-      element_init()
-      extend Html4Fr
-      element_init()
-      extend HtmlExtension
-    end
-  end
-
-end   # class CGI
+require 'cgi/core'
+require 'cgi/cookie'
+require 'cgi/util'
Index: lib/cgi/util.rb
===================================================================
--- lib/cgi/util.rb	(revision 0)
+++ lib/cgi/util.rb	(revision 19272)
@@ -0,0 +1,181 @@
+class CGI
+  # URL-encode a string.
+  #   url_encoded_string = CGI::escape("'Stop!' said Fred")
+  #      # => "%27Stop%21%27+said+Fred"
+  def CGI::escape(string)
+    string.gsub(/([^ a-zA-Z0-9_.-]+)/) do
+      '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
+    end.tr(' ', '+')
+  end
+
+
+  # URL-decode a string.
+  #   string = CGI::unescape("%27Stop%21%27+said+Fred")
+  #      # => "'Stop!' said Fred"
+  def CGI::unescape(string)
+    enc = string.encoding
+    string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/) do
+      [$1.delete('%')].pack('H*').force_encoding(enc)
+    end
+  end
+
+  TABLE_FOR_ESCAPE_HTML__ = {
+    '&' => '&amp;',
+    '"' => '&quot;',
+    '<' => '&lt;',
+    '>' => '&gt;',
+  }
+
+  # Escape special characters in HTML, namely &\"<>
+  #   CGI::escapeHTML('Usage: foo "bar" <baz>')
+  #      # => "Usage: foo &quot;bar&quot; &lt;baz&gt;"
+  def CGI::escapeHTML(string)
+    string.gsub(/[&\"<>]/, TABLE_FOR_ESCAPE_HTML__)
+  end
+
+
+  # Unescape a string that has been HTML-escaped
+  #   CGI::unescapeHTML("Usage: foo &quot;bar&quot; &lt;baz&gt;")
+  #      # => "Usage: foo \"bar\" <baz>"
+  def CGI::unescapeHTML(string)
+    enc = string.encoding
+    if [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE].include?(enc)
+      return string.gsub(Regexp.new('&(amp|quot|gt|lt|#[0-9]+|#x[0-9A-Fa-f]+);'.encode(enc))) do
+        case $1.encode("US-ASCII")
+        when 'amp'                 then '&'.encode(enc)
+        when 'quot'                then '"'.encode(enc)
+        when 'gt'                  then '>'.encode(enc)
+        when 'lt'                  then '<'.encode(enc)
+        when /\A#0*(\d+)\z/        then $1.to_i.chr(enc)
+        when /\A#x([0-9a-f]+)\z/i  then $1.hex.chr(enc)
+        end
+      end
+    end
+    asciicompat = Encoding.compatible?(string, "a")
+    string.gsub(/&(amp|quot|gt|lt|\#[0-9]+|\#x[0-9A-Fa-f]+);/) do
+      match = $1.dup
+      case match
+      when 'amp'                 then '&'
+      when 'quot'                then '"'
+      when 'gt'                  then '>'
+      when 'lt'                  then '<'
+      when /\A#0*(\d+)\z/
+        n = $1.to_i
+        if enc == Encoding::UTF_8 or
+          enc == Encoding::ISO_8859_1 && n < 256 or
+          asciicompat && n < 128
+          n.chr(enc)
+        else
+          "&##{$1};"
+        end
+      when /\A#x([0-9a-f]+)\z/i
+        n = $1.hex
+        if enc == Encoding::UTF_8 or
+          enc == Encoding::ISO_8859_1 && n < 256 or
+          asciicompat && n < 128
+          n.chr(enc)
+        else
+          "&#x#{$1};"
+        end
+      else
+        "&#{match};"
+      end
+    end
+  end
+  def CGI::escape_html(str)
+    escapeHTML(str)
+  end
+  def CGI::unescape_html(str)
+    unescapeHTML(str)
+  end
+
+  # Escape only the tags of certain HTML elements in +string+.
+  #
+  # Takes an element or elements or array of elements.  Each element
+  # is specified by the name of the element, without angle brackets.
+  # This matches both the start and the end tag of that element.
+  # The attribute list of the open tag will also be escaped (for
+  # instance, the double-quotes surrounding attribute values).
+  #
+  #   print CGI::escapeElement('<BR><A HREF="url"></A>', "A", "IMG")
+  #     # "<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt"
+  #
+  #   print CGI::escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"])
+  #     # "<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt"
+  def CGI::escapeElement(string, *elements)
+    elements = elements[0] if elements[0].kind_of?(Array)
+    unless elements.empty?
+      string.gsub(/<\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?>/i) do
+        CGI::escapeHTML($&)
+      end
+    else
+      string
+    end
+  end
+
+
+  # Undo escaping such as that done by CGI::escapeElement()
+  #
+  #   print CGI::unescapeElement(
+  #           CGI::escapeHTML('<BR><A HREF="url"></A>'), "A", "IMG")
+  #     # "&lt;BR&gt;<A HREF="url"></A>"
+  # 
+  #   print CGI::unescapeElement(
+  #           CGI::escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"])
+  #     # "&lt;BR&gt;<A HREF="url"></A>"
+  def CGI::unescapeElement(string, *elements)
+    elements = elements[0] if elements[0].kind_of?(Array)
+    unless elements.empty?
+      string.gsub(/&lt;\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?&gt;/i) do
+        CGI::unescapeHTML($&)
+      end
+    else
+      string
+    end
+  end
+  def CGI::escape_element(str)
+    escapeElement(str)
+  end
+  def CGI::unescape_element(str)
+    unescapeElement(str)
+  end
+
+  # Format a +Time+ object as a String using the format specified by RFC 1123.
+  #
+  #   CGI::rfc1123_date(Time.now)
+  #     # Sat, 01 Jan 2000 00:00:00 GMT
+  def CGI::rfc1123_date(time)
+    t = time.clone.gmtime
+    return format("%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
+                RFC822_DAYS[t.wday], t.day, RFC822_MONTHS[t.month-1], t.year,
+                t.hour, t.min, t.sec)
+  end
+
+  # Prettify (indent) an HTML string.
+  #
+  # +string+ is the HTML string to indent.  +shift+ is the indentation
+  # unit to use; it defaults to two spaces.
+  #
+  #   print CGI::pretty("<HTML><BODY></BODY></HTML>")
+  #     # <HTML>
+  #     #   <BODY>
+  #     #   </BODY>
+  #     # </HTML>
+  # 
+  #   print CGI::pretty("<HTML><BODY></BODY></HTML>", "\t")
+  #     # <HTML>
+  #     #         <BODY>
+  #     #         </BODY>
+  #     # </HTML>
+  #
+  def CGI::pretty(string, shift = "  ")
+    lines = string.gsub(/(?!\A)<(?:.|\n)*?>/, "\n\\0").gsub(/<(?:.|\n)*?>(?!\n)/, "\\0\n")
+    end_pos = 0
+    while end_pos = lines.index(/^<\/(\w+)/, end_pos)
+      element = $1.dup
+      start_pos = lines.rindex(/^\s*<#{element}/i, end_pos)
+      lines[start_pos ... end_pos] = "__" + lines[start_pos ... end_pos].gsub(/\n(?!\z)/, "\n" + shift) + "__"
+    end
+    lines.gsub(/^((?:#{Regexp::quote(shift)})*)__(?=<\/?\w)/, '\1')
+  end
+end
Index: lib/cgi/html.rb
===================================================================
--- lib/cgi/html.rb	(revision 0)
+++ lib/cgi/html.rb	(revision 19272)
@@ -0,0 +1,1021 @@
+  # Base module for HTML-generation mixins.
+  #
+  # Provides methods for code generation for tags following
+  # the various DTD element types.
+class CGI
+  module TagMaker # :nodoc:
+
+    # Generate code for an element with required start and end tags.
+    #
+    #   - -
+    def nn_element_def(element)
+      nOE_element_def(element, <<-END)
+          if block_given?
+            yield.to_s
+          else
+            ""
+          end +
+          "</#{element.upcase}>"
+      END
+    end
+
+    # Generate code for an empty element.
+    #
+    #   - O EMPTY
+    def nOE_element_def(element, append = nil)
+      s = <<-END
+          attributes={attributes=>nil} if attributes.kind_of?(String)
+          "<#{element.upcase}" + attributes.collect{|name, value|
+            next unless value
+            " " + CGI::escapeHTML(name) +
+            if true == value
+              ""
+            else
+              '="' + CGI::escapeHTML(value) + '"'
+            end
+          }.join + ">"
+      END
+      s.sub!(/\Z/, " +") << append if append
+      s
+    end
+
+    # Generate code for an element for which the end (and possibly the
+    # start) tag is optional.
+    #
+    #   O O or - O
+    def nO_element_def(element)
+      nOE_element_def(element, <<-END)
+          if block_given?
+            yield.to_s + "</#{element.upcase}>"
+          else
+            ""
+          end
+      END
+    end
+
+  end # TagMaker
+
+
+  #
+  # Mixin module providing HTML generation methods.
+  #
+  # For example,
+  #   cgi.a("http://www.example.com") { "Example" }
+  #     # => "<A HREF=\"http://www.example.com\">Example</A>"
+  #
+  # Modules Http3, Http4, etc., contain more basic HTML-generation methods
+  # (:title, :center, etc.).
+  #
+  # See class CGI for a detailed example. 
+  #
+  module HtmlExtension
+
+
+    # Generate an Anchor element as a string.
+    #
+    # +href+ can either be a string, giving the URL
+    # for the HREF attribute, or it can be a hash of
+    # the element's attributes.
+    #
+    # The body of the element is the string returned by the no-argument
+    # block passed in.
+    #
+    #   a("http://www.example.com") { "Example" }
+    #     # => "<A HREF=\"http://www.example.com\">Example</A>"
+    #
+    #   a("HREF" => "http://www.example.com", "TARGET" => "_top") { "Example" }
+    #     # => "<A HREF=\"http://www.example.com\" TARGET=\"_top\">Example</A>"
+    #
+    def a(href = "") # :yield:
+      attributes = if href.kind_of?(String)
+                     { "HREF" => href }
+                   else
+                     href
+                   end
+      if block_given?
+        super(attributes){ yield }
+      else
+        super(attributes)
+      end
+    end
+
+    # Generate a Document Base URI element as a String. 
+    #
+    # +href+ can either by a string, giving the base URL for the HREF
+    # attribute, or it can be a has of the element's attributes.
+    #
+    # The passed-in no-argument block is ignored.
+    #
+    #   base("http://www.example.com/cgi")
+    #     # => "<BASE HREF=\"http://www.example.com/cgi\">"
+    def base(href = "") # :yield:
+      attributes = if href.kind_of?(String)
+                     { "HREF" => href }
+                   else
+                     href
+                   end
+      if block_given?
+        super(attributes){ yield }
+      else
+        super(attributes)
+      end
+    end
+
+    # Generate a BlockQuote element as a string.
+    #
+    # +cite+ can either be a string, give the URI for the source of
+    # the quoted text, or a hash, giving all attributes of the element,
+    # or it can be omitted, in which case the element has no attributes.
+    #
+    # The body is provided by the passed-in no-argument block
+    #
+    #   blockquote("http://www.example.com/quotes/foo.html") { "Foo!" }
+    #     #=> "<BLOCKQUOTE CITE=\"http://www.example.com/quotes/foo.html\">Foo!</BLOCKQUOTE>
+    def blockquote(cite = {})  # :yield:
+      attributes = if cite.kind_of?(String)
+                     { "CITE" => cite }
+                   else
+                     cite
+                   end
+      if block_given?
+        super(attributes){ yield }
+      else
+        super(attributes)
+      end
+    end
+
+
+    # Generate a Table Caption element as a string.
+    #
+    # +align+ can be a string, giving the alignment of the caption
+    # (one of top, bottom, left, or right).  It can be a hash of
+    # all the attributes of the element.  Or it can be omitted.
+    #
+    # The body of the element is provided by the passed-in no-argument block.
+    #
+    #   caption("left") { "Capital Cities" }
+    #     # => <CAPTION ALIGN=\"left\">Capital Cities</CAPTION>
+    def caption(align = {}) # :yield:
+      attributes = if align.kind_of?(String)
+                     { "ALIGN" => align }
+                   else
+                     align
+                   end
+      if block_given?
+        super(attributes){ yield }
+      else
+        super(attributes)
+      end
+    end
+
+
+    # Generate a Checkbox Input element as a string.
+    #
+    # The attributes of the element can be specified as three arguments,
+    # +name+, +value+, and +checked+.  +checked+ is a boolean value;
+    # if true, the CHECKED attribute will be included in the element.
+    #
+    # Alternatively, the attributes can be specified as a hash.
+    #
+    #   checkbox("name")
+    #     # = checkbox("NAME" => "name")
+    # 
+    #   checkbox("name", "value")
+    #     # = checkbox("NAME" => "name", "VALUE" => "value")
+    # 
+    #   checkbox("name", "value", true)
+    #     # = checkbox("NAME" => "name", "VALUE" => "value", "CHECKED" => true)
+    def checkbox(name = "", value = nil, checked = nil)
+      attributes = if name.kind_of?(String)
+                     { "TYPE" => "checkbox", "NAME" => name,
+                       "VALUE" => value, "CHECKED" => checked }
+                   else
+                     name["TYPE"] = "checkbox"
+                     name
+                   end
+      input(attributes)
+    end
+
+    # Generate a sequence of checkbox elements, as a String.
+    #
+    # The checkboxes will all have the same +name+ attribute.
+    # Each checkbox is followed by a label.
+    # There will be one checkbox for each value.  Each value
+    # can be specified as a String, which will be used both
+    # as the value of the VALUE attribute and as the label
+    # for that checkbox.  A single-element array has the
+    # same effect.
+    #
+    # Each value can also be specified as a three-element array.
+    # The first element is the VALUE attribute; the second is the
+    # label; and the third is a boolean specifying whether this
+    # checkbox is CHECKED.
+    #
+    # Each value can also be specified as a two-element
+    # array, by omitting either the value element (defaults
+    # to the same as the label), or the boolean checked element
+    # (defaults to false).
+    #
+    #   checkbox_group("name", "foo", "bar", "baz")
+    #     # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo
+    #     # <INPUT TYPE="checkbox" NAME="name" VALUE="bar">bar
+    #     # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz
+    # 
+    #   checkbox_group("name", ["foo"], ["bar", true], "baz")
+    #     # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo
+    #     # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="bar">bar
+    #     # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz
+    # 
+    #   checkbox_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz")
+    #     # <INPUT TYPE="checkbox" NAME="name" VALUE="1">Foo
+    #     # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="2">Bar
+    #     # <INPUT TYPE="checkbox" NAME="name" VALUE="Baz">Baz
+    # 
+    #   checkbox_group("NAME" => "name",
+    #                    "VALUES" => ["foo", "bar", "baz"])
+    # 
+    #   checkbox_group("NAME" => "name",
+    #                    "VALUES" => [["foo"], ["bar", true], "baz"])
+    # 
+    #   checkbox_group("NAME" => "name",
+    #                    "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])
+    def checkbox_group(name = "", *values)
+      if name.kind_of?(Hash)
+        values = name["VALUES"]
+        name = name["NAME"]
+      end
+      values.collect{|value|
+        if value.kind_of?(String)
+          checkbox(name, value) + value
+        else
+          if value[value.bytesize - 1] == true
+            checkbox(name, value[0], true) +
+            value[value.bytesize - 2]
+          else
+            checkbox(name, value[0]) +
+            value[value.bytesize - 1]
+          end
+        end
+      }.join
+    end
+
+
+    # Generate an File Upload Input element as a string.
+    #
+    # The attributes of the element can be specified as three arguments,
+    # +name+, +size+, and +maxlength+.  +maxlength+ is the maximum length
+    # of the file's _name_, not of the file's _contents_.
+    #
+    # Alternatively, the attributes can be specified as a hash.
+    #
+    # See #multipart_form() for forms that include file uploads.
+    #
+    #   file_field("name")
+    #     # <INPUT TYPE="file" NAME="name" SIZE="20">
+    # 
+    #   file_field("name", 40)
+    #     # <INPUT TYPE="file" NAME="name" SIZE="40">
+    # 
+    #   file_field("name", 40, 100)
+    #     # <INPUT TYPE="file" NAME="name" SIZE="40" MAXLENGTH="100">
+    # 
+    #   file_field("NAME" => "name", "SIZE" => 40)
+    #     # <INPUT TYPE="file" NAME="name" SIZE="40">
+    def file_field(name = "", size = 20, maxlength = nil)
+      attributes = if name.kind_of?(String)
+                     { "TYPE" => "file", "NAME" => name,
+                       "SIZE" => size.to_s }
+                   else
+                     name["TYPE"] = "file"
+                     name
+                   end
+      attributes["MAXLENGTH"] = maxlength.to_s if maxlength
+      input(attributes)
+    end
+
+
+    # Generate a Form element as a string.
+    #
+    # +method+ should be either "get" or "post", and defaults to the latter.
+    # +action+ defaults to the current CGI script name.  +enctype+
+    # defaults to "application/x-www-form-urlencoded".  
+    #
+    # Alternatively, the attributes can be specified as a hash.
+    #
+    # See also #multipart_form() for forms that include file uploads.
+    #
+    #   form{ "string" }
+    #     # <FORM METHOD="post" ENCTYPE="application/x-www-form-urlencoded">string</FORM>
+    # 
+    #   form("get") { "string" }
+    #     # <FORM METHOD="get" ENCTYPE="application/x-www-form-urlencoded">string</FORM>
+    # 
+    #   form("get", "url") { "string" }
+    #     # <FORM METHOD="get" ACTION="url" ENCTYPE="application/x-www-form-urlencoded">string</FORM>
+    # 
+    #   form("METHOD" => "post", "ENCTYPE" => "enctype") { "string" }
+    #     # <FORM METHOD="post" ENCTYPE="enctype">string</FORM>
+    def form(method = "post", action = script_name, enctype = "application/x-www-form-urlencoded")
+      attributes = if method.kind_of?(String)
+                     { "METHOD" => method, "ACTION" => action,
+                       "ENCTYPE" => enctype } 
+                   else
+                     unless method.has_key?("METHOD")
+                       method["METHOD"] = "post"
+                     end
+                     unless method.has_key?("ENCTYPE")
+                       method["ENCTYPE"] = enctype
+                     end
+                     method
+                   end
+      if block_given?
+        body = yield
+      else
+        body = ""
+      end
+      if @output_hidden
+        body += @output_hidden.collect{|k,v|
+          "<INPUT TYPE=\"HIDDEN\" NAME=\"#{k}\" VALUE=\"#{v}\">"
+        }.join
+      end
+      super(attributes){body}
+    end
+
+    # Generate a Hidden Input element as a string.
+    #
+    # The attributes of the element can be specified as two arguments,
+    # +name+ and +value+.
+    #
+    # Alternatively, the attributes can be specified as a hash.
+    #
+    #   hidden("name")
+    #     # <INPUT TYPE="hidden" NAME="name">
+    # 
+    #   hidden("name", "value")
+    #     # <INPUT TYPE="hidden" NAME="name" VALUE="value">
+    # 
+    #   hidden("NAME" => "name", "VALUE" => "reset", "ID" => "foo")
+    #     # <INPUT TYPE="hidden" NAME="name" VALUE="value" ID="foo">
+    def hidden(name = "", value = nil)
+      attributes = if name.kind_of?(String)
+                     { "TYPE" => "hidden", "NAME" => name, "VALUE" => value }
+                   else
+                     name["TYPE"] = "hidden"
+                     name
+                   end
+      input(attributes)
+    end
+
+    # Generate a top-level HTML element as a string.
+    #
+    # The attributes of the element are specified as a hash.  The
+    # pseudo-attribute "PRETTY" can be used to specify that the generated
+    # HTML string should be indented.  "PRETTY" can also be specified as
+    # a string as the sole argument to this method.  The pseudo-attribute
+    # "DOCTYPE", if given, is used as the leading DOCTYPE SGML tag; it
+    # should include the entire text of this tag, including angle brackets.
+    #
+    # The body of the html element is supplied as a block.
+    # 
+    #   html{ "string" }
+    #     # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML>string</HTML>
+    # 
+    #   html("LANG" => "ja") { "string" }
+    #     # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML LANG="ja">string</HTML>
+    # 
+    #   html("DOCTYPE" => false) { "string" }
+    #     # <HTML>string</HTML>
+    # 
+    #   html("DOCTYPE" => '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">') { "string" }
+    #     # <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"><HTML>string</HTML>
+    # 
+    #   html("PRETTY" => "  ") { "<BODY></BODY>" }
+    #     # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+    #     # <HTML>
+    #     #   <BODY>
+    #     #   </BODY>
+    #     # </HTML>
+    # 
+    #   html("PRETTY" => "\t") { "<BODY></BODY>" }
+    #     # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+    #     # <HTML>
+    #     #         <BODY>
+    #     #         </BODY>
+    #     # </HTML>
+    # 
+    #   html("PRETTY") { "<BODY></BODY>" }
+    #     # = html("PRETTY" => "  ") { "<BODY></BODY>" }
+    # 
+    #   html(if $VERBOSE then "PRETTY" end) { "HTML string" }
+    #
+    def html(attributes = {}) # :yield:
+      if nil == attributes
+        attributes = {}
+      elsif "PRETTY" == attributes
+        attributes = { "PRETTY" => true }
+      end
+      pretty = attributes.delete("PRETTY")
+      pretty = "  " if true == pretty
+      buf = ""
+
+      if attributes.has_key?("DOCTYPE")
+        if attributes["DOCTYPE"]
+          buf += attributes.delete("DOCTYPE")
+        else
+          attributes.delete("DOCTYPE")
+        end
+      else
+        buf += doctype
+      end
+
+      if block_given?
+        buf += super(attributes){ yield }
+      else
+        buf += super(attributes)
+      end
+
+      if pretty
+        CGI::pretty(buf, pretty)
+      else
+        buf
+      end
+
+    end
+
+    # Generate an Image Button Input element as a string.
+    #
+    # +src+ is the URL of the image to use for the button.  +name+ 
+    # is the input name.  +alt+ is the alternative text for the image.
+    #
+    # Alternatively, the attributes can be specified as a hash.
+    # 
+    #   image_button("url")
+    #     # <INPUT TYPE="image" SRC="url">
+    # 
+    #   image_button("url", "name", "string")
+    #     # <INPUT TYPE="image" SRC="url" NAME="name" ALT="string">
+    # 
+    #   image_button("SRC" => "url", "ATL" => "strng")
+    #     # <INPUT TYPE="image" SRC="url" ALT="string">
+    def image_button(src = "", name = nil, alt = nil)
+      attributes = if src.kind_of?(String)
+                     { "TYPE" => "image", "SRC" => src, "NAME" => name,
+                       "ALT" => alt }
+                   else
+                     src["TYPE"] = "image"
+                     src["SRC"] ||= ""
+                     src
+                   end
+      input(attributes)
+    end
+
+
+    # Generate an Image element as a string.
+    #
+    # +src+ is the URL of the image.  +alt+ is the alternative text for
+    # the image.  +width+ is the width of the image, and +height+ is
+    # its height.
+    #
+    # Alternatively, the attributes can be specified as a hash.
+    #
+    #   img("src", "alt", 100, 50)
+    #     # <IMG SRC="src" ALT="alt" WIDTH="100" HEIGHT="50">
+    # 
+    #   img("SRC" => "src", "ALT" => "alt", "WIDTH" => 100, "HEIGHT" => 50)
+    #     # <IMG SRC="src" ALT="alt" WIDTH="100" HEIGHT="50">
+    def img(src = "", alt = "", width = nil, height = nil)
+      attributes = if src.kind_of?(String)
+                     { "SRC" => src, "ALT" => alt }
+                   else
+                     src
+                   end
+      attributes["WIDTH"] = width.to_s if width
+      attributes["HEIGHT"] = height.to_s if height
+      super(attributes)
+    end
+
+
+    # Generate a Form element with multipart encoding as a String.
+    #
+    # Multipart encoding is used for forms that include file uploads.
+    #
+    # +action+ is the action to perform.  +enctype+ is the encoding
+    # type, which defaults to "multipart/form-data".
+    #
+    # Alternatively, the attributes can be specified as a hash.
+    #
+    #   multipart_form{ "string" }
+    #     # <FORM METHOD="post" ENCTYPE="multipart/form-data">string</FORM>
+    # 
+    #   multipart_form("url") { "string" }
+    #     # <FORM METHOD="post" ACTION="url" ENCTYPE="multipart/form-data">string</FORM>
+    def multipart_form(action = nil, enctype = "multipart/form-data")
+      attributes = if action == nil
+                     { "METHOD" => "post", "ENCTYPE" => enctype } 
+                   elsif action.kind_of?(String)
+                     { "METHOD" => "post", "ACTION" => action,
+                       "ENCTYPE" => enctype } 
+                   else
+                     unless action.has_key?("METHOD")
+                       action["METHOD"] = "post"
+                     end
+                     unless action.has_key?("ENCTYPE")
+                       action["ENCTYPE"] = enctype
+                     end
+                     action
+                   end
+      if block_given?
+        form(attributes){ yield }
+      else
+        form(attributes)
+      end
+    end
+
+
+    # Generate a Password Input element as a string.
+    #
+    # +name+ is the name of the input field.  +value+ is its default
+    # value.  +size+ is the size of the input field display.  +maxlength+
+    # is the maximum length of the inputted password.
+    #
+    # Alternatively, attributes can be specified as a hash.
+    #
+    #   password_field("name")
+    #     # <INPUT TYPE="password" NAME="name" SIZE="40">
+    # 
+    #   password_field("name", "value")
+    #     # <INPUT TYPE="password" NAME="name" VALUE="value" SIZE="40">
+    # 
+    #   password_field("password", "value", 80, 200)
+    #     # <INPUT TYPE="password" NAME="name" VALUE="value" SIZE="80" MAXLENGTH="200">
+    # 
+    #   password_field("NAME" => "name", "VALUE" => "value")
+    #     # <INPUT TYPE="password" NAME="name" VALUE="value">
+    def password_field(name = "", value = nil, size = 40, maxlength = nil)
+      attributes = if name.kind_of?(String)
+                     { "TYPE" => "password", "NAME" => name,
+                       "VALUE" => value, "SIZE" => size.to_s }
+                   else
+                     name["TYPE"] = "password"
+                     name
+                   end
+      attributes["MAXLENGTH"] = maxlength.to_s if maxlength
+      input(attributes)
+    end
+
+    # Generate a Select element as a string.
+    #
+    # +name+ is the name of the element.  The +values+ are the options that
+    # can be selected from the Select menu.  Each value can be a String or
+    # a one, two, or three-element Array.  If a String or a one-element
+    # Array, this is both the value of that option and the text displayed for
+    # it.  If a three-element Array, the elements are the option value, displayed
+    # text, and a boolean value specifying whether this option starts as selected.
+    # The two-element version omits either the option value (defaults to the same
+    # as the display text) or the boolean selected specifier (defaults to false).
+    #
+    # The attributes and options can also be specified as a hash.  In this
+    # case, options are specified as an array of values as described above,
+    # with the hash key of "VALUES".
+    #
+    #   popup_menu("name", "foo", "bar", "baz")
+    #     # <SELECT NAME="name">
+    #     #   <OPTION VALUE="foo">foo</OPTION>
+    #     #   <OPTION VALUE="bar">bar</OPTION>
+    #     #   <OPTION VALUE="baz">baz</OPTION>
+    #     # </SELECT>
+    # 
+    #   popup_menu("name", ["foo"], ["bar", true], "baz")
+    #     # <SELECT NAME="name">
+    #     #   <OPTION VALUE="foo">foo</OPTION>
+    #     #   <OPTION VALUE="bar" SELECTED>bar</OPTION>
+    #     #   <OPTION VALUE="baz">baz</OPTION>
+    #     # </SELECT>
+    # 
+    #   popup_menu("name", ["1", "Foo"], ["2", "Bar", true], "Baz")
+    #     # <SELECT NAME="name">
+    #     #   <OPTION VALUE="1">Foo</OPTION>
+    #     #   <OPTION SELECTED VALUE="2">Bar</OPTION>
+    #     #   <OPTION VALUE="Baz">Baz</OPTION>
+    #     # </SELECT>
+    # 
+    #   popup_menu("NAME" => "name", "SIZE" => 2, "MULTIPLE" => true,
+    #               "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])
+    #     # <SELECT NAME="name" MULTIPLE SIZE="2">
+    #     #   <OPTION VALUE="1">Foo</OPTION>
+    #     #   <OPTION SELECTED VALUE="2">Bar</OPTION>
+    #     #   <OPTION VALUE="Baz">Baz</OPTION>
+    #     # </SELECT>
+    def popup_menu(name = "", *values)
+
+      if name.kind_of?(Hash)
+        values   = name["VALUES"]
+        size     = name["SIZE"].to_s if name["SIZE"]
+        multiple = name["MULTIPLE"]
+        name     = name["NAME"]
+      else
+        size = nil
+        multiple = nil
+      end
+
+      select({ "NAME" => name, "SIZE" => size,
+               "MULTIPLE" => multiple }){
+        values.collect{|value|
+          if value.kind_of?(String)
+            option({ "VALUE" => value }){ value }
+          else
+            if value[value.bytesize - 1] == true
+              option({ "VALUE" => value[0], "SELECTED" => true }){
+                value[value.bytesize - 2]
+              }
+            else
+              option({ "VALUE" => value[0] }){
+                value[value.bytesize - 1]
+              }
+            end
+          end
+        }.join
+      }
+
+    end
+
+    # Generates a radio-button Input element.
+    #
+    # +name+ is the name of the input field.  +value+ is the value of
+    # the field if checked.  +checked+ specifies whether the field
+    # starts off checked.
+    #
+    # Alternatively, the attributes can be specified as a hash.
+    #
+    #   radio_button("name", "value")
+    #     # <INPUT TYPE="radio" NAME="name" VALUE="value">
+    # 
+    #   radio_button("name", "value", true)
+    #     # <INPUT TYPE="radio" NAME="name" VALUE="value" CHECKED>
+    # 
+    #   radio_button("NAME" => "name", "VALUE" => "value", "ID" => "foo")
+    #     # <INPUT TYPE="radio" NAME="name" VALUE="value" ID="foo">
+    def radio_button(name = "", value = nil, checked = nil)
+      attributes = if name.kind_of?(String)
+                     { "TYPE" => "radio", "NAME" => name,
+                       "VALUE" => value, "CHECKED" => checked }
+                   else
+                     name["TYPE"] = "radio"
+                     name
+                   end
+      input(attributes)
+    end
+
+    # Generate a sequence of radio button Input elements, as a String.
+    #
+    # This works the same as #checkbox_group().  However, it is not valid
+    # to have more than one radiobutton in a group checked.
+    # 
+    #   radio_group("name", "foo", "bar", "baz")
+    #     # <INPUT TYPE="radio" NAME="name" VALUE="foo">foo
+    #     # <INPUT TYPE="radio" NAME="name" VALUE="bar">bar
+    #     # <INPUT TYPE="radio" NAME="name" VALUE="baz">baz
+    # 
+    #   radio_group("name", ["foo"], ["bar", true], "baz")
+    #     # <INPUT TYPE="radio" NAME="name" VALUE="foo">foo
+    #     # <INPUT TYPE="radio" CHECKED NAME="name" VALUE="bar">bar
+    #     # <INPUT TYPE="radio" NAME="name" VALUE="baz">baz
+    # 
+    #   radio_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz")
+    #     # <INPUT TYPE="radio" NAME="name" VALUE="1">Foo
+    #     # <INPUT TYPE="radio" CHECKED NAME="name" VALUE="2">Bar
+    #     # <INPUT TYPE="radio" NAME="name" VALUE="Baz">Baz
+    # 
+    #   radio_group("NAME" => "name",
+    #                 "VALUES" => ["foo", "bar", "baz"])
+    # 
+    #   radio_group("NAME" => "name",
+    #                 "VALUES" => [["foo"], ["bar", true], "baz"])
+    # 
+    #   radio_group("NAME" => "name",
+    #                 "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])
+    def radio_group(name = "", *values)
+      if name.kind_of?(Hash)
+        values = name["VALUES"]
+        name = name["NAME"]
+      end
+      values.collect{|value|
+        if value.kind_of?(String)
+          radio_button(name, value) + value
+        else
+          if value[value.bytesize - 1] == true
+            radio_button(name, value[0], true) +
+            value[value.bytesize - 2]
+          else
+            radio_button(name, value[0]) +
+            value[value.bytesize - 1]
+          end
+        end
+      }.join
+    end
+
+    # Generate a reset button Input element, as a String.
+    #
+    # This resets the values on a form to their initial values.  +value+
+    # is the text displayed on the button. +name+ is the name of this button.
+    #
+    # Alternatively, the attributes can be specified as a hash.
+    #
+    #   reset
+    #     # <INPUT TYPE="reset">
+    # 
+    #   reset("reset")
+    #     # <INPUT TYPE="reset" VALUE="reset">
+    # 
+    #   reset("VALUE" => "reset", "ID" => "foo")
+    #     # <INPUT TYPE="reset" VALUE="reset" ID="foo">
+    def reset(value = nil, name = nil)
+      attributes = if (not value) or value.kind_of?(String)
+                     { "TYPE" => "reset", "VALUE" => value, "NAME" => name }
+                   else
+                     value["TYPE"] = "reset"
+                     value
+                   end
+      input(attributes)
+    end
+
+    alias scrolling_list popup_menu
+
+    # Generate a submit button Input element, as a String.
+    #
+    # +value+ is the text to display on the button.  +name+ is the name
+    # of the input.
+    #
+    # Alternatively, the attributes can be specified as a hash.
+    #
+    #   submit
+    #     # <INPUT TYPE="submit">
+    # 
+    #   submit("ok")
+    #     # <INPUT TYPE="submit" VALUE="ok">
+    # 
+    #   submit("ok", "button1")
+    #     # <INPUT TYPE="submit" VALUE="ok" NAME="button1">
+    # 
+    #   submit("VALUE" => "ok", "NAME" => "button1", "ID" => "foo")
+    #     # <INPUT TYPE="submit" VALUE="ok" NAME="button1" ID="foo">
+    def submit(value = nil, name = nil)
+      attributes = if (not value) or value.kind_of?(String)
+                     { "TYPE" => "submit", "VALUE" => value, "NAME" => name }
+                   else
+                     value["TYPE"] = "submit"
+                     value
+                   end
+      input(attributes)
+    end
+
+    # Generate a text field Input element, as a String.
+    #
+    # +name+ is the name of the input field.  +value+ is its initial
+    # value.  +size+ is the size of the input area.  +maxlength+
+    # is the maximum length of input accepted.
+    #
+    # Alternatively, the attributes can be specified as a hash.
+    #
+    #   text_field("name")
+    #     # <INPUT TYPE="text" NAME="name" SIZE="40">
+    # 
+    #   text_field("name", "value")
+    #     # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="40">
+    # 
+    #   text_field("name", "value", 80)
+    #     # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="80">
+    # 
+    #   text_field("name", "value", 80, 200)
+    #     # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="80" MAXLENGTH="200">
+    # 
+    #   text_field("NAME" => "name", "VALUE" => "value")
+    #     # <INPUT TYPE="text" NAME="name" VALUE="value">
+    def text_field(name = "", value = nil, size = 40, maxlength = nil)
+      attributes = if name.kind_of?(String)
+                     { "TYPE" => "text", "NAME" => name, "VALUE" => value,
+                       "SIZE" => size.to_s }
+                   else
+                     name["TYPE"] = "text"
+                     name
+                   end
+      attributes["MAXLENGTH"] = maxlength.to_s if maxlength
+      input(attributes)
+    end
+
+    # Generate a TextArea element, as a String.
+    #
+    # +name+ is the name of the textarea.  +cols+ is the number of
+    # columns and +rows+ is the number of rows in the display.
+    #
+    # Alternatively, the attributes can be specified as a hash.
+    #
+    # The body is provided by the passed-in no-argument block
+    #
+    #   textarea("name")
+    #      # = textarea("NAME" => "name", "COLS" => 70, "ROWS" => 10)
+    #
+    #   textarea("name", 40, 5)
+    #      # = textarea("NAME" => "name", "COLS" => 40, "ROWS" => 5)
+    def textarea(name = "", cols = 70, rows = 10)  # :yield:
+      attributes = if name.kind_of?(String)
+                     { "NAME" => name, "COLS" => cols.to_s,
+                       "ROWS" => rows.to_s }
+                   else
+                     name
+                   end
+      if block_given?
+        super(attributes){ yield }
+      else
+        super(attributes)
+      end
+    end
+
+  end # HtmlExtension
+
+
+  # Mixin module for HTML version 3 generation methods.
+  module Html3 # :nodoc:
+
+    # The DOCTYPE declaration for this version of HTML
+    def doctype
+      %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">|
+    end
+
+    # Initialise the HTML generation methods for this version.
+    def element_init
+      extend TagMaker
+      methods = ""
+      # - -
+      for element in %w[ A TT I B U STRIKE BIG SMALL SUB SUP EM STRONG
+          DFN CODE SAMP KBD VAR CITE FONT ADDRESS DIV center MAP
+          APPLET PRE XMP LISTING DL OL UL DIR MENU SELECT table TITLE
+          STYLE SCRIPT H1 H2 H3 H4 H5 H6 TEXTAREA FORM BLOCKQUOTE
+          CAPTION ]
+        methods += <<-BEGIN + nn_element_def(element) + <<-END
+          def #{element.downcase}(attributes = {})
+        BEGIN
+          end
+        END
+      end
+
+      # - O EMPTY
+      for element in %w[ IMG BASE BASEFONT BR AREA LINK PARAM HR INPUT
+          ISINDEX META ]
+        methods += <<-BEGIN + nOE_element_def(element) + <<-END
+          def #{element.downcase}(attributes = {})
+        BEGIN
+          end
+        END
+      end
+
+      # O O or - O
+      for element in %w[ HTML HEAD BODY P PLAINTEXT DT DD LI OPTION tr
+          th td ]
+        methods += <<-BEGIN + nO_element_def(element) + <<-END
+          def #{element.downcase}(attributes = {})
+        BEGIN
+          end
+        END
+      end
+      eval(methods)
+    end
+
+  end # Html3
+
+
+  # Mixin module for HTML version 4 generation methods.
+  module Html4 # :nodoc:
+
+    # The DOCTYPE declaration for this version of HTML
+    def doctype
+      %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">|
+    end
+
+    # Initialise the HTML generation methods for this version.
+    def element_init
+      extend TagMaker
+      methods = ""
+      # - -
+      for element in %w[ TT I B BIG SMALL EM STRONG DFN CODE SAMP KBD
+        VAR CITE ABBR ACRONYM SUB SUP SPAN BDO ADDRESS DIV MAP OBJECT
+        H1 H2 H3 H4 H5 H6 PRE Q INS DEL DL OL UL LABEL SELECT OPTGROUP
+        FIELDSET LEGEND BUTTON TABLE TITLE STYLE SCRIPT NOSCRIPT
+        TEXTAREA FORM A BLOCKQUOTE CAPTION ]
+        methods += <<-BEGIN + nn_element_def(element) + <<-END
+          def #{element.downcase}(attributes = {})
+        BEGIN
+          end
+        END
+      end
+
+      # - O EMPTY
+      for element in %w[ IMG BASE BR AREA LINK PARAM HR INPUT COL META ]
+        methods += <<-BEGIN + nOE_element_def(element) + <<-END
+          def #{element.downcase}(attributes = {})
+        BEGIN
+          end
+        END
+      end
+
+      # O O or - O
+      for element in %w[ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY
+          COLGROUP TR TH TD HEAD]
+        methods += <<-BEGIN + nO_element_def(element) + <<-END
+          def #{element.downcase}(attributes = {})
+        BEGIN
+          end
+        END
+      end
+      eval(methods)
+    end
+
+  end # Html4
+
+
+  # Mixin module for HTML version 4 transitional generation methods.
+  module Html4Tr # :nodoc:
+
+    # The DOCTYPE declaration for this version of HTML
+    def doctype
+      %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">|
+    end
+
+    # Initialise the HTML generation methods for this version.
+    def element_init
+      extend TagMaker
+      methods = ""
+      # - -
+      for element in %w[ TT I B U S STRIKE BIG SMALL EM STRONG DFN
+          CODE SAMP KBD VAR CITE ABBR ACRONYM FONT SUB SUP SPAN BDO
+          ADDRESS DIV CENTER MAP OBJECT APPLET H1 H2 H3 H4 H5 H6 PRE Q
+          INS DEL DL OL UL DIR MENU LABEL SELECT OPTGROUP FIELDSET
+          LEGEND BUTTON TABLE IFRAME NOFRAMES TITLE STYLE SCRIPT
+          NOSCRIPT TEXTAREA FORM A BLOCKQUOTE CAPTION ]
+        methods += <<-BEGIN + nn_element_def(element) + <<-END
+          def #{element.downcase}(attributes = {})
+        BEGIN
+          end
+        END
+      end
+
+      # - O EMPTY
+      for element in %w[ IMG BASE BASEFONT BR AREA LINK PARAM HR INPUT
+          COL ISINDEX META ]
+        methods += <<-BEGIN + nOE_element_def(element) + <<-END
+          def #{element.downcase}(attributes = {})
+        BEGIN
+          end
+        END
+      end
+
+      # O O or - O
+      for element in %w[ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY
+          COLGROUP TR TH TD HEAD ]
+        methods += <<-BEGIN + nO_element_def(element) + <<-END
+          def #{element.downcase}(attributes = {})
+        BEGIN
+          end
+        END
+      end
+      eval(methods)
+    end
+
+  end # Html4Tr
+
+
+  # Mixin module for generating HTML version 4 with framesets.
+  module Html4Fr # :nodoc:
+
+    # The DOCTYPE declaration for this version of HTML
+    def doctype
+      %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">|
+    end
+
+    # Initialise the HTML generation methods for this version.
+    def element_init
+      methods = ""
+      # - -
+      for element in %w[ FRAMESET ]
+        methods += <<-BEGIN + nn_element_def(element) + <<-END
+          def #{element.downcase}(attributes = {})
+        BEGIN
+          end
+        END
+      end
+
+      # - O EMPTY
+      for element in %w[ FRAME ]
+        methods += <<-BEGIN + nOE_element_def(element) + <<-END
+          def #{element.downcase}(attributes = {})
+        BEGIN
+          end
+        END
+      end
+      eval(methods)
+    end
+
+  end # Html4Fr
+end
+
+
Index: lib/cgi/core.rb
===================================================================
--- lib/cgi/core.rb	(revision 0)
+++ lib/cgi/core.rb	(revision 19272)
@@ -0,0 +1,710 @@
+class CGI
+
+  # :stopdoc:
+
+  # String for carriage return
+  CR  = "\015"
+
+  # String for linefeed
+  LF  = "\012"
+
+  # Standard internet newline sequence
+  EOL = CR + LF
+
+  REVISION = '$Id: cgi.rb 19245 2008-09-08 13:27:17Z xibbar $' #:nodoc:
+
+  NEEDS_BINMODE = true if /WIN/i.match(RUBY_PLATFORM) 
+
+  # Path separators in different environments.
+  PATH_SEPARATOR = {'UNIX'=>'/', 'WINDOWS'=>'\\', 'MACINTOSH'=>':'}
+
+  # HTTP status codes.
+  HTTP_STATUS = {
+    "OK"                  => "200 OK",
+    "PARTIAL_CONTENT"     => "206 Partial Content",
+    "MULTIPLE_CHOICES"    => "300 Multiple Choices",
+    "MOVED"               => "301 Moved Permanently",
+    "REDIRECT"            => "302 Found",
+    "NOT_MODIFIED"        => "304 Not Modified",
+    "BAD_REQUEST"         => "400 Bad Request",
+    "AUTH_REQUIRED"       => "401 Authorization Required",
+    "FORBIDDEN"           => "403 Forbidden",
+    "NOT_FOUND"           => "404 Not Found",
+    "METHOD_NOT_ALLOWED"  => "405 Method Not Allowed",
+    "NOT_ACCEPTABLE"      => "406 Not Acceptable",
+    "LENGTH_REQUIRED"     => "411 Length Required",
+    "PRECONDITION_FAILED" => "412 Rrecondition Failed",
+    "SERVER_ERROR"        => "500 Internal Server Error",
+    "NOT_IMPLEMENTED"     => "501 Method Not Implemented",
+    "BAD_GATEWAY"         => "502 Bad Gateway",
+    "VARIANT_ALSO_VARIES" => "506 Variant Also Negotiates"
+  }
+
+  # Abbreviated day-of-week names specified by RFC 822
+  RFC822_DAYS = %w[ Sun Mon Tue Wed Thu Fri Sat ]
+
+  # Abbreviated month names specified by RFC 822
+  RFC822_MONTHS = %w[ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ]
+
+  # :startdoc:
+
+  def env_table 
+    ENV
+  end
+
+  def stdinput
+    $stdin
+  end
+
+  def stdoutput
+    $stdout
+  end
+
+  private :env_table, :stdinput, :stdoutput
+
+
+  # Create an HTTP header block as a string.
+  #
+  # Includes the empty line that ends the header block.
+  #
+  # +options+ can be a string specifying the Content-Type (defaults
+  # to text/html), or a hash of header key/value pairs.  The following
+  # header keys are recognized:
+  #
+  # type:: the Content-Type header.  Defaults to "text/html"
+  # charset:: the charset of the body, appended to the Content-Type header.
+  # nph:: a boolean value.  If true, prepend protocol string and status code, and
+  #       date; and sets default values for "server" and "connection" if not
+  #       explicitly set.
+  # status:: the HTTP status code, returned as the Status header.  See the
+  #          list of available status codes below.
+  # server:: the server software, returned as the Server header.
+  # connection:: the connection type, returned as the Connection header (for 
+  #              instance, "close".
+  # length:: the length of the content that will be sent, returned as the
+  #          Content-Length header.
+  # language:: the language of the content, returned as the Content-Language
+  #            header.
+  # expires:: the time on which the current content expires, as a +Time+
+  #           object, returned as the Expires header.
+  # cookie:: a cookie or cookies, returned as one or more Set-Cookie headers.
+  #          The value can be the literal string of the cookie; a CGI::Cookie
+  #          object; an Array of literal cookie strings or Cookie objects; or a 
+  #          hash all of whose values are literal cookie strings or Cookie objects.
+  #          These cookies are in addition to the cookies held in the
+  #          @output_cookies field.
+  #
+  # Other header lines can also be set; they are appended as key: value.
+  # 
+  #   header
+  #     # Content-Type: text/html
+  # 
+  #   header("text/plain")
+  #     # Content-Type: text/plain
+  # 
+  #   header("nph"        => true,
+  #          "status"     => "OK",  # == "200 OK"
+  #            # "status"     => "200 GOOD",
+  #          "server"     => ENV['SERVER_SOFTWARE'],
+  #          "connection" => "close",
+  #          "type"       => "text/html",
+  #          "charset"    => "iso-2022-jp",
+  #            # Content-Type: text/html; charset=iso-2022-jp
+  #          "length"     => 103,
+  #          "language"   => "ja",
+  #          "expires"    => Time.now + 30,
+  #          "cookie"     => [cookie1, cookie2],
+  #          "my_header1" => "my_value"
+  #          "my_header2" => "my_value")
+  # 
+  # The status codes are:
+  # 
+  #   "OK"                  --> "200 OK"
+  #   "PARTIAL_CONTENT"     --> "206 Partial Content"
+  #   "MULTIPLE_CHOICES"    --> "300 Multiple Choices"
+  #   "MOVED"               --> "301 Moved Permanently"
+  #   "REDIRECT"            --> "302 Found"
+  #   "NOT_MODIFIED"        --> "304 Not Modified"
+  #   "BAD_REQUEST"         --> "400 Bad Request"
+  #   "AUTH_REQUIRED"       --> "401 Authorization Required"
+  #   "FORBIDDEN"           --> "403 Forbidden"
+  #   "NOT_FOUND"           --> "404 Not Found"
+  #   "METHOD_NOT_ALLOWED"  --> "405 Method Not Allowed"
+  #   "NOT_ACCEPTABLE"      --> "406 Not Acceptable"
+  #   "LENGTH_REQUIRED"     --> "411 Length Required"
+  #   "PRECONDITION_FAILED" --> "412 Precondition Failed"
+  #   "SERVER_ERROR"        --> "500 Internal Server Error"
+  #   "NOT_IMPLEMENTED"     --> "501 Method Not Implemented"
+  #   "BAD_GATEWAY"         --> "502 Bad Gateway"
+  #   "VARIANT_ALSO_VARIES" --> "506 Variant Also Negotiates"
+  # 
+  # This method does not perform charset conversion. 
+  #
+  def header(options = "text/html")
+
+    buf = ""
+
+    case options
+    when String
+      options = { "type" => options }
+    when Hash
+      options = options.dup
+    end
+
+    unless options.has_key?("type")
+      options["type"] = "text/html"
+    end
+
+    if options.has_key?("charset")
+      options["type"] += "; charset=" + options.delete("charset")
+    end
+
+    options.delete("nph") if defined?(MOD_RUBY)
+    if options.delete("nph") or
+        (/IIS\/(\d+)/.match(env_table['SERVER_SOFTWARE']) and $1.to_i < 5)
+      buf += (env_table["SERVER_PROTOCOL"] or "HTTP/1.0")  + " " +
+             (HTTP_STATUS[options["status"]] or options["status"] or "200 OK") +
+             EOL +
+             "Date: " + CGI::rfc1123_date(Time.now) + EOL
+
+      unless options.has_key?("server")
+        options["server"] = (env_table['SERVER_SOFTWARE'] or "")
+      end
+
+      unless options.has_key?("connection")
+        options["connection"] = "close"
+      end
+
+      options.delete("status")
+    end
+
+    if options.has_key?("status")
+      buf += "Status: " +
+             (HTTP_STATUS[options["status"]] or options["status"]) + EOL
+      options.delete("status")
+    end
+
+    if options.has_key?("server")
+      buf += "Server: " + options.delete("server") + EOL
+    end
+
+    if options.has_key?("connection")
+      buf += "Connection: " + options.delete("connection") + EOL
+    end
+
+    buf += "Content-Type: " + options.delete("type") + EOL
+
+    if options.has_key?("length")
+      buf += "Content-Length: " + options.delete("length").to_s + EOL
+    end
+
+    if options.has_key?("language")
+      buf += "Content-Language: " + options.delete("language") + EOL
+    end
+
+    if options.has_key?("expires")
+      buf += "Expires: " + CGI::rfc1123_date( options.delete("expires") ) + EOL
+    end
+
+    if options.has_key?("cookie")
+      if options["cookie"].kind_of?(String) or
+           options["cookie"].kind_of?(Cookie)
+        buf += "Set-Cookie: " + options.delete("cookie").to_s + EOL
+      elsif options["cookie"].kind_of?(Array)
+        options.delete("cookie").each{|cookie|
+          buf += "Set-Cookie: " + cookie.to_s + EOL
+        }
+      elsif options["cookie"].kind_of?(Hash)
+        options.delete("cookie").each_value{|cookie|
+          buf += "Set-Cookie: " + cookie.to_s + EOL
+        }
+      end
+    end
+    if @output_cookies
+      for cookie in @output_cookies
+        buf += "Set-Cookie: " + cookie.to_s + EOL
+      end
+    end
+
+    options.each{|key, value|
+      buf += key + ": " + value.to_s + EOL
+    }
+
+    if defined?(MOD_RUBY)
+      table = Apache::request.headers_out
+      buf.scan(/([^:]+): (.+)#{EOL}/){ |name, value|
+        warn sprintf("name:%s value:%s\n", name, value) if $DEBUG
+        case name
+        when 'Set-Cookie'
+          table.add(name, value)
+        when /^status$/i
+          Apache::request.status_line = value
+          Apache::request.status = value.to_i
+        when /^content-type$/i
+          Apache::request.content_type = value
+        when /^content-encoding$/i
+          Apache::request.content_encoding = value
+        when /^location$/i
+	  if Apache::request.status == 200
+	    Apache::request.status = 302
+	  end
+          Apache::request.headers_out[name] = value
+        else
+          Apache::request.headers_out[name] = value
+        end
+      }
+      Apache::request.send_http_header
+      ''
+    else
+      buf + EOL
+    end
+
+  end # header()
+
+
+  # Print an HTTP header and body to $DEFAULT_OUTPUT ($>)
+  #
+  # The header is provided by +options+, as for #header().
+  # The body of the document is that returned by the passed-
+  # in block.  This block takes no arguments.  It is required.
+  #
+  #   cgi = CGI.new
+  #   cgi.out{ "string" }
+  #     # Content-Type: text/html
+  #     # Content-Length: 6
+  #     #
+  #     # string
+  # 
+  #   cgi.out("text/plain") { "string" }
+  #     # Content-Type: text/plain
+  #     # Content-Length: 6
+  #     #
+  #     # string
+  # 
+  #   cgi.out("nph"        => true,
+  #           "status"     => "OK",  # == "200 OK"
+  #           "server"     => ENV['SERVER_SOFTWARE'],
+  #           "connection" => "close",
+  #           "type"       => "text/html",
+  #           "charset"    => "iso-2022-jp",
+  #             # Content-Type: text/html; charset=iso-2022-jp
+  #           "language"   => "ja",
+  #           "expires"    => Time.now + (3600 * 24 * 30),
+  #           "cookie"     => [cookie1, cookie2],
+  #           "my_header1" => "my_value",
+  #           "my_header2" => "my_value") { "string" }
+  # 
+  # Content-Length is automatically calculated from the size of
+  # the String returned by the content block.
+  #
+  # If ENV['REQUEST_METHOD'] == "HEAD", then only the header
+  # is outputted (the content block is still required, but it
+  # is ignored).
+  # 
+  # If the charset is "iso-2022-jp" or "euc-jp" or "shift_jis" then
+  # the content is converted to this charset, and the language is set 
+  # to "ja".
+  def out(options = "text/html") # :yield:
+
+    options = { "type" => options } if options.kind_of?(String)
+    content = yield
+    options["length"] = content.bytesize.to_s
+    output = stdoutput
+    output.binmode if defined? output.binmode
+    output.print header(options)
+    output.print content unless "HEAD" == env_table['REQUEST_METHOD']
+  end
+
+
+  # Print an argument or list of arguments to the default output stream
+  #
+  #   cgi = CGI.new
+  #   cgi.print    # default:  cgi.print == $DEFAULT_OUTPUT.print
+  def print(*options)
+    stdoutput.print(*options)
+  end
+
+  # Parse an HTTP query string into a hash of key=>value pairs.
+  #
+  #   params = CGI::parse("query_string")
+  #     # {"name1" => ["value1", "value2", ...],
+  #     #  "name2" => ["value1", "value2", ...], ... }
+  #
+  def CGI::parse(query)
+    params = Hash.new([].freeze)
+
+    query.split(/[&;]/).each do |pairs|
+      key, value = pairs.split('=',2).collect{|v| CGI::unescape(v) }
+      if params.has_key?(key)
+        params[key].push(value)
+      else
+        params[key] = [value]
+      end
+    end
+
+    params
+  end
+
+  # Mixin module. It provides the follow functionality groups:
+  #
+  # 1. Access to CGI environment variables as methods.  See 
+  #    documentation to the CGI class for a list of these variables.
+  #
+  # 2. Access to cookies, including the cookies attribute.
+  #
+  # 3. Access to parameters, including the params attribute, and overloading
+  #    [] to perform parameter value lookup by key.
+  #
+  # 4. The initialize_query method, for initialising the above
+  #    mechanisms, handling multipart forms, and allowing the
+  #    class to be used in "offline" mode.
+  #
+  module QueryExtension
+
+    %w[ CONTENT_LENGTH SERVER_PORT ].each do |env|
+      define_method(env.sub(/^HTTP_/, '').downcase) do
+        (val = env_table[env]) && Integer(val)
+      end
+    end
+
+    %w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO
+        PATH_TRANSLATED QUERY_STRING REMOTE_ADDR REMOTE_HOST
+        REMOTE_IDENT REMOTE_USER REQUEST_METHOD SCRIPT_NAME
+        SERVER_NAME SERVER_PROTOCOL SERVER_SOFTWARE
+
+        HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
+        HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST
+        HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
+      define_method(env.sub(/^HTTP_/, '').downcase) do
+        env_table[env]
+      end
+    end
+
+    # Get the raw cookies as a string.
+    def raw_cookie
+      env_table["HTTP_COOKIE"]
+    end
+
+    # Get the raw RFC2965 cookies as a string.
+    def raw_cookie2
+      env_table["HTTP_COOKIE2"]
+    end
+
+    # Get the cookies as a hash of cookie-name=>Cookie pairs.
+    attr_accessor :cookies
+
+    # Get the parameters as a hash of name=>values pairs, where
+    # values is an Array.
+    attr_reader :params
+
+    # Set all the parameters.
+    def params=(hash)
+      @params.clear
+      @params.update(hash)
+    end
+
+    def read_multipart(boundary, content_length)
+      params = Hash.new([])
+      boundary = "--" + boundary
+      quoted_boundary = Regexp.quote(boundary)
+      buf = ""
+      bufsize = 10 * 1024
+      boundary_end=""
+
+      # start multipart/form-data
+      stdinput.binmode if defined? stdinput.binmode
+      boundary_size = boundary.bytesize + EOL.bytesize
+      content_length -= boundary_size
+      status = stdinput.read(boundary_size)
+      if nil == status
+        raise EOFError, "no content body"
+      elsif boundary + EOL != status
+        raise EOFError, "bad content body"
+      end
+
+      loop do
+        head = nil
+        body = MorphingBody.new
+
+        until head and /#{quoted_boundary}(?:#{EOL}|--)/.match(buf)
+          if (not head) and /#{EOL}#{EOL}/.match(buf)
+            buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/) do
+              head = $1.dup
+              ""
+            end
+            next
+          end
+
+          if head and ( (EOL + boundary + EOL).bytesize < buf.bytesize )
+            body.print buf[0 ... (buf.bytesize - (EOL + boundary + EOL).bytesize)]
+            buf[0 ... (buf.bytesize - (EOL + boundary + EOL).bytesize)] = ""
+          end
+
+          c = if bufsize < content_length
+                stdinput.read(bufsize)
+              else
+                stdinput.read(content_length)
+              end
+          if c.nil? || c.empty?
+            raise EOFError, "bad content body"
+          end
+          buf.concat(c)
+          content_length -= c.bytesize
+        end
+
+        buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/) do
+          body.print $1
+          if "--" == $2
+            content_length = -1
+          end
+          boundary_end = $2.dup
+          ""
+        end
+
+        body.rewind
+
+        /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;\s]*))/i.match(head)
+	filename = ($1 or $2 or "")
+	if /Mac/i.match(env_table['HTTP_USER_AGENT']) and
+	    /Mozilla/i.match(env_table['HTTP_USER_AGENT']) and
+	    (not /MSIE/i.match(env_table['HTTP_USER_AGENT']))
+	  filename = CGI::unescape(filename)
+	end
+        
+        /Content-Type: ([^\s]*)/i.match(head)
+        content_type = ($1 or "")
+
+        (class << body; self; end).class_eval do
+          alias local_path path
+          define_method(:original_filename) {filename.dup.taint}
+          define_method(:content_type) {content_type.dup.taint}
+        end
+
+        /Content-Disposition:.* name="?([^\";\s]*)"?/i.match(head)
+        name = ($1 || "").dup
+
+        if params.has_key?(name)
+          params[name].push(body)
+        else
+          params[name] = [body]
+        end
+        break if buf.bytesize == 0
+        break if content_length == -1
+      end
+      raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
+
+      params
+    end # read_multipart
+    private :read_multipart
+
+    # offline mode. read name=value pairs on standard input.
+    def read_from_cmdline
+      require "shellwords"
+
+      string = unless ARGV.empty?
+        ARGV.join(' ')
+      else
+        if STDIN.tty?
+          STDERR.print(
+            %|(offline mode: enter name=value pairs on standard input)\n|
+          )
+        end
+        readlines.join(' ').gsub(/\n/, '')
+      end.gsub(/\\=/, '%3D').gsub(/\\&/, '%26')
+
+      words = Shellwords.shellwords(string)
+
+      if words.find{|x| /=/.match(x) }
+        words.join('&')
+      else
+        words.join('+')
+      end
+    end
+    private :read_from_cmdline
+
+    # A wrapper class to use a StringIO object as the body and switch
+    # to a TempFile when the passed threshold is passed.
+    class MorphingBody
+      begin
+        require "stringio"
+        @@small_buffer = lambda{StringIO.new}
+      rescue LoadError
+        require "tempfile"
+        @@small_buffer = lambda{
+          n = Tempfile.new("CGI")
+          n.binmode
+          n
+        }
+      end
+
+      def initialize(morph_threshold = 10240)
+        @threshold = morph_threshold
+        @body = @@small_buffer.call
+        @cur_size = 0
+        @morph_check = true
+      end
+
+      def print(data)
+        if @morph_check && (@cur_size + data.bytesize > @threshold)
+          convert_body
+        end
+        @body.print data
+      end
+      def rewind
+        @body.rewind
+      end
+      def path
+        @body.path
+      end
+
+      # returns the true body object.
+      def extract
+        @body
+      end
+
+      private
+      def convert_body
+        new_body = TempFile.new("CGI")
+        new_body.binmode if defined? @body.binmode
+        new_body.binmode if defined? new_body.binmode
+
+        @body.rewind
+        new_body.print @body.read
+        @body = new_body
+        @morph_check = false
+      end
+    end
+
+    # Initialize the data from the query.
+    #
+    # Handles multipart forms (in particular, forms that involve file uploads).
+    # Reads query parameters in the @params field, and cookies into @cookies.
+    def initialize_query()
+      if ("POST" == env_table['REQUEST_METHOD']) and
+         %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|.match(env_table['CONTENT_TYPE'])
+        boundary = $1.dup
+        @multipart = true
+        @params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH']))
+      else
+        @multipart = false
+        @params = CGI::parse(
+                    case env_table['REQUEST_METHOD']
+                    when "GET", "HEAD"
+                      if defined?(MOD_RUBY)
+                        Apache::request.args or ""
+                      else
+                        env_table['QUERY_STRING'] or ""
+                      end
+                    when "POST"
+                      stdinput.binmode if defined? stdinput.binmode
+                      stdinput.read(Integer(env_table['CONTENT_LENGTH'])) or ''
+                    else
+                      read_from_cmdline
+                    end
+                  )
+      end
+
+      @cookies = CGI::Cookie::parse((env_table['HTTP_COOKIE'] or env_table['COOKIE']))
+    end
+    private :initialize_query
+
+    def multipart?
+      @multipart
+    end
+
+    # Get the value for the parameter with a given key.
+    #
+    # If the parameter has multiple values, only the first will be 
+    # retrieved; use #params() to get the array of values.
+    def [](key)
+      params = @params[key]
+      return '' unless params
+      value = params[0]
+      if @multipart
+        if value
+          return value
+        elsif defined? StringIO
+          StringIO.new("")
+        else
+          Tempfile.new("CGI")
+        end
+      else
+        str = if value then value.dup else "" end
+        str
+      end
+    end
+
+    # Return all parameter keys as an array.
+    def keys(*args)
+      @params.keys(*args)
+    end
+
+    # Returns true if a given parameter key exists in the query.
+    def has_key?(*args)
+      @params.has_key?(*args)
+    end
+    alias key? has_key?
+    alias include? has_key?
+
+  end # QueryExtension
+
+
+  # Creates a new CGI instance.
+  #
+  # +type+ specifies which version of HTML to load the HTML generation
+  # methods for.  The following versions of HTML are supported:
+  #
+  # html3:: HTML 3.x
+  # html4:: HTML 4.0
+  # html4Tr:: HTML 4.0 Transitional
+  # html4Fr:: HTML 4.0 with Framesets
+  #
+  # If not specified, no HTML generation methods will be loaded.
+  #
+  # If the CGI object is not created in a standard CGI call environment
+  # (that is, it can't locate REQUEST_METHOD in its environment), then
+  # it will run in "offline" mode.  In this mode, it reads its parameters
+  # from the command line or (failing that) from standard input.  Otherwise,
+  # cookies and other parameters are parsed automatically from the standard
+  # CGI locations, which varies according to the REQUEST_METHOD.
+  def initialize(type = "query")
+    if defined?(MOD_RUBY) && !ENV.key?("GATEWAY_INTERFACE")
+      Apache.request.setup_cgi_env
+    end
+
+    extend QueryExtension
+    @multipart = false
+
+    initialize_query()  # set @params, @cookies
+    @output_cookies = nil
+    @output_hidden = nil
+
+    case type
+    when "html3"
+      require 'cgi/html'
+      extend Html3
+      element_init()
+      extend HtmlExtension
+    when "html4"
+      require 'cgi/html'
+      extend Html4
+      element_init()
+      extend HtmlExtension
+    when "html4Tr"
+      require 'cgi/html'
+      extend Html4Tr
+      element_init()
+      extend HtmlExtension
+    when "html4Fr"
+      require 'cgi/html'
+      extend Html4Tr
+      element_init()
+      extend Html4Fr
+      element_init()
+      extend HtmlExtension
+    end
+  end
+
+end   # class CGI
+
+
Index: lib/cgi/cookie.rb
===================================================================
--- lib/cgi/cookie.rb	(revision 0)
+++ lib/cgi/cookie.rb	(revision 19272)
@@ -0,0 +1,156 @@
+  # Class representing an HTTP cookie.
+  #
+  # In addition to its specific fields and methods, a Cookie instance
+  # is a delegator to the array of its values.
+  #
+  # See RFC 2965.
+  #
+  # == Examples of use
+  #   cookie1 = CGI::Cookie::new("name", "value1", "value2", ...)
+  #   cookie1 = CGI::Cookie::new("name" => "name", "value" => "value")
+  #   cookie1 = CGI::Cookie::new('name'    => 'name',
+  #                              'value'   => ['value1', 'value2', ...],
+  #                              'path'    => 'path',   # optional
+  #                              'domain'  => 'domain', # optional
+  #                              'expires' => Time.now, # optional
+  #                              'secure'  => true      # optional
+  #                             )
+  # 
+  #   cgi.out("cookie" => [cookie1, cookie2]) { "string" }
+  # 
+  #   name    = cookie1.name
+  #   values  = cookie1.value
+  #   path    = cookie1.path
+  #   domain  = cookie1.domain
+  #   expires = cookie1.expires
+  #   secure  = cookie1.secure
+  # 
+  #   cookie1.name    = 'name'
+  #   cookie1.value   = ['value1', 'value2', ...]
+  #   cookie1.path    = 'path'
+  #   cookie1.domain  = 'domain'
+  #   cookie1.expires = Time.now + 30
+  #   cookie1.secure  = true
+class CGI
+  class Cookie < Array
+
+    # Create a new CGI::Cookie object.
+    #
+    # The contents of the cookie can be specified as a +name+ and one
+    # or more +value+ arguments.  Alternatively, the contents can
+    # be specified as a single hash argument.  The possible keywords of
+    # this hash are as follows:
+    #
+    # name:: the name of the cookie.  Required.
+    # value:: the cookie's value or list of values.
+    # path:: the path for which this cookie applies.  Defaults to the
+    #        base directory of the CGI script.
+    # domain:: the domain for which this cookie applies.
+    # expires:: the time at which this cookie expires, as a +Time+ object.
+    # secure:: whether this cookie is a secure cookie or not (default to
+    #          false).  Secure cookies are only transmitted to HTTPS 
+    #          servers.
+    #
+    # These keywords correspond to attributes of the cookie object.
+    def initialize(name = "", *value)
+      if name.kind_of?(String)
+        @name = name
+        @value = value
+        %r|^(.*/)|.match(ENV["SCRIPT_NAME"])
+        @path = ($1 or "")
+        @secure = false
+        return super(@value)
+      end
+
+      options = name
+      unless options.has_key?("name")
+        raise ArgumentError, "`name' required"
+      end
+
+      @name = options["name"]
+      @value = Array(options["value"])
+      # simple support for IE
+      if options["path"]
+        @path = options["path"]
+      else
+        %r|^(.*/)|.match(ENV["SCRIPT_NAME"])
+        @path = ($1 or "")
+      end
+      @domain = options["domain"]
+      @expires = options["expires"]
+      @secure = options["secure"] == true ? true : false
+
+      super(@value)
+    end
+
+    attr_accessor("name", "value", "path", "domain", "expires")
+    attr_reader("secure")
+
+    # Set whether the Cookie is a secure cookie or not.
+    #
+    # +val+ must be a boolean.
+    def secure=(val)
+      @secure = val if val == true or val == false
+      @secure
+    end
+
+    # Convert the Cookie to its string representation.
+    def to_s
+      buf = ""
+      buf += @name + '='
+
+      if @value.kind_of?(String)
+        buf += CGI::escape(@value)
+      else
+        buf += @value.collect{|v| CGI::escape(v) }.join("&")
+      end
+
+      if @domain
+        buf += '; domain=' + @domain
+      end
+
+      if @path
+        buf += '; path=' + @path
+      end
+
+      if @expires
+        buf += '; expires=' + CGI::rfc1123_date(@expires)
+      end
+
+      if @secure == true
+        buf += '; secure'
+      end
+
+      buf
+    end
+
+  end # class Cookie
+
+
+  # Parse a raw cookie string into a hash of cookie-name=>Cookie
+  # pairs.
+  #
+  #   cookies = CGI::Cookie::parse("raw_cookie_string")
+  #     # { "name1" => cookie1, "name2" => cookie2, ... }
+  #
+  def Cookie::parse(raw_cookie)
+    cookies = Hash.new([])
+    return cookies unless raw_cookie
+
+    raw_cookie.split(/[;,]\s?/).each do |pairs|
+      name, values = pairs.split('=',2)
+      next unless name and values
+      name = CGI::unescape(name)
+      values ||= ""
+      values = values.split('&').collect{|v| CGI::unescape(v) }
+      if cookies.has_key?(name)
+        values = cookies[name].value + values
+      end
+      cookies[name] = Cookie::new(name, *values)
+    end
+
+    cookies
+  end
+end
+
+

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]