Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/ruby/lib/cgi
diff options
context:
space:
mode:
Diffstat (limited to 'ruby/lib/cgi')
-rw-r--r--ruby/lib/cgi/cookie.rb144
-rw-r--r--ruby/lib/cgi/core.rb786
-rw-r--r--ruby/lib/cgi/html.rb1021
-rw-r--r--ruby/lib/cgi/session.rb537
-rw-r--r--ruby/lib/cgi/session/pstore.rb111
-rw-r--r--ruby/lib/cgi/util.rb181
6 files changed, 2780 insertions, 0 deletions
diff --git a/ruby/lib/cgi/cookie.rb b/ruby/lib/cgi/cookie.rb
new file mode 100644
index 0000000..4e8d3e2
--- /dev/null
+++ b/ruby/lib/cgi/cookie.rb
@@ -0,0 +1,144 @@
+# 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
+ %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", "path", "domain", "expires")
+ attr_reader("secure")
+
+ def value
+ self
+ end
+
+ def value=(val)
+ replace(Array(val))
+ end
+
+ # 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
+ val = collect{|v| CGI::escape(v) }.join("&")
+ buf = "#{@name}=#{val}"
+ buf << "; domain=#{@domain}" if @domain
+ buf << "; path=#{@path}" if @path
+ buf << "; expires=#{CGI::rfc1123_date(@expires)}" if @expires
+ buf << "; secure" if @secure == true
+ 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
+
+
diff --git a/ruby/lib/cgi/core.rb b/ruby/lib/cgi/core.rb
new file mode 100644
index 0000000..4521d87
--- /dev/null
+++ b/ruby/lib/cgi/core.rb
@@ -0,0 +1,786 @@
+class CGI
+
+ $CGI_ENV = ENV # for FCGI support
+
+ # String for carriage return
+ CR = "\015"
+
+ # String for linefeed
+ LF = "\012"
+
+ # Standard internet newline sequence
+ EOL = CR + LF
+
+ REVISION = '$Id: core.rb 23760 2009-06-20 09:06:49Z yugui $' #: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 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"
+ }
+
+ # 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')
+ if options.is_a?(String)
+ content_type = options
+ buf = _header_for_string(content_type)
+ elsif options.is_a?(Hash)
+ if options.size == 1 && options.has_key?('type')
+ content_type = options['type']
+ buf = _header_for_string(content_type)
+ else
+ buf = _header_for_hash(options.dup)
+ end
+ else
+ raise ArgumentError.new("expected String or Hash but got #{options.class}")
+ end
+ if defined?(MOD_RUBY)
+ _header_for_modruby(buf)
+ return ''
+ else
+ buf << EOL # empty line of separator
+ return buf
+ end
+ end # header()
+
+ def _header_for_string(content_type) #:nodoc:
+ buf = ''
+ if nph?()
+ buf << "#{$CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0'} 200 OK#{EOL}"
+ buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
+ buf << "Server: #{$CGI_ENV['SERVER_SOFTWARE']}#{EOL}"
+ buf << "Connection: close#{EOL}"
+ end
+ buf << "Content-Type: #{content_type}#{EOL}"
+ if @output_cookies
+ @output_cookies.each {|cookie| buf << "Set-Cookie: #{cookie}#{EOL}" }
+ end
+ return buf
+ end # _header_for_string
+ private :_header_for_string
+
+ def _header_for_hash(options) #:nodoc:
+ buf = ''
+ ## add charset to option['type']
+ options['type'] ||= 'text/html'
+ charset = options.delete('charset')
+ options['type'] += "; charset=#{charset}" if charset
+ ## NPH
+ options.delete('nph') if defined?(MOD_RUBY)
+ if options.delete('nph') || nph?()
+ protocol = $CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0'
+ status = options.delete('status')
+ status = HTTP_STATUS[status] || status || '200 OK'
+ buf << "#{protocol} #{status}#{EOL}"
+ buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
+ options['server'] ||= $CGI_ENV['SERVER_SOFTWARE'] || ''
+ options['connection'] ||= 'close'
+ end
+ ## common headers
+ status = options.delete('status')
+ buf << "Status: #{HTTP_STATUS[status] || status}#{EOL}" if status
+ server = options.delete('server')
+ buf << "Server: #{server}#{EOL}" if server
+ connection = options.delete('connection')
+ buf << "Connection: #{connection}#{EOL}" if connection
+ type = options.delete('type')
+ buf << "Content-Type: #{type}#{EOL}" #if type
+ length = options.delete('length')
+ buf << "Content-Length: #{length}#{EOL}" if length
+ language = options.delete('language')
+ buf << "Content-Language: #{language}#{EOL}" if language
+ expires = options.delete('expires')
+ buf << "Expires: #{CGI.rfc1123_date(expires)}#{EOL}" if expires
+ ## cookie
+ if cookie = options.delete('cookie')
+ case cookie
+ when String, Cookie
+ buf << "Set-Cookie: #{cookie}#{EOL}"
+ when Array
+ arr = cookie
+ arr.each {|c| buf << "Set-Cookie: #{c}#{EOL}" }
+ when Hash
+ hash = cookie
+ hash.each {|name, c| buf << "Set-Cookie: #{c}#{EOL}" }
+ end
+ end
+ if @output_cookies
+ @output_cookies.each {|c| buf << "Set-Cookie: #{c}#{EOL}" }
+ end
+ ## other headers
+ options.each do |key, value|
+ buf << "#{key}: #{value}#{EOL}"
+ end
+ return buf
+ end # _header_for_hash
+ private :_header_for_hash
+
+ def nph? #:nodoc:
+ return /IIS\/(\d+)/.match($CGI_ENV['SERVER_SOFTWARE']) && $1.to_i < 5
+ end
+
+ def _header_for_modruby(buf) #:nodoc:
+ request = Apache::request
+ buf.scan(/([^:]+): (.+)#{EOL}/o) do |name, value|
+ warn sprintf("name:%s value:%s\n", name, value) if $DEBUG
+ case name
+ when 'Set-Cookie'
+ request.headers_out.add(name, value)
+ when /^status$/i
+ request.status_line = value
+ request.status = value.to_i
+ when /^content-type$/i
+ request.content_type = value
+ when /^content-encoding$/i
+ request.content_encoding = value
+ when /^location$/i
+ request.status = 302 if request.status == 200
+ request.headers_out[name] = value
+ else
+ request.headers_out[name] = value
+ end
+ end
+ request.send_http_header
+ return ''
+ end
+ private :_header_for_modruby
+ #
+
+ # 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 = {}
+ query.split(/[&;]/).each do |pairs|
+ key, value = pairs.split('=',2).collect{|v| CGI::unescape(v) }
+ if key && value
+ params.has_key?(key) ? params[key].push(value) : params[key] = [value]
+ elsif key
+ params[key]=[]
+ end
+ end
+ params.default=[].freeze
+ params
+ end
+
+ # Maximum content length of post data
+ ##MAX_CONTENT_LENGTH = 2 * 1024 * 1024
+
+ # Maximum content length of multipart data
+ MAX_MULTIPART_LENGTH = 128 * 1024 * 1024
+
+ # Maximum number of request parameters when multipart
+ MAX_MULTIPART_COUNT = 128
+
+ # 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
+
+ # Get the uploaed files as a hash of name=>values pairs
+ attr_reader :files
+
+ # Set all the parameters.
+ def params=(hash)
+ @params.clear
+ @params.update(hash)
+ end
+
+ def read_multipart(boundary, content_length)
+ ## read first boundary
+ stdin = $stdin
+ first_line = "--#{boundary}#{EOL}"
+ content_length -= first_line.bytesize
+ status = stdin.read(first_line.bytesize)
+ raise EOFError.new("no content body") unless status
+ raise EOFError.new("bad content body") unless first_line == status
+ ## parse and set params
+ params = {}
+ @files = {}
+ boundary_rexp = /--#{Regexp.quote(boundary)}(#{EOL}|--)/
+ boundary_size = "#{EOL}--#{boundary}#{EOL}".bytesize
+ boundary_end = nil
+ buf = ''
+ bufsize = 10 * 1024
+ max_count = MAX_MULTIPART_COUNT
+ n = 0
+ while true
+ (n += 1) < max_count or raise StandardError.new("too many parameters.")
+ ## create body (StringIO or Tempfile)
+ body = create_body(bufsize < content_length)
+ class << body
+ alias local_path path
+ attr_reader :original_filename, :content_type
+ end
+ ## find head and boundary
+ head = nil
+ separator = EOL * 2
+ until head && matched = boundary_rexp.match(buf)
+ if !head && pos = buf.index(separator)
+ len = pos + EOL.bytesize
+ head = buf[0, len]
+ buf = buf[(pos+separator.bytesize)..-1]
+ else
+ if head && buf.size > boundary_size
+ len = buf.size - boundary_size
+ body.print(buf[0, len])
+ buf[0, len] = ''
+ end
+ c = stdin.read(bufsize < content_length ? bufsize : content_length)
+ raise EOFError.new("bad content body") if c.nil? || c.empty?
+ buf << c
+ content_length -= c.bytesize
+ end
+ end
+ ## read to end of boundary
+ m = matched
+ len = m.begin(0)
+ s = buf[0, len]
+ if s =~ /(\r?\n)\z/
+ s = buf[0, len - $1.bytesize]
+ end
+ body.print(s)
+ buf = buf[m.end(0)..-1]
+ boundary_end = m[1]
+ content_length = -1 if boundary_end == '--'
+ ## reset file cursor position
+ body.rewind
+ ## original filename
+ /Content-Disposition:.* filename=(?:"(.*?)"|([^;\r\n]*))/i.match(head)
+ filename = $1 || $2 || ''
+ filename = CGI.unescape(filename) if unescape_filename?()
+ body.instance_variable_set('@original_filename', filename.taint)
+ ## content type
+ /Content-Type: (.*)/i.match(head)
+ (content_type = $1 || '').chomp!
+ body.instance_variable_set('@content_type', content_type.taint)
+ ## query parameter name
+ /Content-Disposition:.* name=(?:"(.*?)"|([^;\r\n]*))/i.match(head)
+ name = $1 || $2 || ''
+ if body.original_filename.empty?
+ value=body.read.dup.force_encoding(@accept_charset)
+ (params[name] ||= []) << value
+ unless value.valid_encoding?
+ if @accept_charset_error_block
+ @accept_charset_error_block.call(name,value)
+ else
+ raise InvalidEncoding,"Accept-Charset encoding error"
+ end
+ end
+ class << params[name].last;self;end.class_eval do
+ define_method(:read){self}
+ define_method(:original_filename){""}
+ define_method(:content_type){""}
+ end
+ else
+ (params[name] ||= []) << body
+ @files[name]=body
+ end
+ ## break loop
+ break if buf.size == 0
+ break if content_length == -1
+ end
+ raise EOFError, "bad boundary end of body part" unless boundary_end =~ /--/
+ params.default = []
+ params
+ end # read_multipart
+ private :read_multipart
+ def create_body(is_large) #:nodoc:
+ if is_large
+ require 'tempfile'
+ body = Tempfile.new('CGI', encoding: "ascii-8bit")
+ else
+ begin
+ require 'stringio'
+ body = StringIO.new("".force_encoding("ascii-8bit"))
+ rescue LoadError
+ require 'tempfile'
+ body = Tempfile.new('CGI', encoding: "ascii-8bit")
+ end
+ end
+ body.binmode if defined? body.binmode
+ return body
+ end
+ def unescape_filename? #:nodoc:
+ user_agent = $CGI_ENV['HTTP_USER_AGENT']
+ return /Mac/i.match(user_agent) && /Mozilla/i.match(user_agent) && !/MSIE/i.match(user_agent)
+ end
+
+ # 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.
+ # 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'])
+ raise StandardError.new("too large multipart data.") if env_table['CONTENT_LENGTH'].to_i > MAX_MULTIPART_LENGTH
+ 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.dup.force_encoding(@accept_charset)
+ )
+ unless Encoding.find(@accept_charset) == Encoding::ASCII_8BIT
+ @params.each do |key,values|
+ values.each do |value|
+ unless value.valid_encoding?
+ if @accept_charset_error_block
+ @accept_charset_error_block.call(key,value)
+ else
+ raise InvalidEncoding,"Accept-Charset encoding error"
+ end
+ end
+ end
+ end
+ 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("".force_encoding("ascii-8bit"))
+ else
+ Tempfile.new("CGI",encoding:"ascii-8bit")
+ 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
+
+ # InvalidEncoding Exception class
+ class InvalidEncoding < Exception; end
+
+ # @@accept_charset is default accept character set.
+ # This default value default is "UTF-8"
+ # If you want to change the default accept character set
+ # when create a new CGI instance, set this:
+ #
+ # CGI.accept_charset = "EUC-JP"
+ #
+
+ @@accept_charset="UTF-8"
+
+ def self.accept_charset
+ @@accept_charset
+ end
+
+ def self.accept_charset=(accept_charset)
+ @@accept_charset=accept_charset
+ end
+
+ # Create a new CGI instance.
+ #
+ # CGI accept constructor parameters either in a hash, string as a block.
+ # But string is as same as using :tag_maker of hash.
+ #
+ # CGI.new("html3") #=> CGI.new(:tag_maker=>"html3")
+ #
+ # And, if you specify string, @accept_charset cannot be changed.
+ # Instead, please use hash parameter.
+ #
+ # == accept_charset
+ #
+ # :accept_charset specifies encoding of received query string.
+ # ( Default value is @@accept_charset. )
+ # If not valid, raise CGI::InvalidEncoding
+ #
+ # Example. Suppose @@accept_charset # => "UTF-8"
+ #
+ # when not specified:
+ #
+ # cgi=CGI.new # @accept_charset # => "UTF-8"
+ #
+ # when specified "EUC-JP":
+ #
+ # cgi=CGI.new(:accept_charset => "EUC-JP") # => "EUC-JP"
+ #
+ # == block
+ #
+ # When you use a block, you can write a process
+ # that query encoding is invalid. Example:
+ #
+ # encoding_error={}
+ # cgi=CGI.new(:accept_charset=>"EUC-JP") do |name,value|
+ # encoding_error[key] = value
+ # end
+ #
+ # == tag_maker
+ #
+ # :tag_maker 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. It works this:
+ #
+ # CGI.new(:tag_maker=>"html3")
+ #
+ # This will be obsolete:
+ #
+ # CGI.new("html3")
+ #
+ attr_reader :accept_charset
+ def initialize(options = {},&block)
+ @accept_charset_error_block=block if block_given?
+ @options={:accept_charset=>@@accept_charset}
+ case options
+ when Hash
+ @options.merge!(options)
+ when String
+ @options[:tag_maker]=options
+ end
+ @accept_charset=@options[:accept_charset]
+ 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 @options[:tag_maker]
+ 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
+
+
diff --git a/ruby/lib/cgi/html.rb b/ruby/lib/cgi/html.rb
new file mode 100644
index 0000000..62f1fc1
--- /dev/null
+++ b/ruby/lib/cgi/html.rb
@@ -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.to_s) +
+ if true == value
+ ""
+ else
+ '="' + CGI::escapeHTML(value.to_s) + '"'
+ 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[-1] == true || value[-1] == false
+ checkbox(name, value[0], value[-1]) +
+ value[-2]
+ else
+ checkbox(name, value[0]) +
+ value[-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.size - 1] == true
+ option({ "VALUE" => value[0], "SELECTED" => true }){
+ value[value.size - 2]
+ }
+ else
+ option({ "VALUE" => value[0] }){
+ value[value.size - 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[-1] == true || value[-1] == false
+ radio_button(name, value[0], value[-1]) +
+ value[-2]
+ else
+ radio_button(name, value[0]) +
+ value[-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
+
+
diff --git a/ruby/lib/cgi/session.rb b/ruby/lib/cgi/session.rb
new file mode 100644
index 0000000..2b5aa84
--- /dev/null
+++ b/ruby/lib/cgi/session.rb
@@ -0,0 +1,537 @@
+#
+# cgi/session.rb - session support for cgi scripts
+#
+# Copyright (C) 2001 Yukihiro "Matz" Matsumoto
+# Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
+# Copyright (C) 2000 Information-technology Promotion Agency, Japan
+#
+# Author: Yukihiro "Matz" Matsumoto
+#
+# Documentation: William Webber (william@williamwebber.com)
+#
+# == Overview
+#
+# This file provides the +CGI::Session+ class, which provides session
+# support for CGI scripts. A session is a sequence of HTTP requests
+# and responses linked together and associated with a single client.
+# Information associated with the session is stored
+# on the server between requests. A session id is passed between client
+# and server with every request and response, transparently
+# to the user. This adds state information to the otherwise stateless
+# HTTP request/response protocol.
+#
+# See the documentation to the +CGI::Session+ class for more details
+# and examples of usage. See cgi.rb for the +CGI+ class itself.
+
+require 'cgi'
+require 'tmpdir'
+
+class CGI
+
+ # Class representing an HTTP session. See documentation for the file
+ # cgi/session.rb for an introduction to HTTP sessions.
+ #
+ # == Lifecycle
+ #
+ # A CGI::Session instance is created from a CGI object. By default,
+ # this CGI::Session instance will start a new session if none currently
+ # exists, or continue the current session for this client if one does
+ # exist. The +new_session+ option can be used to either always or
+ # never create a new session. See #new() for more details.
+ #
+ # #delete() deletes a session from session storage. It
+ # does not however remove the session id from the client. If the client
+ # makes another request with the same id, the effect will be to start
+ # a new session with the old session's id.
+ #
+ # == Setting and retrieving session data.
+ #
+ # The Session class associates data with a session as key-value pairs.
+ # This data can be set and retrieved by indexing the Session instance
+ # using '[]', much the same as hashes (although other hash methods
+ # are not supported).
+ #
+ # When session processing has been completed for a request, the
+ # session should be closed using the close() method. This will
+ # store the session's state to persistent storage. If you want
+ # to store the session's state to persistent storage without
+ # finishing session processing for this request, call the update()
+ # method.
+ #
+ # == Storing session state
+ #
+ # The caller can specify what form of storage to use for the session's
+ # data with the +database_manager+ option to CGI::Session::new. The
+ # following storage classes are provided as part of the standard library:
+ #
+ # CGI::Session::FileStore:: stores data as plain text in a flat file. Only
+ # works with String data. This is the default
+ # storage type.
+ # CGI::Session::MemoryStore:: stores data in an in-memory hash. The data
+ # only persists for as long as the current ruby
+ # interpreter instance does.
+ # CGI::Session::PStore:: stores data in Marshalled format. Provided by
+ # cgi/session/pstore.rb. Supports data of any type,
+ # and provides file-locking and transaction support.
+ #
+ # Custom storage types can also be created by defining a class with
+ # the following methods:
+ #
+ # new(session, options)
+ # restore # returns hash of session data.
+ # update
+ # close
+ # delete
+ #
+ # Changing storage type mid-session does not work. Note in particular
+ # that by default the FileStore and PStore session data files have the
+ # same name. If your application switches from one to the other without
+ # making sure that filenames will be different
+ # and clients still have old sessions lying around in cookies, then
+ # things will break nastily!
+ #
+ # == Maintaining the session id.
+ #
+ # Most session state is maintained on the server. However, a session
+ # id must be passed backwards and forwards between client and server
+ # to maintain a reference to this session state.
+ #
+ # The simplest way to do this is via cookies. The CGI::Session class
+ # provides transparent support for session id communication via cookies
+ # if the client has cookies enabled.
+ #
+ # If the client has cookies disabled, the session id must be included
+ # as a parameter of all requests sent by the client to the server. The
+ # CGI::Session class in conjunction with the CGI class will transparently
+ # add the session id as a hidden input field to all forms generated
+ # using the CGI#form() HTML generation method. No built-in support is
+ # provided for other mechanisms, such as URL re-writing. The caller is
+ # responsible for extracting the session id from the session_id
+ # attribute and manually encoding it in URLs and adding it as a hidden
+ # input to HTML forms created by other mechanisms. Also, session expiry
+ # is not automatically handled.
+ #
+ # == Examples of use
+ #
+ # === Setting the user's name
+ #
+ # require 'cgi'
+ # require 'cgi/session'
+ # require 'cgi/session/pstore' # provides CGI::Session::PStore
+ #
+ # cgi = CGI.new("html4")
+ #
+ # session = CGI::Session.new(cgi,
+ # 'database_manager' => CGI::Session::PStore, # use PStore
+ # 'session_key' => '_rb_sess_id', # custom session key
+ # 'session_expires' => Time.now + 30 * 60, # 30 minute timeout
+ # 'prefix' => 'pstore_sid_') # PStore option
+ # if cgi.has_key?('user_name') and cgi['user_name'] != ''
+ # # coerce to String: cgi[] returns the
+ # # string-like CGI::QueryExtension::Value
+ # session['user_name'] = cgi['user_name'].to_s
+ # elsif !session['user_name']
+ # session['user_name'] = "guest"
+ # end
+ # session.close
+ #
+ # === Creating a new session safely
+ #
+ # require 'cgi'
+ # require 'cgi/session'
+ #
+ # cgi = CGI.new("html4")
+ #
+ # # We make sure to delete an old session if one exists,
+ # # not just to free resources, but to prevent the session
+ # # from being maliciously hijacked later on.
+ # begin
+ # session = CGI::Session.new(cgi, 'new_session' => false)
+ # session.delete
+ # rescue ArgumentError # if no old session
+ # end
+ # session = CGI::Session.new(cgi, 'new_session' => true)
+ # session.close
+ #
+ class Session
+
+ class NoSession < RuntimeError #:nodoc:
+ end
+
+ # The id of this session.
+ attr_reader :session_id, :new_session
+
+ def Session::callback(dbman) #:nodoc:
+ Proc.new{
+ dbman[0].close unless dbman.empty?
+ }
+ end
+
+ # Create a new session id.
+ #
+ # The session id is an MD5 hash based upon the time,
+ # a random number, and a constant string. This routine
+ # is used internally for automatically generated
+ # session ids.
+ def create_new_id
+ require 'securerandom'
+ begin
+ session_id = SecureRandom.hex(16)
+ rescue NotImplementedError
+ require 'digest/md5'
+ md5 = Digest::MD5::new
+ now = Time::now
+ md5.update(now.to_s)
+ md5.update(String(now.usec))
+ md5.update(String(rand(0)))
+ md5.update(String($$))
+ md5.update('foobar')
+ session_id = md5.hexdigest
+ end
+ session_id
+ end
+ private :create_new_id
+
+ # Create a new CGI::Session object for +request+.
+ #
+ # +request+ is an instance of the +CGI+ class (see cgi.rb).
+ # +option+ is a hash of options for initialising this
+ # CGI::Session instance. The following options are
+ # recognised:
+ #
+ # session_key:: the parameter name used for the session id.
+ # Defaults to '_session_id'.
+ # session_id:: the session id to use. If not provided, then
+ # it is retrieved from the +session_key+ parameter
+ # of the request, or automatically generated for
+ # a new session.
+ # new_session:: if true, force creation of a new session. If not set,
+ # a new session is only created if none currently
+ # exists. If false, a new session is never created,
+ # and if none currently exists and the +session_id+
+ # option is not set, an ArgumentError is raised.
+ # database_manager:: the name of the class providing storage facilities
+ # for session state persistence. Built-in support
+ # is provided for +FileStore+ (the default),
+ # +MemoryStore+, and +PStore+ (from
+ # cgi/session/pstore.rb). See the documentation for
+ # these classes for more details.
+ #
+ # The following options are also recognised, but only apply if the
+ # session id is stored in a cookie.
+ #
+ # session_expires:: the time the current session expires, as a
+ # +Time+ object. If not set, the session will terminate
+ # when the user's browser is closed.
+ # session_domain:: the hostname domain for which this session is valid.
+ # If not set, defaults to the hostname of the server.
+ # session_secure:: if +true+, this session will only work over HTTPS.
+ # session_path:: the path for which this session applies. Defaults
+ # to the directory of the CGI script.
+ #
+ # +option+ is also passed on to the session storage class initializer; see
+ # the documentation for each session storage class for the options
+ # they support.
+ #
+ # The retrieved or created session is automatically added to +request+
+ # as a cookie, and also to its +output_hidden+ table, which is used
+ # to add hidden input elements to forms.
+ #
+ # *WARNING* the +output_hidden+
+ # fields are surrounded by a <fieldset> tag in HTML 4 generation, which
+ # is _not_ invisible on many browsers; you may wish to disable the
+ # use of fieldsets with code similar to the following
+ # (see http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/37805)
+ #
+ # cgi = CGI.new("html4")
+ # class << cgi
+ # undef_method :fieldset
+ # end
+ #
+ def initialize(request, option={})
+ @new_session = false
+ session_key = option['session_key'] || '_session_id'
+ session_id = option['session_id']
+ unless session_id
+ if option['new_session']
+ session_id = create_new_id
+ @new_session = true
+ end
+ end
+ unless session_id
+ if request.key?(session_key)
+ session_id = request[session_key]
+ session_id = session_id.read if session_id.respond_to?(:read)
+ end
+ unless session_id
+ session_id, = request.cookies[session_key]
+ end
+ unless session_id
+ unless option.fetch('new_session', true)
+ raise ArgumentError, "session_key `%s' should be supplied"%session_key
+ end
+ session_id = create_new_id
+ @new_session = true
+ end
+ end
+ @session_id = session_id
+ dbman = option['database_manager'] || FileStore
+ begin
+ @dbman = dbman::new(self, option)
+ rescue NoSession
+ unless option.fetch('new_session', true)
+ raise ArgumentError, "invalid session_id `%s'"%session_id
+ end
+ session_id = @session_id = create_new_id unless session_id
+ @new_session=true
+ retry
+ end
+ request.instance_eval do
+ @output_hidden = {session_key => session_id} unless option['no_hidden']
+ @output_cookies = [
+ Cookie::new("name" => session_key,
+ "value" => session_id,
+ "expires" => option['session_expires'],
+ "domain" => option['session_domain'],
+ "secure" => option['session_secure'],
+ "path" =>
+ if option['session_path']
+ option['session_path']
+ elsif ENV["SCRIPT_NAME"]
+ File::dirname(ENV["SCRIPT_NAME"])
+ else
+ ""
+ end)
+ ] unless option['no_cookies']
+ end
+ @dbprot = [@dbman]
+ ObjectSpace::define_finalizer(self, Session::callback(@dbprot))
+ end
+
+ # Retrieve the session data for key +key+.
+ def [](key)
+ @data ||= @dbman.restore
+ @data[key]
+ end
+
+ # Set the session date for key +key+.
+ def []=(key, val)
+ @write_lock ||= true
+ @data ||= @dbman.restore
+ @data[key] = val
+ end
+
+ # Store session data on the server. For some session storage types,
+ # this is a no-op.
+ def update
+ @dbman.update
+ end
+
+ # Store session data on the server and close the session storage.
+ # For some session storage types, this is a no-op.
+ def close
+ @dbman.close
+ @dbprot.clear
+ end
+
+ # Delete the session from storage. Also closes the storage.
+ #
+ # Note that the session's data is _not_ automatically deleted
+ # upon the session expiring.
+ def delete
+ @dbman.delete
+ @dbprot.clear
+ end
+
+ # File-based session storage class.
+ #
+ # Implements session storage as a flat file of 'key=value' values.
+ # This storage type only works directly with String values; the
+ # user is responsible for converting other types to Strings when
+ # storing and from Strings when retrieving.
+ class FileStore
+ # Create a new FileStore instance.
+ #
+ # This constructor is used internally by CGI::Session. The
+ # user does not generally need to call it directly.
+ #
+ # +session+ is the session for which this instance is being
+ # created. The session id must only contain alphanumeric
+ # characters; automatically generated session ids observe
+ # this requirement.
+ #
+ # +option+ is a hash of options for the initializer. The
+ # following options are recognised:
+ #
+ # tmpdir:: the directory to use for storing the FileStore
+ # file. Defaults to Dir::tmpdir (generally "/tmp"
+ # on Unix systems).
+ # prefix:: the prefix to add to the session id when generating
+ # the filename for this session's FileStore file.
+ # Defaults to "cgi_sid_".
+ # suffix:: the prefix to add to the session id when generating
+ # the filename for this session's FileStore file.
+ # Defaults to the empty string.
+ #
+ # This session's FileStore file will be created if it does
+ # not exist, or opened if it does.
+ def initialize(session, option={})
+ dir = option['tmpdir'] || Dir::tmpdir
+ prefix = option['prefix'] || 'cgi_sid_'
+ suffix = option['suffix'] || ''
+ id = session.session_id
+ require 'digest/md5'
+ md5 = Digest::MD5.hexdigest(id)[0,16]
+ @path = dir+"/"+prefix+md5+suffix
+ if File::exist? @path
+ @hash = nil
+ else
+ unless session.new_session
+ raise CGI::Session::NoSession, "uninitialized session"
+ end
+ @hash = {}
+ end
+ end
+
+ # Restore session state from the session's FileStore file.
+ #
+ # Returns the session state as a hash.
+ def restore
+ unless @hash
+ @hash = {}
+ begin
+ lockf = File.open(@path+".lock", "r")
+ lockf.flock File::LOCK_SH
+ f = File.open(@path, 'r')
+ for line in f
+ line.chomp!
+ k, v = line.split('=',2)
+ @hash[CGI::unescape(k)] = Marshal.restore(CGI::unescape(v))
+ end
+ ensure
+ f.close unless f.nil?
+ lockf.close if lockf
+ end
+ end
+ @hash
+ end
+
+ # Save session state to the session's FileStore file.
+ def update
+ return unless @hash
+ begin
+ lockf = File.open(@path+".lock", File::CREAT|File::RDWR, 0600)
+ lockf.flock File::LOCK_EX
+ f = File.open(@path+".new", File::CREAT|File::TRUNC|File::WRONLY, 0600)
+ for k,v in @hash
+ f.printf "%s=%s\n", CGI::escape(k), CGI::escape(String(Marshal.dump(v)))
+ end
+ f.close
+ File.rename @path+".new", @path
+ ensure
+ f.close if f and !f.closed?
+ lockf.close if lockf
+ end
+ end
+
+ # Update and close the session's FileStore file.
+ def close
+ update
+ end
+
+ # Close and delete the session's FileStore file.
+ def delete
+ File::unlink @path+".lock" rescue nil
+ File::unlink @path+".new" rescue nil
+ File::unlink @path rescue Errno::ENOENT
+ end
+ end
+
+ # In-memory session storage class.
+ #
+ # Implements session storage as a global in-memory hash. Session
+ # data will only persist for as long as the ruby interpreter
+ # instance does.
+ class MemoryStore
+ GLOBAL_HASH_TABLE = {} #:nodoc:
+
+ # Create a new MemoryStore instance.
+ #
+ # +session+ is the session this instance is associated with.
+ # +option+ is a list of initialisation options. None are
+ # currently recognised.
+ def initialize(session, option=nil)
+ @session_id = session.session_id
+ unless GLOBAL_HASH_TABLE.key?(@session_id)
+ unless session.new_session
+ raise CGI::Session::NoSession, "uninitialized session"
+ end
+ GLOBAL_HASH_TABLE[@session_id] = {}
+ end
+ end
+
+ # Restore session state.
+ #
+ # Returns session data as a hash.
+ def restore
+ GLOBAL_HASH_TABLE[@session_id]
+ end
+
+ # Update session state.
+ #
+ # A no-op.
+ def update
+ # don't need to update; hash is shared
+ end
+
+ # Close session storage.
+ #
+ # A no-op.
+ def close
+ # don't need to close
+ end
+
+ # Delete the session state.
+ def delete
+ GLOBAL_HASH_TABLE.delete(@session_id)
+ end
+ end
+
+ # Dummy session storage class.
+ #
+ # Implements session storage place holder. No actual storage
+ # will be done.
+ class NullStore
+ # Create a new NullStore instance.
+ #
+ # +session+ is the session this instance is associated with.
+ # +option+ is a list of initialisation options. None are
+ # currently recognised.
+ def initialize(session, option=nil)
+ end
+
+ # Restore (empty) session state.
+ def restore
+ {}
+ end
+
+ # Update session state.
+ #
+ # A no-op.
+ def update
+ end
+
+ # Close session storage.
+ #
+ # A no-op.
+ def close
+ end
+
+ # Delete the session state.
+ #
+ # A no-op.
+ def delete
+ end
+ end
+ end
+end
diff --git a/ruby/lib/cgi/session/pstore.rb b/ruby/lib/cgi/session/pstore.rb
new file mode 100644
index 0000000..3cd3e46
--- /dev/null
+++ b/ruby/lib/cgi/session/pstore.rb
@@ -0,0 +1,111 @@
+#
+# cgi/session/pstore.rb - persistent storage of marshalled session data
+#
+# Documentation: William Webber (william@williamwebber.com)
+#
+# == Overview
+#
+# This file provides the CGI::Session::PStore class, which builds
+# persistent of session data on top of the pstore library. See
+# cgi/session.rb for more details on session storage managers.
+
+require 'cgi/session'
+require 'pstore'
+
+class CGI
+ class Session
+ # PStore-based session storage class.
+ #
+ # This builds upon the top-level PStore class provided by the
+ # library file pstore.rb. Session data is marshalled and stored
+ # in a file. File locking and transaction services are provided.
+ class PStore
+ # Create a new CGI::Session::PStore instance
+ #
+ # This constructor is used internally by CGI::Session. The
+ # user does not generally need to call it directly.
+ #
+ # +session+ is the session for which this instance is being
+ # created. The session id must only contain alphanumeric
+ # characters; automatically generated session ids observe
+ # this requirement.
+ #
+ # +option+ is a hash of options for the initializer. The
+ # following options are recognised:
+ #
+ # tmpdir:: the directory to use for storing the PStore
+ # file. Defaults to Dir::tmpdir (generally "/tmp"
+ # on Unix systems).
+ # prefix:: the prefix to add to the session id when generating
+ # the filename for this session's PStore file.
+ # Defaults to the empty string.
+ #
+ # This session's PStore file will be created if it does
+ # not exist, or opened if it does.
+ def initialize(session, option={})
+ dir = option['tmpdir'] || Dir::tmpdir
+ prefix = option['prefix'] || ''
+ id = session.session_id
+ require 'digest/md5'
+ md5 = Digest::MD5.hexdigest(id)[0,16]
+ path = dir+"/"+prefix+md5
+ path.untaint
+ if File::exist?(path)
+ @hash = nil
+ else
+ unless session.new_session
+ raise CGI::Session::NoSession, "uninitialized session"
+ end
+ @hash = {}
+ end
+ @p = ::PStore.new(path)
+ @p.transaction do |p|
+ File.chmod(0600, p.path)
+ end
+ end
+
+ # Restore session state from the session's PStore file.
+ #
+ # Returns the session state as a hash.
+ def restore
+ unless @hash
+ @p.transaction do
+ @hash = @p['hash'] || {}
+ end
+ end
+ @hash
+ end
+
+ # Save session state to the session's PStore file.
+ def update
+ @p.transaction do
+ @p['hash'] = @hash
+ end
+ end
+
+ # Update and close the session's PStore file.
+ def close
+ update
+ end
+
+ # Close and delete the session's PStore file.
+ def delete
+ path = @p.path
+ File::unlink path
+ end
+
+ end
+ end
+end
+
+if $0 == __FILE__
+ # :enddoc:
+ STDIN.reopen("/dev/null")
+ cgi = CGI.new
+ session = CGI::Session.new(cgi, 'database_manager' => CGI::Session::PStore)
+ session['key'] = {'k' => 'v'}
+ puts session['key'].class
+ fail unless Hash === session['key']
+ puts session['key'].inspect
+ fail unless session['key'].inspect == '{"k"=>"v"}'
+end
diff --git a/ruby/lib/cgi/util.rb b/ruby/lib/cgi/util.rb
new file mode 100644
index 0000000..991b68c
--- /dev/null
+++ b/ruby/lib/cgi/util.rb
@@ -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