diff options
author | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-16 18:49:26 +0900 |
---|---|---|
committer | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-30 00:39:06 +0900 |
commit | fcbf63e62c627deae76c1b8cb8c0876c536ed811 (patch) | |
tree | 64cb17de3f41a2b6fef2368028fbd00349946994 /jni/ruby/lib/cgi |
Fresh start
Diffstat (limited to 'jni/ruby/lib/cgi')
-rw-r--r-- | jni/ruby/lib/cgi/cookie.rb | 170 | ||||
-rw-r--r-- | jni/ruby/lib/cgi/core.rb | 880 | ||||
-rw-r--r-- | jni/ruby/lib/cgi/html.rb | 1034 | ||||
-rw-r--r-- | jni/ruby/lib/cgi/session.rb | 531 | ||||
-rw-r--r-- | jni/ruby/lib/cgi/session/pstore.rb | 100 | ||||
-rw-r--r-- | jni/ruby/lib/cgi/util.rb | 194 |
6 files changed, 2909 insertions, 0 deletions
diff --git a/jni/ruby/lib/cgi/cookie.rb b/jni/ruby/lib/cgi/cookie.rb new file mode 100644 index 0000000..3ec884d --- /dev/null +++ b/jni/ruby/lib/cgi/cookie.rb @@ -0,0 +1,170 @@ +require 'cgi/util' +class CGI + # 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 + @@accept_charset="UTF-8" unless defined?(@@accept_charset) + + # Create a new CGI::Cookie object. + # + # :call-seq: + # Cookie.new(name_string,*value) + # Cookie.new(options_hash) + # + # +name_string+:: + # The name of the cookie; in this form, there is no #domain or + # #expiration. The #path is gleaned from the +SCRIPT_NAME+ environment + # variable, and #secure is false. + # <tt>*value</tt>:: + # value or list of values of the cookie + # +options_hash+:: + # A Hash of options to initialize this Cookie. Possible options are: + # + # 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 + # the value of the +SCRIPT_NAME+ environment variable. + # 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) + @domain = nil + @expires = nil + 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 + + # Name of this cookie, as a +String+ + attr_accessor :name + # Path for which this cookie applies, as a +String+ + attr_accessor :path + # Domain for which this cookie applies, as a +String+ + attr_accessor :domain + # Time at which this cookie expires, as a +Time+ + attr_accessor :expires + # True if this cookie is secure; false otherwise + attr_reader("secure") + + # Returns the value or list of values for this cookie. + def value + self + end + + # Replaces the value of this cookie with a new value or list of values. + 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 + + # 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 self.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,@@accept_charset) } + if cookies.has_key?(name) + values = cookies[name].value + values + end + cookies[name] = Cookie.new(name, *values) + end + + cookies + end + + # A summary of cookie string. + def inspect + "#<CGI::Cookie: #{self.to_s.inspect}>" + end + + end # class Cookie +end + + diff --git a/jni/ruby/lib/cgi/core.rb b/jni/ruby/lib/cgi/core.rb new file mode 100644 index 0000000..cadd01f --- /dev/null +++ b/jni/ruby/lib/cgi/core.rb @@ -0,0 +1,880 @@ +#-- +# Methods for generating HTML, parsing CGI-related parameters, and +# generating HTTP responses. +#++ +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 46973 2014-07-27 11:04:28Z hsbt $' #:nodoc: + + # Whether processing will be required in binary vs text + NEEDS_BINMODE = File::BINARY != 0 + + # 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" + } + + # :startdoc: + + # Synonym for ENV. + def env_table + ENV + end + + # Synonym for $stdin. + def stdinput + $stdin + end + + # Synonym for $stdout. + def stdoutput + $stdout + end + + private :env_table, :stdinput, :stdoutput + + # Create an HTTP header block as a string. + # + # :call-seq: + # http_header(content_type_string="text/html") + # http_header(headers_hash) + # + # Includes the empty line that ends the header block. + # + # +content_type_string+:: + # If this form is used, this string is the <tt>Content-Type</tt> + # +headers_hash+:: + # A Hash of header values. 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 as a String, returned as the Status header. The + # values 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 + # + # 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 headers can also be set; they are appended as key: value. + # + # Examples: + # + # http_header + # # Content-Type: text/html + # + # http_header("text/plain") + # # Content-Type: text/plain + # + # http_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") + # + # This method does not perform charset conversion. + def http_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 # http_header() + + # This method is an alias for #http_header, when HTML5 tag maker is inactive. + # + # NOTE: use #http_header to create HTTP header blocks, this alias is only + # provided for backwards compatibility. + # + # Using #header with the HTML5 tag maker will create a <header> element. + alias :header :http_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_value {|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 ($>) + # + # :call-seq: + # cgi.out(content_type_string='text/html') + # cgi.out(headers_hash) + # + # +content_type_string+:: + # If a string is passed, it is assumed to be the content type. + # +headers_hash+:: + # This is a Hash of headers, similar to that used by #http_header. + # +block+:: + # A block is required and should evaluate to the body of the response. + # + # <tt>Content-Length</tt> is automatically calculated from the size of + # the String returned by the content block. + # + # If <tt>ENV['REQUEST_METHOD'] == "HEAD"</tt>, then only the header + # is output (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". + # + # Example: + # + # 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" } + # # HTTP/1.1 200 OK + # # Date: Sun, 15 May 2011 17:35:54 GMT + # # Server: Apache 2.2.0 + # # Connection: close + # # Content-Type: text/html; charset=iso-2022-jp + # # Content-Length: 6 + # # Content-Language: ja + # # Expires: Tue, 14 Jun 2011 17:35:54 GMT + # # Set-Cookie: foo + # # Set-Cookie: bar + # # my_header1: my_value + # # my_header2: my_value + # # + # # string + 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 http_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) } + + next unless key + + params[key] ||= [] + params[key].push(value) if value + end + + params.default=[].freeze + params + end + + # Maximum content length of post data + ##MAX_CONTENT_LENGTH = 2 * 1024 * 1024 + + # Maximum number of request parameters when multipart + MAX_MULTIPART_COUNT = 128 + + # Mixin module that provides the following: + # + # 1. Access to the CGI environment variables as methods. See + # documentation to the CGI class for a list of these variables. The + # methods are exposed by removing the leading +HTTP_+ (if it exists) and + # downcasing the name. For example, +auth_type+ will return the + # environment variable +AUTH_TYPE+, and +accept+ will return the value + # for +HTTP_ACCEPT+. + # + # 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 initializing 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 uploaded files as a hash of name=>values pairs + attr_reader :files + + # Set all the parameters. + def params=(hash) + @params.clear + @params.update(hash) + end + + ## + # Parses multipart form elements according to + # http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 + # + # Returns a hash of multipart form parameters with bodies of type StringIO or + # Tempfile depending on whether the multipart form element exceeds 10 KB + # + # params[name => body] + # + def read_multipart(boundary, content_length) + ## read first boundary + stdin = stdinput + 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 + buf = '' + bufsize = 10 * 1024 + max_count = MAX_MULTIPART_COUNT + n = 0 + tempfiles = [] + while true + (n += 1) < max_count or raise StandardError.new("too many parameters.") + ## create body (StringIO or Tempfile) + body = create_body(bufsize < content_length) + tempfiles << body if defined?(Tempfile) && body.kind_of?(Tempfile) + class << body + if method_defined?(:path) + alias local_path path + else + def local_path + nil + end + end + 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) + body.close! if defined?(Tempfile) && body.kind_of?(Tempfile) + (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 content_length == -1 + end + raise EOFError, "bad boundary end of body part" unless boundary_end =~ /--/ + params.default = [] + params + rescue Exception + if tempfiles + tempfiles.each {|t| + if t.path + t.close! + end + } + end + raise + end # read_multipart + private :read_multipart + def create_body(is_large) #:nodoc: + if is_large + require 'tempfile' + body = Tempfile.new('CGI', encoding: Encoding::ASCII_8BIT) + else + begin + require 'stringio' + body = StringIO.new("".force_encoding(Encoding::ASCII_8BIT)) + rescue LoadError + require 'tempfile' + body = Tempfile.new('CGI', encoding: 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 + array = readlines rescue nil + if not array.nil? + array.join(' ').gsub(/\n/n, '') + else + "" + end + end.gsub(/\\=/n, '%3D').gsub(/\\&/n, '%26') + + words = Shellwords.shellwords(string) + + if words.find{|x| /=/n.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']) + current_max_multipart_length = @max_multipart_length.respond_to?(:call) ? @max_multipart_length.call : @max_multipart_length + raise StandardError.new("too large multipart data.") if env_table['CONTENT_LENGTH'].to_i > current_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 + + # Returns whether the form contained multipart/form-data + 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(Encoding::ASCII_8BIT)) + else + Tempfile.new("CGI",encoding: Encoding::ASCII_8BIT) + end + else + str = if value then value.dup else "" end + str + end + end + + # Return all query parameter names as an array of String. + def keys(*args) + @params.keys(*args) + end + + # Returns true if a given query string parameter exists. + def has_key?(*args) + @params.has_key?(*args) + end + alias key? has_key? + alias include? has_key? + + end # QueryExtension + + # Exception raised when there is an invalid encoding detected + 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" + + # Return the accept character set for all new CGI instances. + def self.accept_charset + @@accept_charset + end + + # Set the accept character set for all new CGI instances. + def self.accept_charset=(accept_charset) + @@accept_charset=accept_charset + end + + # Return the accept character set for this CGI instance. + attr_reader :accept_charset + + # @@max_multipart_length is the maximum length of multipart data. + # The default value is 128 * 1024 * 1024 bytes + # + # The default can be set to something else in the CGI constructor, + # via the :max_multipart_length key in the option hash. + # + # See CGI.new documentation. + # + @@max_multipart_length= 128 * 1024 * 1024 + + # Create a new CGI instance. + # + # :call-seq: + # CGI.new(tag_maker) { block } + # CGI.new(options_hash = {}) { block } + # + # + # <tt>tag_maker</tt>:: + # This is the same as using the +options_hash+ form with the value <tt>{ + # :tag_maker => tag_maker }</tt> Note that it is recommended to use the + # +options_hash+ form, since it also allows you specify the charset you + # will accept. + # <tt>options_hash</tt>:: + # A Hash that recognizes three options: + # + # <tt>:accept_charset</tt>:: + # specifies encoding of received query string. If omitted, + # <tt>@@accept_charset</tt> is used. If the encoding is not valid, a + # CGI::InvalidEncoding will be raised. + # + # Example. Suppose <tt>@@accept_charset</tt> is "UTF-8" + # + # when not specified: + # + # cgi=CGI.new # @accept_charset # => "UTF-8" + # + # when specified as "EUC-JP": + # + # cgi=CGI.new(:accept_charset => "EUC-JP") # => "EUC-JP" + # + # <tt>:tag_maker</tt>:: + # String that specifies which version of the HTML generation methods to + # use. If not specified, no HTML generation methods will be loaded. + # + # The following values are supported: + # + # "html3":: HTML 3.x + # "html4":: HTML 4.0 + # "html4Tr":: HTML 4.0 Transitional + # "html4Fr":: HTML 4.0 with Framesets + # "html5":: HTML 5 + # + # <tt>:max_multipart_length</tt>:: + # Specifies maximum length of multipart data. Can be an Integer scalar or + # a lambda, that will be evaluated when the request is parsed. This + # allows more complex logic to be set when determining whether to accept + # multipart data (e.g. consult a registered users upload allowance) + # + # Default is 128 * 1024 * 1024 bytes + # + # cgi=CGI.new(:max_multipart_length => 268435456) # simple scalar + # + # cgi=CGI.new(:max_multipart_length => -> {check_filesystem}) # lambda + # + # <tt>block</tt>:: + # If provided, the block is called when an invalid encoding is + # encountered. For example: + # + # encoding_errors={} + # cgi=CGI.new(:accept_charset=>"EUC-JP") do |name,value| + # encoding_errors[name] = value + # end + # + # Finally, 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(options = {}, &block) # :yields: name, value + @accept_charset_error_block = block_given? ? block : nil + @options={ + :accept_charset=>@@accept_charset, + :max_multipart_length=>@@max_multipart_length + } + case options + when Hash + @options.merge!(options) + when String + @options[:tag_maker]=options + end + @accept_charset=@options[:accept_charset] + @max_multipart_length=@options[:max_multipart_length] + 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 + extend HtmlExtension + when "html4" + require 'cgi/html' + extend Html4 + extend HtmlExtension + when "html4Tr" + require 'cgi/html' + extend Html4Tr + extend HtmlExtension + when "html4Fr" + require 'cgi/html' + extend Html4Tr + extend Html4Fr + extend HtmlExtension + when "html5" + require 'cgi/html' + extend Html5 + extend HtmlExtension + end + end + +end # class CGI diff --git a/jni/ruby/lib/cgi/html.rb b/jni/ruby/lib/cgi/html.rb new file mode 100644 index 0000000..db47bb8 --- /dev/null +++ b/jni/ruby/lib/cgi/html.rb @@ -0,0 +1,1034 @@ +class CGI + # 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(element, attributes = {}) + s = nOE_element(element, attributes) + if block_given? + s << yield.to_s + end + s << "</#{element.upcase}>" + end + + def nn_element_def(attributes = {}, &block) + nn_element(__callee__, attributes, &block) + end + + # Generate code for an empty element. + # + # - O EMPTY + def nOE_element(element, attributes = {}) + attributes={attributes=>nil} if attributes.kind_of?(String) + s = "<#{element.upcase}" + attributes.each do|name, value| + next unless value + s << " " + s << CGI::escapeHTML(name.to_s) + if value != true + s << '="' + s << CGI::escapeHTML(value.to_s) + s << '"' + end + end + s << ">" + end + + def nOE_element_def(attributes = {}, &block) + nOE_element(__callee__, attributes, &block) + end + + + # Generate code for an element for which the end (and possibly the + # start) tag is optional. + # + # O O or - O + def nO_element(element, attributes = {}) + s = nOE_element(element, attributes) + if block_given? + s << yield.to_s + s << "</#{element.upcase}>" + end + s + end + + def nO_element_def(attributes = {}, &block) + nO_element(__callee__, attributes, &block) + 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 Html3, Html4, etc., contain more basic HTML-generation methods + # (+#title+, +#h1+, 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 + super(attributes) + 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 + super(attributes) + 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 + super(attributes) + 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 + super(attributes) + 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 + + buf << super(attributes) + + 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", "ALT" => "string") + # # <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 + super(attributes) + end + + end # HtmlExtension + + + # Mixin module for HTML version 3 generation methods. + module Html3 # :nodoc: + include TagMaker + + # The DOCTYPE declaration for this version of HTML + def doctype + %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">| + end + + instance_method(:nn_element_def).tap do |m| + # - - + 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 ] + define_method(element.downcase, m) + end + end + + instance_method(:nOE_element_def).tap do |m| + # - O EMPTY + for element in %w[ IMG BASE BASEFONT BR AREA LINK PARAM HR INPUT + ISINDEX META ] + define_method(element.downcase, m) + end + end + + instance_method(:nO_element_def).tap do |m| + # O O or - O + for element in %w[ HTML HEAD BODY P PLAINTEXT DT DD LI OPTION TR + TH TD ] + define_method(element.downcase, m) + end + end + + end # Html3 + + + # Mixin module for HTML version 4 generation methods. + module Html4 # :nodoc: + include TagMaker + + # 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 + + # Initialize the HTML generation methods for this version. + # - - + instance_method(:nn_element_def).tap do |m| + 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 ] + define_method(element.downcase, m) + end + end + + # - O EMPTY + instance_method(:nOE_element_def).tap do |m| + for element in %w[ IMG BASE BR AREA LINK PARAM HR INPUT COL META ] + define_method(element.downcase, m) + end + end + + # O O or - O + instance_method(:nO_element_def).tap do |m| + for element in %w[ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY + COLGROUP TR TH TD HEAD ] + define_method(element.downcase, m) + end + end + + end # Html4 + + + # Mixin module for HTML version 4 transitional generation methods. + module Html4Tr # :nodoc: + include TagMaker + + # 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. + # - - + instance_method(:nn_element_def).tap do |m| + 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 ] + define_method(element.downcase, m) + end + end + + # - O EMPTY + instance_method(:nOE_element_def).tap do |m| + for element in %w[ IMG BASE BASEFONT BR AREA LINK PARAM HR INPUT + COL ISINDEX META ] + define_method(element.downcase, m) + end + end + + # O O or - O + instance_method(:nO_element_def).tap do |m| + for element in %w[ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY + COLGROUP TR TH TD HEAD ] + define_method(element.downcase, m) + end + end + + end # Html4Tr + + + # Mixin module for generating HTML version 4 with framesets. + module Html4Fr # :nodoc: + include TagMaker + + # 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. + # - - + instance_method(:nn_element_def).tap do |m| + for element in %w[ FRAMESET ] + define_method(element.downcase, m) + end + end + + # - O EMPTY + instance_method(:nOE_element_def).tap do |m| + for element in %w[ FRAME ] + define_method(element.downcase, m) + end + end + + end # Html4Fr + + + # Mixin module for HTML version 5 generation methods. + module Html5 # :nodoc: + include TagMaker + + # The DOCTYPE declaration for this version of HTML + def doctype + %|<!DOCTYPE HTML>| + end + + # Initialise the HTML generation methods for this version. + # - - + instance_method(:nn_element_def).tap do |m| + for element in %w[ SECTION NAV ARTICLE ASIDE HGROUP HEADER + FOOTER FIGURE FIGCAPTION S TIME U MARK RUBY BDI IFRAME + VIDEO AUDIO CANVAS DATALIST OUTPUT PROGRESS METER DETAILS + SUMMARY MENU DIALOG I B SMALL EM STRONG DFN CODE SAMP KBD + VAR CITE ABBR SUB SUP SPAN BDO ADDRESS DIV MAP OBJECT + H1 H2 H3 H4 H5 H6 PRE Q INS DEL DL OL UL LABEL SELECT + FIELDSET LEGEND BUTTON TABLE TITLE STYLE SCRIPT NOSCRIPT + TEXTAREA FORM A BLOCKQUOTE CAPTION ] + define_method(element.downcase, m) + end + end + + # - O EMPTY + instance_method(:nOE_element_def).tap do |m| + for element in %w[ IMG BASE BR AREA LINK PARAM HR INPUT COL META + COMMAND EMBED KEYGEN SOURCE TRACK WBR ] + define_method(element.downcase, m) + end + end + + # O O or - O + instance_method(:nO_element_def).tap do |m| + for element in %w[ HTML HEAD BODY P DT DD LI OPTION THEAD TFOOT TBODY + OPTGROUP COLGROUP RT RP TR TH TD ] + define_method(element.downcase, m) + end + end + + end # Html5 + + class HTML3 + include Html3 + include HtmlExtension + end + + class HTML4 + include Html4 + include HtmlExtension + end + + class HTML4Tr + include Html4Tr + include HtmlExtension + end + + class HTML4Fr + include Html4Tr + include Html4Fr + include HtmlExtension + end + + class HTML5 + include Html5 + include HtmlExtension + end + +end diff --git a/jni/ruby/lib/cgi/session.rb b/jni/ruby/lib/cgi/session.rb new file mode 100644 index 0000000..63c5003 --- /dev/null +++ b/jni/ruby/lib/cgi/session.rb @@ -0,0 +1,531 @@ +# +# 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) + +require 'cgi' +require 'tmpdir' + +class CGI + + # == 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. + # + # == 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 data 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 nil + 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 recognized. + 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/jni/ruby/lib/cgi/session/pstore.rb b/jni/ruby/lib/cgi/session/pstore.rb new file mode 100644 index 0000000..7534314 --- /dev/null +++ b/jni/ruby/lib/cgi/session/pstore.rb @@ -0,0 +1,100 @@ +# +# 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 +# :enddoc: diff --git a/jni/ruby/lib/cgi/util.rb b/jni/ruby/lib/cgi/util.rb new file mode 100644 index 0000000..3d7db8f --- /dev/null +++ b/jni/ruby/lib/cgi/util.rb @@ -0,0 +1,194 @@ +class CGI; module Util; end; extend Util; end +module CGI::Util + @@accept_charset="UTF-8" unless defined?(@@accept_charset) + # URL-encode a string. + # url_encoded_string = CGI::escape("'Stop!' said Fred") + # # => "%27Stop%21%27+said+Fred" + def escape(string) + encoding = string.encoding + string.b.gsub(/([^ a-zA-Z0-9_.-]+)/) do |m| + '%' + m.unpack('H2' * m.bytesize).join('%').upcase + end.tr(' ', '+').force_encoding(encoding) + end + + # URL-decode a string with encoding(optional). + # string = CGI::unescape("%27Stop%21%27+said+Fred") + # # => "'Stop!' said Fred" + def unescape(string,encoding=@@accept_charset) + str=string.tr('+', ' ').b.gsub(/((?:%[0-9a-fA-F]{2})+)/) do |m| + [m.delete('%')].pack('H*') + end.force_encoding(encoding) + str.valid_encoding? ? str : str.force_encoding(string.encoding) + end + + # The set of special characters and their escaped values + TABLE_FOR_ESCAPE_HTML__ = { + "'" => ''', + '&' => '&', + '"' => '"', + '<' => '<', + '>' => '>', + } + + # Escape special characters in HTML, namely &\"<> + # CGI::escapeHTML('Usage: foo "bar" <baz>') + # # => "Usage: foo "bar" <baz>" + def escapeHTML(string) + string.gsub(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__) + end + + # Unescape a string that has been HTML-escaped + # CGI::unescapeHTML("Usage: foo "bar" <baz>") + # # => "Usage: foo \"bar\" <baz>" + def unescapeHTML(string) + return string unless string.include? '&' + enc = string.encoding + if enc != Encoding::UTF_8 && [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE].include?(enc) + return string.gsub(Regexp.new('&(apos|amp|quot|gt|lt|#[0-9]+|#x[0-9A-Fa-f]+);'.encode(enc))) do + case $1.encode(Encoding::US_ASCII) + when 'apos' then "'".encode(enc) + 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(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do + match = $1.dup + case match + when 'apos' then "'" + 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 + + # Synonym for CGI::escapeHTML(str) + alias escape_html escapeHTML + + # Synonym for CGI::unescapeHTML(str) + alias unescape_html unescapeHTML + + # 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><A HREF="url"></A>" + # + # print CGI::escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"]) + # # "<BR><A HREF="url"></A>" + def 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") + # # "<BR><A HREF="url"></A>" + # + # print CGI::unescapeElement( + # CGI::escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"]) + # # "<BR><A HREF="url"></A>" + def unescapeElement(string, *elements) + elements = elements[0] if elements[0].kind_of?(Array) + unless elements.empty? + string.gsub(/<\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?>/i) do + unescapeHTML($&) + end + else + string + end + end + + # Synonym for CGI::escapeElement(str) + alias escape_element escapeElement + + # Synonym for CGI::unescapeElement(str) + alias unescape_element unescapeElement + + # 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 ] + + # 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 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 pretty(string, shift = " ") + lines = string.gsub(/(?!\A)<.*?>/m, "\n\\0").gsub(/<.*?>(?!\n)/m, "\\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 + + alias h escapeHTML +end |