/vendor/jruby-1.1.6RC1/lib/ruby/1.8/cgi.rb
Ruby | 2303 lines | 1488 code | 78 blank | 737 comment | 70 complexity | 3698cb86a6ce553e8b4fbcc1a672034e MD5 | raw file
Possible License(s): CPL-1.0, AGPL-1.0, LGPL-2.1, JSON
Large files files are truncated, but you can click here to view the full file
- #
- # cgi.rb - cgi support library
- #
- # Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
- #
- # Copyright (C) 2000 Information-technology Promotion Agency, Japan
- #
- # Author: Wakou Aoyama <wakou@ruby-lang.org>
- #
- # Documentation: Wakou Aoyama (RDoc'd and embellished by William Webber)
- #
- # == Overview
- #
- # The Common Gateway Interface (CGI) is a simple protocol
- # for passing an HTTP request from a web server to a
- # standalone program, and returning the output to the web
- # browser. Basically, a CGI program is called with the
- # parameters of the request passed in either in the
- # environment (GET) or via $stdin (POST), and everything
- # it prints to $stdout is returned to the client.
- #
- # This file holds the +CGI+ class. This class provides
- # functionality for retrieving HTTP request parameters,
- # managing cookies, and generating HTML output. See the
- # class documentation for more details and examples of use.
- #
- # The file cgi/session.rb provides session management
- # functionality; see that file for more details.
- #
- # See http://www.w3.org/CGI/ for more information on the CGI
- # protocol.
- raise "Please, use ruby 1.5.4 or later." if RUBY_VERSION < "1.5.4"
- require 'English'
- # CGI class. See documentation for the file cgi.rb for an overview
- # of the CGI protocol.
- #
- # == Introduction
- #
- # CGI is a large class, providing several categories of methods, many of which
- # are mixed in from other modules. Some of the documentation is in this class,
- # some in the modules CGI::QueryExtension and CGI::HtmlExtension. See
- # CGI::Cookie for specific information on handling cookies, and cgi/session.rb
- # (CGI::Session) for information on sessions.
- #
- # For queries, CGI provides methods to get at environmental variables,
- # parameters, cookies, and multipart request data. For responses, CGI provides
- # methods for writing output and generating HTML.
- #
- # Read on for more details. Examples are provided at the bottom.
- #
- # == Queries
- #
- # The CGI class dynamically mixes in parameter and cookie-parsing
- # functionality, environmental variable access, and support for
- # parsing multipart requests (including uploaded files) from the
- # CGI::QueryExtension module.
- #
- # === Environmental Variables
- #
- # The standard CGI environmental variables are available as read-only
- # attributes of a CGI object. The following is a list of these variables:
- #
- #
- # AUTH_TYPE HTTP_HOST REMOTE_IDENT
- # CONTENT_LENGTH HTTP_NEGOTIATE REMOTE_USER
- # CONTENT_TYPE HTTP_PRAGMA REQUEST_METHOD
- # GATEWAY_INTERFACE HTTP_REFERER SCRIPT_NAME
- # HTTP_ACCEPT HTTP_USER_AGENT SERVER_NAME
- # HTTP_ACCEPT_CHARSET PATH_INFO SERVER_PORT
- # HTTP_ACCEPT_ENCODING PATH_TRANSLATED SERVER_PROTOCOL
- # HTTP_ACCEPT_LANGUAGE QUERY_STRING SERVER_SOFTWARE
- # HTTP_CACHE_CONTROL REMOTE_ADDR
- # HTTP_FROM REMOTE_HOST
- #
- #
- # For each of these variables, there is a corresponding attribute with the
- # same name, except all lower case and without a preceding HTTP_.
- # +content_length+ and +server_port+ are integers; the rest are strings.
- #
- # === Parameters
- #
- # The method #params() returns a hash of all parameters in the request as
- # name/value-list pairs, where the value-list is an Array of one or more
- # values. The CGI object itself also behaves as a hash of parameter names
- # to values, but only returns a single value (as a String) for each
- # parameter name.
- #
- # For instance, suppose the request contains the parameter
- # "favourite_colours" with the multiple values "blue" and "green". The
- # following behaviour would occur:
- #
- # cgi.params["favourite_colours"] # => ["blue", "green"]
- # cgi["favourite_colours"] # => "blue"
- #
- # If a parameter does not exist, the former method will return an empty
- # array, the latter an empty string. The simplest way to test for existence
- # of a parameter is by the #has_key? method.
- #
- # === Cookies
- #
- # HTTP Cookies are automatically parsed from the request. They are available
- # from the #cookies() accessor, which returns a hash from cookie name to
- # CGI::Cookie object.
- #
- # === Multipart requests
- #
- # If a request's method is POST and its content type is multipart/form-data,
- # then it may contain uploaded files. These are stored by the QueryExtension
- # module in the parameters of the request. The parameter name is the name
- # attribute of the file input field, as usual. However, the value is not
- # a string, but an IO object, either an IOString for small files, or a
- # Tempfile for larger ones. This object also has the additional singleton
- # methods:
- #
- # #local_path():: the path of the uploaded file on the local filesystem
- # #original_filename():: the name of the file on the client computer
- # #content_type():: the content type of the file
- #
- # == Responses
- #
- # The CGI class provides methods for sending header and content output to
- # the HTTP client, and mixes in methods for programmatic HTML generation
- # from CGI::HtmlExtension and CGI::TagMaker modules. The precise version of HTML
- # to use for HTML generation is specified at object creation time.
- #
- # === Writing output
- #
- # The simplest way to send output to the HTTP client is using the #out() method.
- # This takes the HTTP headers as a hash parameter, and the body content
- # via a block. The headers can be generated as a string using the #header()
- # method. The output stream can be written directly to using the #print()
- # method.
- #
- # === Generating HTML
- #
- # Each HTML element has a corresponding method for generating that
- # element as a String. The name of this method is the same as that
- # of the element, all lowercase. The attributes of the element are
- # passed in as a hash, and the body as a no-argument block that evaluates
- # to a String. The HTML generation module knows which elements are
- # always empty, and silently drops any passed-in body. It also knows
- # which elements require matching closing tags and which don't. However,
- # it does not know what attributes are legal for which elements.
- #
- # There are also some additional HTML generation methods mixed in from
- # the CGI::HtmlExtension module. These include individual methods for the
- # different types of form inputs, and methods for elements that commonly
- # take particular attributes where the attributes can be directly specified
- # as arguments, rather than via a hash.
- #
- # == Examples of use
- #
- # === Get form values
- #
- # require "cgi"
- # cgi = CGI.new
- # value = cgi['field_name'] # <== value string for 'field_name'
- # # if not 'field_name' included, then return "".
- # fields = cgi.keys # <== array of field names
- #
- # # returns true if form has 'field_name'
- # cgi.has_key?('field_name')
- # cgi.has_key?('field_name')
- # cgi.include?('field_name')
- #
- # CAUTION! cgi['field_name'] returned an Array with the old
- # cgi.rb(included in ruby 1.6)
- #
- # === Get form values as hash
- #
- # require "cgi"
- # cgi = CGI.new
- # params = cgi.params
- #
- # cgi.params is a hash.
- #
- # cgi.params['new_field_name'] = ["value"] # add new param
- # cgi.params['field_name'] = ["new_value"] # change value
- # cgi.params.delete('field_name') # delete param
- # cgi.params.clear # delete all params
- #
- #
- # === Save form values to file
- #
- # require "pstore"
- # db = PStore.new("query.db")
- # db.transaction do
- # db["params"] = cgi.params
- # end
- #
- #
- # === Restore form values from file
- #
- # require "pstore"
- # db = PStore.new("query.db")
- # db.transaction do
- # cgi.params = db["params"]
- # end
- #
- #
- # === Get multipart form values
- #
- # require "cgi"
- # cgi = CGI.new
- # value = cgi['field_name'] # <== value string for 'field_name'
- # value.read # <== body of value
- # value.local_path # <== path to local file of value
- # value.original_filename # <== original filename of value
- # value.content_type # <== content_type of value
- #
- # and value has StringIO or Tempfile class methods.
- #
- # === Get cookie values
- #
- # require "cgi"
- # cgi = CGI.new
- # values = cgi.cookies['name'] # <== array of 'name'
- # # if not 'name' included, then return [].
- # names = cgi.cookies.keys # <== array of cookie names
- #
- # and cgi.cookies is a hash.
- #
- # === Get cookie objects
- #
- # require "cgi"
- # cgi = CGI.new
- # for name, cookie in cgi.cookies
- # cookie.expires = Time.now + 30
- # end
- # cgi.out("cookie" => cgi.cookies) {"string"}
- #
- # cgi.cookies # { "name1" => cookie1, "name2" => cookie2, ... }
- #
- # require "cgi"
- # cgi = CGI.new
- # cgi.cookies['name'].expires = Time.now + 30
- # cgi.out("cookie" => cgi.cookies['name']) {"string"}
- #
- # === Print http header and html string to $DEFAULT_OUTPUT ($>)
- #
- # require "cgi"
- # cgi = CGI.new("html3") # add HTML generation methods
- # cgi.out() do
- # cgi.html() do
- # cgi.head{ cgi.title{"TITLE"} } +
- # cgi.body() do
- # cgi.form() do
- # cgi.textarea("get_text") +
- # cgi.br +
- # cgi.submit
- # end +
- # cgi.pre() do
- # CGI::escapeHTML(
- # "params: " + cgi.params.inspect + "\n" +
- # "cookies: " + cgi.cookies.inspect + "\n" +
- # ENV.collect() do |key, value|
- # key + " --> " + value + "\n"
- # end.join("")
- # )
- # end
- # end
- # end
- # end
- #
- # # add HTML generation methods
- # CGI.new("html3") # html3.2
- # CGI.new("html4") # html4.01 (Strict)
- # CGI.new("html4Tr") # html4.01 Transitional
- # CGI.new("html4Fr") # html4.01 Frameset
- #
- class CGI
- # :stopdoc:
- # String for carriage return
- CR = "\015"
- # String for linefeed
- LF = "\012"
- # Standard internet newline sequence
- EOL = CR + LF
- REVISION = '$Id: cgi.rb 5479 2008-01-03 21:39:44Z headius $' #:nodoc:
- NEEDS_BINMODE = true if /WIN/ni.match(RUBY_PLATFORM)
- # Path separators in different environments.
- PATH_SEPARATOR = {'UNIX'=>'/', 'WINDOWS'=>'\\', 'MACINTOSH'=>':'}
- # HTTP status codes.
- HTTP_STATUS = {
- "OK" => "200 OK",
- "PARTIAL_CONTENT" => "206 Partial Content",
- "MULTIPLE_CHOICES" => "300 Multiple Choices",
- "MOVED" => "301 Moved Permanently",
- "REDIRECT" => "302 Found",
- "NOT_MODIFIED" => "304 Not Modified",
- "BAD_REQUEST" => "400 Bad Request",
- "AUTH_REQUIRED" => "401 Authorization Required",
- "FORBIDDEN" => "403 Forbidden",
- "NOT_FOUND" => "404 Not Found",
- "METHOD_NOT_ALLOWED" => "405 Method Not Allowed",
- "NOT_ACCEPTABLE" => "406 Not Acceptable",
- "LENGTH_REQUIRED" => "411 Length Required",
- "PRECONDITION_FAILED" => "412 Rrecondition Failed",
- "SERVER_ERROR" => "500 Internal Server Error",
- "NOT_IMPLEMENTED" => "501 Method Not Implemented",
- "BAD_GATEWAY" => "502 Bad Gateway",
- "VARIANT_ALSO_VARIES" => "506 Variant Also Negotiates"
- }
- # Abbreviated day-of-week names specified by RFC 822
- RFC822_DAYS = %w[ Sun Mon Tue Wed Thu Fri Sat ]
- # Abbreviated month names specified by RFC 822
- RFC822_MONTHS = %w[ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ]
- # :startdoc:
- def env_table
- ENV
- end
- def stdinput
- $stdin
- end
- def stdoutput
- $DEFAULT_OUTPUT
- end
- private :env_table, :stdinput, :stdoutput
- # URL-encode a string.
- # url_encoded_string = CGI::escape("'Stop!' said Fred")
- # # => "%27Stop%21%27+said+Fred"
- def CGI::escape(string)
- string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
- '%' + $1.unpack('H2' * $1.size).join('%').upcase
- end.tr(' ', '+')
- end
- # URL-decode a string.
- # string = CGI::unescape("%27Stop%21%27+said+Fred")
- # # => "'Stop!' said Fred"
- def CGI::unescape(string)
- string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) do
- [$1.delete('%')].pack('H*')
- end
- end
- # Escape special characters in HTML, namely &\"<>
- # CGI::escapeHTML('Usage: foo "bar" <baz>')
- # # => "Usage: foo "bar" <baz>"
- def CGI::escapeHTML(string)
- string.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/</n, '<')
- end
- # Unescape a string that has been HTML-escaped
- # CGI::unescapeHTML("Usage: foo "bar" <baz>")
- # # => "Usage: foo \"bar\" <baz>"
- def CGI::unescapeHTML(string)
- string.gsub(/&(amp|quot|gt|lt|\#[0-9]+|\#x[0-9A-Fa-f]+);/n) do
- match = $1.dup
- case match
- when 'amp' then '&'
- when 'quot' then '"'
- when 'gt' then '>'
- when 'lt' then '<'
- when /\A#0*(\d+)\z/n then
- if Integer($1) < 256
- Integer($1).chr
- else
- if Integer($1) < 65536 and ($KCODE[0] == ?u or $KCODE[0] == ?U)
- [Integer($1)].pack("U")
- else
- "&##{$1};"
- end
- end
- when /\A#x([0-9a-f]+)\z/ni then
- if $1.hex < 256
- $1.hex.chr
- else
- if $1.hex < 65536 and ($KCODE[0] == ?u or $KCODE[0] == ?U)
- [$1.hex].pack("U")
- else
- "&#x#{$1};"
- end
- end
- else
- "&#{match};"
- end
- end
- 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><A HREF="url"></A>"
- #
- # print CGI::escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"])
- # # "<BR><A HREF="url"></A>"
- def CGI::escapeElement(string, *elements)
- elements = elements[0] if elements[0].kind_of?(Array)
- unless elements.empty?
- string.gsub(/<\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?>/ni) 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 CGI::unescapeElement(string, *elements)
- elements = elements[0] if elements[0].kind_of?(Array)
- unless elements.empty?
- string.gsub(/<\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?>/ni) do
- CGI::unescapeHTML($&)
- end
- else
- string
- end
- end
- # Format a +Time+ object as a String using the format specified by RFC 1123.
- #
- # CGI::rfc1123_date(Time.now)
- # # Sat, 01 Jan 2000 00:00:00 GMT
- def CGI::rfc1123_date(time)
- t = time.clone.gmtime
- return format("%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
- RFC822_DAYS[t.wday], t.day, RFC822_MONTHS[t.month-1], t.year,
- t.hour, t.min, t.sec)
- end
- # Create an HTTP header block as a string.
- #
- # Includes the empty line that ends the header block.
- #
- # +options+ can be a string specifying the Content-Type (defaults
- # to text/html), or a hash of header key/value pairs. The following
- # header keys are recognized:
- #
- # type:: the Content-Type header. Defaults to "text/html"
- # charset:: the charset of the body, appended to the Content-Type header.
- # nph:: a boolean value. If true, prepend protocol string and status code, and
- # date; and sets default values for "server" and "connection" if not
- # explicitly set.
- # status:: the HTTP status code, returned as the Status header. See the
- # list of available status codes below.
- # server:: the server software, returned as the Server header.
- # connection:: the connection type, returned as the Connection header (for
- # instance, "close".
- # length:: the length of the content that will be sent, returned as the
- # Content-Length header.
- # language:: the language of the content, returned as the Content-Language
- # header.
- # expires:: the time on which the current content expires, as a +Time+
- # object, returned as the Expires header.
- # cookie:: a cookie or cookies, returned as one or more Set-Cookie headers.
- # The value can be the literal string of the cookie; a CGI::Cookie
- # object; an Array of literal cookie strings or Cookie objects; or a
- # hash all of whose values are literal cookie strings or Cookie objects.
- # These cookies are in addition to the cookies held in the
- # @output_cookies field.
- #
- # Other header lines can also be set; they are appended as key: value.
- #
- # header
- # # Content-Type: text/html
- #
- # header("text/plain")
- # # Content-Type: text/plain
- #
- # header("nph" => true,
- # "status" => "OK", # == "200 OK"
- # # "status" => "200 GOOD",
- # "server" => ENV['SERVER_SOFTWARE'],
- # "connection" => "close",
- # "type" => "text/html",
- # "charset" => "iso-2022-jp",
- # # Content-Type: text/html; charset=iso-2022-jp
- # "length" => 103,
- # "language" => "ja",
- # "expires" => Time.now + 30,
- # "cookie" => [cookie1, cookie2],
- # "my_header1" => "my_value"
- # "my_header2" => "my_value")
- #
- # The status codes are:
- #
- # "OK" --> "200 OK"
- # "PARTIAL_CONTENT" --> "206 Partial Content"
- # "MULTIPLE_CHOICES" --> "300 Multiple Choices"
- # "MOVED" --> "301 Moved Permanently"
- # "REDIRECT" --> "302 Found"
- # "NOT_MODIFIED" --> "304 Not Modified"
- # "BAD_REQUEST" --> "400 Bad Request"
- # "AUTH_REQUIRED" --> "401 Authorization Required"
- # "FORBIDDEN" --> "403 Forbidden"
- # "NOT_FOUND" --> "404 Not Found"
- # "METHOD_NOT_ALLOWED" --> "405 Method Not Allowed"
- # "NOT_ACCEPTABLE" --> "406 Not Acceptable"
- # "LENGTH_REQUIRED" --> "411 Length Required"
- # "PRECONDITION_FAILED" --> "412 Precondition Failed"
- # "SERVER_ERROR" --> "500 Internal Server Error"
- # "NOT_IMPLEMENTED" --> "501 Method Not Implemented"
- # "BAD_GATEWAY" --> "502 Bad Gateway"
- # "VARIANT_ALSO_VARIES" --> "506 Variant Also Negotiates"
- #
- # This method does not perform charset conversion.
- #
- def header(options = "text/html")
- buf = ""
- case options
- when String
- options = { "type" => options }
- when Hash
- options = options.dup
- end
- unless options.has_key?("type")
- options["type"] = "text/html"
- end
- if options.has_key?("charset")
- options["type"] += "; charset=" + options.delete("charset")
- end
- options.delete("nph") if defined?(MOD_RUBY)
- if options.delete("nph") or
- (/IIS\/(\d+)/n.match(env_table['SERVER_SOFTWARE']) and $1.to_i < 5)
- buf += (env_table["SERVER_PROTOCOL"] or "HTTP/1.0") + " " +
- (HTTP_STATUS[options["status"]] or options["status"] or "200 OK") +
- EOL +
- "Date: " + CGI::rfc1123_date(Time.now) + EOL
- unless options.has_key?("server")
- options["server"] = (env_table['SERVER_SOFTWARE'] or "")
- end
- unless options.has_key?("connection")
- options["connection"] = "close"
- end
- options.delete("status")
- end
- if options.has_key?("status")
- buf += "Status: " +
- (HTTP_STATUS[options["status"]] or options["status"]) + EOL
- options.delete("status")
- end
- if options.has_key?("server")
- buf += "Server: " + options.delete("server") + EOL
- end
- if options.has_key?("connection")
- buf += "Connection: " + options.delete("connection") + EOL
- end
- buf += "Content-Type: " + options.delete("type") + EOL
- if options.has_key?("length")
- buf += "Content-Length: " + options.delete("length").to_s + EOL
- end
- if options.has_key?("language")
- buf += "Content-Language: " + options.delete("language") + EOL
- end
- if options.has_key?("expires")
- buf += "Expires: " + CGI::rfc1123_date( options.delete("expires") ) + EOL
- end
- if options.has_key?("cookie")
- if options["cookie"].kind_of?(String) or
- options["cookie"].kind_of?(Cookie)
- buf += "Set-Cookie: " + options.delete("cookie").to_s + EOL
- elsif options["cookie"].kind_of?(Array)
- options.delete("cookie").each{|cookie|
- buf += "Set-Cookie: " + cookie.to_s + EOL
- }
- elsif options["cookie"].kind_of?(Hash)
- options.delete("cookie").each_value{|cookie|
- buf += "Set-Cookie: " + cookie.to_s + EOL
- }
- end
- end
- if @output_cookies
- for cookie in @output_cookies
- buf += "Set-Cookie: " + cookie.to_s + EOL
- end
- end
- options.each{|key, value|
- buf += key + ": " + value.to_s + EOL
- }
- if defined?(MOD_RUBY)
- table = Apache::request.headers_out
- buf.scan(/([^:]+): (.+)#{EOL}/n){ |name, value|
- warn sprintf("name:%s value:%s\n", name, value) if $DEBUG
- case name
- when 'Set-Cookie'
- table.add(name, value)
- when /^status$/ni
- Apache::request.status_line = value
- Apache::request.status = value.to_i
- when /^content-type$/ni
- Apache::request.content_type = value
- when /^content-encoding$/ni
- Apache::request.content_encoding = value
- when /^location$/ni
- if Apache::request.status == 200
- Apache::request.status = 302
- end
- Apache::request.headers_out[name] = value
- else
- Apache::request.headers_out[name] = value
- end
- }
- Apache::request.send_http_header
- ''
- else
- buf + EOL
- end
- end # header()
- # Print an HTTP header and body to $DEFAULT_OUTPUT ($>)
- #
- # The header is provided by +options+, as for #header().
- # The body of the document is that returned by the passed-
- # in block. This block takes no arguments. It is required.
- #
- # cgi = CGI.new
- # cgi.out{ "string" }
- # # Content-Type: text/html
- # # Content-Length: 6
- # #
- # # string
- #
- # cgi.out("text/plain") { "string" }
- # # Content-Type: text/plain
- # # Content-Length: 6
- # #
- # # string
- #
- # cgi.out("nph" => true,
- # "status" => "OK", # == "200 OK"
- # "server" => ENV['SERVER_SOFTWARE'],
- # "connection" => "close",
- # "type" => "text/html",
- # "charset" => "iso-2022-jp",
- # # Content-Type: text/html; charset=iso-2022-jp
- # "language" => "ja",
- # "expires" => Time.now + (3600 * 24 * 30),
- # "cookie" => [cookie1, cookie2],
- # "my_header1" => "my_value",
- # "my_header2" => "my_value") { "string" }
- #
- # Content-Length is automatically calculated from the size of
- # the String returned by the content block.
- #
- # If ENV['REQUEST_METHOD'] == "HEAD", then only the header
- # is outputted (the content block is still required, but it
- # is ignored).
- #
- # If the charset is "iso-2022-jp" or "euc-jp" or "shift_jis" then
- # the content is converted to this charset, and the language is set
- # to "ja".
- def out(options = "text/html") # :yield:
- options = { "type" => options } if options.kind_of?(String)
- content = yield
- if options.has_key?("charset")
- require "nkf"
- case options["charset"]
- when /iso-2022-jp/ni
- content = NKF::nkf('-m0 -x -j', content)
- options["language"] = "ja" unless options.has_key?("language")
- when /euc-jp/ni
- content = NKF::nkf('-m0 -x -e', content)
- options["language"] = "ja" unless options.has_key?("language")
- when /shift_jis/ni
- content = NKF::nkf('-m0 -x -s', content)
- options["language"] = "ja" unless options.has_key?("language")
- end
- end
- options["length"] = content.length.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
- require "delegate"
- # 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 < DelegateClass(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)
- options = if name.kind_of?(String)
- { "name" => name, "value" => value }
- else
- name
- end
- unless options.has_key?("name")
- raise ArgumentError, "`name' required"
- end
- @name = options["name"]
- @value = Array(options["value"])
- # simple support for IE
- if options["path"]
- @path = options["path"]
- else
- %r|^(.*/)|.match(ENV["SCRIPT_NAME"])
- @path = ($1 or "")
- end
- @domain = options["domain"]
- @expires = options["expires"]
- @secure = options["secure"] == true ? true : false
- super(@value)
- end
- attr_accessor("name", "value", "path", "domain", "expires")
- attr_reader("secure")
- # Set whether the Cookie is a secure cookie or not.
- #
- # +val+ must be a boolean.
- def secure=(val)
- @secure = val if val == true or val == false
- @secure
- end
- # Convert the Cookie to its string representation.
- def to_s
- buf = ""
- buf += @name + '='
- if @value.kind_of?(String)
- buf += CGI::escape(@value)
- else
- buf += @value.collect{|v| CGI::escape(v) }.join("&")
- end
- if @domain
- buf += '; domain=' + @domain
- end
- if @path
- buf += '; path=' + @path
- end
- if @expires
- buf += '; expires=' + CGI::rfc1123_date(@expires)
- end
- if @secure == true
- buf += '; secure'
- end
- buf
- end
- end # class Cookie
- # Parse a raw cookie string into a hash of cookie-name=>Cookie
- # pairs.
- #
- # cookies = CGI::Cookie::parse("raw_cookie_string")
- # # { "name1" => cookie1, "name2" => cookie2, ... }
- #
- def Cookie::parse(raw_cookie)
- cookies = Hash.new([])
- return cookies unless raw_cookie
- raw_cookie.split(/[;,]\s?/).each do |pairs|
- name, values = pairs.split('=',2)
- next unless name and values
- name = CGI::unescape(name)
- values ||= ""
- values = values.split('&').collect{|v| CGI::unescape(v) }
- if cookies.has_key?(name)
- values = cookies[name].value + values
- end
- cookies[name] = Cookie::new({ "name" => name, "value" => values })
- end
- cookies
- end
- # Parse an HTTP query string into a hash of key=>value pairs.
- #
- # params = CGI::parse("query_string")
- # # {"name1" => ["value1", "value2", ...],
- # # "name2" => ["value1", "value2", ...], ... }
- #
- def CGI::parse(query)
- params = Hash.new([].freeze)
- query.split(/[&;]/n).each do |pairs|
- key, value = pairs.split('=',2).collect{|v| CGI::unescape(v) }
- if params.has_key?(key)
- params[key].push(value)
- else
- params[key] = [value]
- end
- end
- params
- end
- # Mixin module. It provides the follow functionality groups:
- #
- # 1. Access to CGI environment variables as methods. See
- # documentation to the CGI class for a list of these variables.
- #
- # 2. Access to cookies, including the cookies attribute.
- #
- # 3. Access to parameters, including the params attribute, and overloading
- # [] to perform parameter value lookup by key.
- #
- # 4. The initialize_query method, for initialising the above
- # mechanisms, handling multipart forms, and allowing the
- # class to be used in "offline" mode.
- #
- module QueryExtension
- %w[ CONTENT_LENGTH SERVER_PORT ].each do |env|
- define_method(env.sub(/^HTTP_/n, '').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_/n, '').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("params")
- # Set all the parameters.
- def params=(hash)
- @params.clear
- @params.update(hash)
- end
- def read_multipart(boundary, content_length)
- params = Hash.new([])
- boundary = "--" + boundary
- quoted_boundary = Regexp.quote(boundary, "n")
- buf = ""
- bufsize = 10 * 1024
- boundary_end=""
- # start multipart/form-data
- stdinput.binmode if defined? stdinput.binmode
- boundary_size = boundary.size + EOL.size
- content_length -= boundary_size
- status = stdinput.read(boundary_size)
- if nil == status
- raise EOFError, "no content body"
- elsif boundary + EOL != status
- raise EOFError, "bad content body"
- end
- loop do
- head = nil
- if 10240 < content_length
- require "tempfile"
- body = Tempfile.new("CGI")
- else
- begin
- require "stringio"
- body = StringIO.new
- rescue LoadError
- require "tempfile"
- body = Tempfile.new("CGI")
- end
- end
- body.binmode if defined? body.binmode
- until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
- if (not head) and /#{EOL}#{EOL}/n.match(buf)
- buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
- head = $1.dup
- ""
- end
- next
- end
- if head and ( (EOL + boundary + EOL).size < buf.size )
- body.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
- buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
- end
- c = if bufsize < content_length
- stdinput.read(bufsize)
- else
- stdinput.read(content_length)
- end
- if c.nil? || c.empty?
- raise EOFError, "bad content body"
- end
- buf.concat(c)
- content_length -= c.size
- end
- buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
- body.print $1
- if "--" == $2
- content_length = -1
- end
- boundary_end = $2.dup
- ""
- end
- body.rewind
- /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni.match(head)
- filename = ($1 or $2 or "")
- if /Mac/ni.match(env_table['HTTP_USER_AGENT']) and
- /Mozilla/ni.match(env_table['HTTP_USER_AGENT']) and
- (not /MSIE/ni.match(env_table['HTTP_USER_AGENT']))
- filename = CGI::unescape(filename)
- end
-
- /Content-Type: (.*)/ni.match(head)
- content_type = ($1 or "")
- (class << body; self; end).class_eval do
- alias local_path path
- define_method(:original_filename) {filename.dup.taint}
- define_method(:content_type) {content_type.dup.taint}
- end
- /Content-Disposition:.* name="?([^\";]*)"?/ni.match(head)
- name = $1.dup
- if params.has_key?(name)
- params[name].push(body)
- else
- params[name] = [body]
- end
- break if buf.size == 0
- break if content_length == -1
- end
- raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
- params
- end # read_multipart
- private :read_multipart
- # offline mode. read name=value pairs on standard input.
- def read_from_cmdline
- require "shellwords"
- string = unless ARGV.empty?
- ARGV.join(' ')
- else
- if STDIN.tty?
- STDERR.print(
- %|(offline mode: enter name=value pairs on standard input)\n|
- )
- end
- readlines.join(' ').gsub(/\n/n, '')
- 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
- # 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=\"?([^\";,]+)\"?|n.match(env_table['CONTENT_TYPE'])
- boundary = $1.dup
- @multipart = true
- @params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH']))
- else
- @multipart = false
- @params = CGI::parse(
- case env_table['REQUEST_METHOD']
- when "GET", "HEAD"
- if defined?(MOD_RUBY)
- Apache::request.args or ""
- else
- env_table['QUERY_STRING'] or ""
- end
- when "POST"
- stdinput.binmode if defined? stdinput.binmode
- stdinput.read(Integer(env_table['CONTENT_LENGTH'])) or ''
- else
- read_from_cmdline
- end
- )
- end
- @cookies = CGI::Cookie::parse((env_table['HTTP_COOKIE'] or env_table['COOKIE']))
- end
- private :initialize_query
- def multipart?
- @multipart
- end
- module Value # :nodoc:
- def set_params(params)
- @params = params
- end
- def [](idx, *args)
- if args.size == 0
- warn "#{caller(1)[0]}:CAUTION! cgi['key'] == cgi.params['key'][0]; if want Array, use cgi.params['key']"
- @params[idx]
- else
- super[idx,*args]
- end
- end
- def first
- warn "#{caller(1)[0]}:CAUTION! cgi['key'] == cgi.params['key'][0]; if want Array, use cgi.params['key']"
- self
- end
- alias last first
- def to_a
- @params || [self]
- end
- alias to_ary to_a # to be rhs of multiple assignment
- end
- # Get the value for the parameter with a given key.
- #
- # If the parameter has multiple values, only the first will be
- # retrieved; use #params() to get the array of values.
- def [](key)
- params = @params[key]
- return '' unless params
- value = params[0]
- if @multipart
- if value
- return value
- elsif defined? StringIO
- StringIO.new("")
- else
- Tempfile.new("CGI")
- end
- else
- str = if value then value.dup else "" end
- str.extend(Value)
- str.set_params(params)
- str
- end
- end
- # Return all parameter keys as an array.
- def keys(*args)
- @params.keys(*args)
- end
- # Returns true if a given parameter key exists in the query.
- def has_key?(*args)
- @params.has_key?(*args)
- end
- alias key? has_key?
- alias include? has_key?
- end # QueryExtension
- # Prettify (indent) an HTML string.
- #
- # +string+ is the HTML string to indent. +shift+ is the indentation
- # unit to use; it defaults to two spaces.
- #
- # print CGI::pretty("<HTML><BODY></BODY></HTML>")
- # # <HTML>
- # # <BODY>
- # # </BODY>
- # # </HTML>
- #
- # print CGI::pretty("<HTML><BODY></BODY></HTML>", "\t")
- # # <HTML>
- # # <BODY>
- # # </BODY>
- # # </HTML>
- #
- def CGI::pretty(string, shift = " ")
- lines = string.gsub(/(?!\A)<(?:.|\n)*?>/n, "\n\\0").gsub(/<(?:.|\n)*?>(?!\n)/n, "\\0\n")
- end_pos = 0
- while end_pos = lines.index(/^<\/(\w+)/n, end_pos)
- element = $1.dup
- start_pos = lines.rindex(/^\s*<#{element}/ni, end_pos)
- lines[start_pos ... end_pos] = "__" + lines[start_pos ... end_pos].gsub(/\n(?!\z)/n, "\n" + shift) + "__"
- end
- lines.gsub(/^((?:#{Regexp::quote(shift)})*)__(?=<\/?\w)/n, '\1')
- end
- # Base module for HTML-generation mixins.
- #
- # Provides methods for code generation for tags following
- # the various DTD element types.
- module TagMaker # :nodoc:
- # Generate code for an element with required start and end tags.
- #
- # - -
- def nn_element_def(element)
- nOE_element_def(element, <<-END)
- if block_given?
- yield.to_s
- else
- ""
- end +
- "</#{element.upcase}>"
- END
- end
- # Generate code for an empty element.
- #
- # - O EMPTY
- def nOE_element_def(element, append = nil)
- s = <<-END
- "<#{element.upcase}" + attributes.collect{|name, value|
- next unless value
- " " + CGI::escapeHTML(name) +
- if true == value
- ""
- else
- '="' + CGI::escapeHTML(value) + '"'
- end
- }.to_s + ">"
- 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 = nil) # :yield:
- attributes = if cite.kind_of?(String)
- { "CITE" => cite }
- else
- cite or ""
- 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 = nil) # :yield:
- attributes = if align.kind_of?(String)
- { "ALIGN" => align }
- else
- align or ""
- end
- if block_given?
- super(attributes){ yield }
- else
- super(attributes)
- end
- end
- # Generate a Checkbox Input element as a string.
- #
- # The attributes of the element can be specified as three arguments,
- # +name+, +value+, and +checked+. +checked+ is a boolean value;
- # if true, the CHECKED attribute will be included in the element.
- #
- # Alternatively, the attributes can be specified as a hash.
- #
- # checkbox("name")
- # # = checkbox("NAME" => "name")
- #
- # checkbox("name", "value")
- # # = checkbox("NAME" => "name", "VALUE" => "value")
- #
- # checkbox("name", "value", true)
- # # = checkbox("NAME" => "name", "VALUE" => "value", "CHECKED" => true)
- def checkbox(name = "", value = nil, checked = nil)
- attributes = if name.kind_of?(String)
- { "TYPE" => "checkbox", "NAME" => name,
- "VALUE" => value, "CHECKED" => checked }
- else
- name["TYPE"] = "checkbox"
- name
- end
- input(attributes)
- end
- # Generate a sequence of checkbox elements, as a String.
- #
- # The checkboxes will all have the same +name+ attribute.
- # Each checkbox is followed by a label.
- # There will be one checkbox for each value. Each value
- # can be specified as a String, which will be used both
- # as the value of the VALUE attribute and as the label
- # for that checkbox. A single-element array has the
- # same effect.
- #
- # Each value can also be specified as a three-element array.
- # The first element is the VALUE attribute; the second is the
- # label; and the third is a boolean specifying whether this
- # checkbox is CHECKED.
- #
- # Each value can also be specified as a two-element
- # array, by omitting either the value element (defaults
- # to the same as the label), or the boolean checked element
- # (defaults to false).
- #
- # checkbox_group("name", "foo", "bar", "baz")
- # # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo
- # # <INPUT TYPE="checkbox" NAME="name" VALUE="bar">bar
- # # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz
- #
- # checkbox_group("name", ["foo"], ["bar", true], "baz")
- # # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo
- # # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="bar">bar
- # # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz
- #
- # checkbox_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz")
- # # <INPUT TYPE="checkbox" NAME="name" VALUE="1">Foo
- # # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="2">Bar
- # # <INPUT TYPE="checkbox" NAME="name" VALUE="Baz">Baz
- #
- # checkbox_group("NAME" => "name",
- # "VALUES" => ["foo", "bar", "baz"])
- #
- # checkbox_group("NAME" => "name",
- # "VALUES" => [["foo"], ["bar", true], "baz"])
- #
- # checkbox_group("NAME" => "name",
- # "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])
- def checkbox_group(name = "", *values)
- if name.kind_of?(Hash)
- values = name["VALUES"]
- name = name["NAME"]
- end
- values.collect{|value|
- if value.kind_of?(String)
- checkbox(name, value) + value
- else
- if value[value.size - 1] == true
- checkbox(name, value[0], true) +
- value[value.size - 2]
- else
- checkbox(name, value[0]) +
- value[value.size - 1]
- end
- end
- }.to_s
- 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)
- …
Large files files are truncated, but you can click here to view the full file