mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
* lib/cgi*: split cgi.rb into four files. [ruby-dev:36041]
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@19272 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
d456f7e357
commit
0bbda39758
6 changed files with 2076 additions and 2060 deletions
|
@ -1,3 +1,7 @@
|
|||
Tue Sep 9 21:59:48 2008 Takeyuki Fujioka <xibbar@ruby-lang.org>
|
||||
|
||||
* lib/cgi*: split cgi.rb into four files. [ruby-dev:36041]
|
||||
|
||||
Tue Sep 9 21:17:00 2008 Tanaka Akira <akr@fsij.org>
|
||||
|
||||
* include/ruby/encoding.h (ECONV_DECORATOR_MASK): merge
|
||||
|
|
2064
lib/cgi.rb
2064
lib/cgi.rb
File diff suppressed because it is too large
Load diff
156
lib/cgi/cookie.rb
Normal file
156
lib/cgi/cookie.rb
Normal file
|
@ -0,0 +1,156 @@
|
|||
# Class representing an HTTP cookie.
|
||||
#
|
||||
# In addition to its specific fields and methods, a Cookie instance
|
||||
# is a delegator to the array of its values.
|
||||
#
|
||||
# See RFC 2965.
|
||||
#
|
||||
# == Examples of use
|
||||
# cookie1 = CGI::Cookie::new("name", "value1", "value2", ...)
|
||||
# cookie1 = CGI::Cookie::new("name" => "name", "value" => "value")
|
||||
# cookie1 = CGI::Cookie::new('name' => 'name',
|
||||
# 'value' => ['value1', 'value2', ...],
|
||||
# 'path' => 'path', # optional
|
||||
# 'domain' => 'domain', # optional
|
||||
# 'expires' => Time.now, # optional
|
||||
# 'secure' => true # optional
|
||||
# )
|
||||
#
|
||||
# cgi.out("cookie" => [cookie1, cookie2]) { "string" }
|
||||
#
|
||||
# name = cookie1.name
|
||||
# values = cookie1.value
|
||||
# path = cookie1.path
|
||||
# domain = cookie1.domain
|
||||
# expires = cookie1.expires
|
||||
# secure = cookie1.secure
|
||||
#
|
||||
# cookie1.name = 'name'
|
||||
# cookie1.value = ['value1', 'value2', ...]
|
||||
# cookie1.path = 'path'
|
||||
# cookie1.domain = 'domain'
|
||||
# cookie1.expires = Time.now + 30
|
||||
# cookie1.secure = true
|
||||
class CGI
|
||||
class Cookie < Array
|
||||
|
||||
# Create a new CGI::Cookie object.
|
||||
#
|
||||
# The contents of the cookie can be specified as a +name+ and one
|
||||
# or more +value+ arguments. Alternatively, the contents can
|
||||
# be specified as a single hash argument. The possible keywords of
|
||||
# this hash are as follows:
|
||||
#
|
||||
# name:: the name of the cookie. Required.
|
||||
# value:: the cookie's value or list of values.
|
||||
# path:: the path for which this cookie applies. Defaults to the
|
||||
# base directory of the CGI script.
|
||||
# domain:: the domain for which this cookie applies.
|
||||
# expires:: the time at which this cookie expires, as a +Time+ object.
|
||||
# secure:: whether this cookie is a secure cookie or not (default to
|
||||
# false). Secure cookies are only transmitted to HTTPS
|
||||
# servers.
|
||||
#
|
||||
# These keywords correspond to attributes of the cookie object.
|
||||
def initialize(name = "", *value)
|
||||
if name.kind_of?(String)
|
||||
@name = name
|
||||
@value = value
|
||||
%r|^(.*/)|.match(ENV["SCRIPT_NAME"])
|
||||
@path = ($1 or "")
|
||||
@secure = false
|
||||
return super(@value)
|
||||
end
|
||||
|
||||
options = name
|
||||
unless options.has_key?("name")
|
||||
raise ArgumentError, "`name' required"
|
||||
end
|
||||
|
||||
@name = options["name"]
|
||||
@value = Array(options["value"])
|
||||
# simple support for IE
|
||||
if options["path"]
|
||||
@path = options["path"]
|
||||
else
|
||||
%r|^(.*/)|.match(ENV["SCRIPT_NAME"])
|
||||
@path = ($1 or "")
|
||||
end
|
||||
@domain = options["domain"]
|
||||
@expires = options["expires"]
|
||||
@secure = options["secure"] == true ? true : false
|
||||
|
||||
super(@value)
|
||||
end
|
||||
|
||||
attr_accessor("name", "value", "path", "domain", "expires")
|
||||
attr_reader("secure")
|
||||
|
||||
# Set whether the Cookie is a secure cookie or not.
|
||||
#
|
||||
# +val+ must be a boolean.
|
||||
def secure=(val)
|
||||
@secure = val if val == true or val == false
|
||||
@secure
|
||||
end
|
||||
|
||||
# Convert the Cookie to its string representation.
|
||||
def to_s
|
||||
buf = ""
|
||||
buf += @name + '='
|
||||
|
||||
if @value.kind_of?(String)
|
||||
buf += CGI::escape(@value)
|
||||
else
|
||||
buf += @value.collect{|v| CGI::escape(v) }.join("&")
|
||||
end
|
||||
|
||||
if @domain
|
||||
buf += '; domain=' + @domain
|
||||
end
|
||||
|
||||
if @path
|
||||
buf += '; path=' + @path
|
||||
end
|
||||
|
||||
if @expires
|
||||
buf += '; expires=' + CGI::rfc1123_date(@expires)
|
||||
end
|
||||
|
||||
if @secure == true
|
||||
buf += '; secure'
|
||||
end
|
||||
|
||||
buf
|
||||
end
|
||||
|
||||
end # class Cookie
|
||||
|
||||
|
||||
# Parse a raw cookie string into a hash of cookie-name=>Cookie
|
||||
# pairs.
|
||||
#
|
||||
# cookies = CGI::Cookie::parse("raw_cookie_string")
|
||||
# # { "name1" => cookie1, "name2" => cookie2, ... }
|
||||
#
|
||||
def Cookie::parse(raw_cookie)
|
||||
cookies = Hash.new([])
|
||||
return cookies unless raw_cookie
|
||||
|
||||
raw_cookie.split(/[;,]\s?/).each do |pairs|
|
||||
name, values = pairs.split('=',2)
|
||||
next unless name and values
|
||||
name = CGI::unescape(name)
|
||||
values ||= ""
|
||||
values = values.split('&').collect{|v| CGI::unescape(v) }
|
||||
if cookies.has_key?(name)
|
||||
values = cookies[name].value + values
|
||||
end
|
||||
cookies[name] = Cookie::new(name, *values)
|
||||
end
|
||||
|
||||
cookies
|
||||
end
|
||||
end
|
||||
|
||||
|
710
lib/cgi/core.rb
Normal file
710
lib/cgi/core.rb
Normal file
|
@ -0,0 +1,710 @@
|
|||
class CGI
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
# String for carriage return
|
||||
CR = "\015"
|
||||
|
||||
# String for linefeed
|
||||
LF = "\012"
|
||||
|
||||
# Standard internet newline sequence
|
||||
EOL = CR + LF
|
||||
|
||||
REVISION = '$Id: cgi.rb 19245 2008-09-08 13:27:17Z xibbar $' #:nodoc:
|
||||
|
||||
NEEDS_BINMODE = true if /WIN/i.match(RUBY_PLATFORM)
|
||||
|
||||
# Path separators in different environments.
|
||||
PATH_SEPARATOR = {'UNIX'=>'/', 'WINDOWS'=>'\\', 'MACINTOSH'=>':'}
|
||||
|
||||
# HTTP status codes.
|
||||
HTTP_STATUS = {
|
||||
"OK" => "200 OK",
|
||||
"PARTIAL_CONTENT" => "206 Partial Content",
|
||||
"MULTIPLE_CHOICES" => "300 Multiple Choices",
|
||||
"MOVED" => "301 Moved Permanently",
|
||||
"REDIRECT" => "302 Found",
|
||||
"NOT_MODIFIED" => "304 Not Modified",
|
||||
"BAD_REQUEST" => "400 Bad Request",
|
||||
"AUTH_REQUIRED" => "401 Authorization Required",
|
||||
"FORBIDDEN" => "403 Forbidden",
|
||||
"NOT_FOUND" => "404 Not Found",
|
||||
"METHOD_NOT_ALLOWED" => "405 Method Not Allowed",
|
||||
"NOT_ACCEPTABLE" => "406 Not Acceptable",
|
||||
"LENGTH_REQUIRED" => "411 Length Required",
|
||||
"PRECONDITION_FAILED" => "412 Rrecondition Failed",
|
||||
"SERVER_ERROR" => "500 Internal Server Error",
|
||||
"NOT_IMPLEMENTED" => "501 Method Not Implemented",
|
||||
"BAD_GATEWAY" => "502 Bad Gateway",
|
||||
"VARIANT_ALSO_VARIES" => "506 Variant Also Negotiates"
|
||||
}
|
||||
|
||||
# Abbreviated day-of-week names specified by RFC 822
|
||||
RFC822_DAYS = %w[ Sun Mon Tue Wed Thu Fri Sat ]
|
||||
|
||||
# Abbreviated month names specified by RFC 822
|
||||
RFC822_MONTHS = %w[ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ]
|
||||
|
||||
# :startdoc:
|
||||
|
||||
def env_table
|
||||
ENV
|
||||
end
|
||||
|
||||
def stdinput
|
||||
$stdin
|
||||
end
|
||||
|
||||
def stdoutput
|
||||
$stdout
|
||||
end
|
||||
|
||||
private :env_table, :stdinput, :stdoutput
|
||||
|
||||
|
||||
# Create an HTTP header block as a string.
|
||||
#
|
||||
# Includes the empty line that ends the header block.
|
||||
#
|
||||
# +options+ can be a string specifying the Content-Type (defaults
|
||||
# to text/html), or a hash of header key/value pairs. The following
|
||||
# header keys are recognized:
|
||||
#
|
||||
# type:: the Content-Type header. Defaults to "text/html"
|
||||
# charset:: the charset of the body, appended to the Content-Type header.
|
||||
# nph:: a boolean value. If true, prepend protocol string and status code, and
|
||||
# date; and sets default values for "server" and "connection" if not
|
||||
# explicitly set.
|
||||
# status:: the HTTP status code, returned as the Status header. See the
|
||||
# list of available status codes below.
|
||||
# server:: the server software, returned as the Server header.
|
||||
# connection:: the connection type, returned as the Connection header (for
|
||||
# instance, "close".
|
||||
# length:: the length of the content that will be sent, returned as the
|
||||
# Content-Length header.
|
||||
# language:: the language of the content, returned as the Content-Language
|
||||
# header.
|
||||
# expires:: the time on which the current content expires, as a +Time+
|
||||
# object, returned as the Expires header.
|
||||
# cookie:: a cookie or cookies, returned as one or more Set-Cookie headers.
|
||||
# The value can be the literal string of the cookie; a CGI::Cookie
|
||||
# object; an Array of literal cookie strings or Cookie objects; or a
|
||||
# hash all of whose values are literal cookie strings or Cookie objects.
|
||||
# These cookies are in addition to the cookies held in the
|
||||
# @output_cookies field.
|
||||
#
|
||||
# Other header lines can also be set; they are appended as key: value.
|
||||
#
|
||||
# header
|
||||
# # Content-Type: text/html
|
||||
#
|
||||
# header("text/plain")
|
||||
# # Content-Type: text/plain
|
||||
#
|
||||
# header("nph" => true,
|
||||
# "status" => "OK", # == "200 OK"
|
||||
# # "status" => "200 GOOD",
|
||||
# "server" => ENV['SERVER_SOFTWARE'],
|
||||
# "connection" => "close",
|
||||
# "type" => "text/html",
|
||||
# "charset" => "iso-2022-jp",
|
||||
# # Content-Type: text/html; charset=iso-2022-jp
|
||||
# "length" => 103,
|
||||
# "language" => "ja",
|
||||
# "expires" => Time.now + 30,
|
||||
# "cookie" => [cookie1, cookie2],
|
||||
# "my_header1" => "my_value"
|
||||
# "my_header2" => "my_value")
|
||||
#
|
||||
# The status codes are:
|
||||
#
|
||||
# "OK" --> "200 OK"
|
||||
# "PARTIAL_CONTENT" --> "206 Partial Content"
|
||||
# "MULTIPLE_CHOICES" --> "300 Multiple Choices"
|
||||
# "MOVED" --> "301 Moved Permanently"
|
||||
# "REDIRECT" --> "302 Found"
|
||||
# "NOT_MODIFIED" --> "304 Not Modified"
|
||||
# "BAD_REQUEST" --> "400 Bad Request"
|
||||
# "AUTH_REQUIRED" --> "401 Authorization Required"
|
||||
# "FORBIDDEN" --> "403 Forbidden"
|
||||
# "NOT_FOUND" --> "404 Not Found"
|
||||
# "METHOD_NOT_ALLOWED" --> "405 Method Not Allowed"
|
||||
# "NOT_ACCEPTABLE" --> "406 Not Acceptable"
|
||||
# "LENGTH_REQUIRED" --> "411 Length Required"
|
||||
# "PRECONDITION_FAILED" --> "412 Precondition Failed"
|
||||
# "SERVER_ERROR" --> "500 Internal Server Error"
|
||||
# "NOT_IMPLEMENTED" --> "501 Method Not Implemented"
|
||||
# "BAD_GATEWAY" --> "502 Bad Gateway"
|
||||
# "VARIANT_ALSO_VARIES" --> "506 Variant Also Negotiates"
|
||||
#
|
||||
# This method does not perform charset conversion.
|
||||
#
|
||||
def header(options = "text/html")
|
||||
|
||||
buf = ""
|
||||
|
||||
case options
|
||||
when String
|
||||
options = { "type" => options }
|
||||
when Hash
|
||||
options = options.dup
|
||||
end
|
||||
|
||||
unless options.has_key?("type")
|
||||
options["type"] = "text/html"
|
||||
end
|
||||
|
||||
if options.has_key?("charset")
|
||||
options["type"] += "; charset=" + options.delete("charset")
|
||||
end
|
||||
|
||||
options.delete("nph") if defined?(MOD_RUBY)
|
||||
if options.delete("nph") or
|
||||
(/IIS\/(\d+)/.match(env_table['SERVER_SOFTWARE']) and $1.to_i < 5)
|
||||
buf += (env_table["SERVER_PROTOCOL"] or "HTTP/1.0") + " " +
|
||||
(HTTP_STATUS[options["status"]] or options["status"] or "200 OK") +
|
||||
EOL +
|
||||
"Date: " + CGI::rfc1123_date(Time.now) + EOL
|
||||
|
||||
unless options.has_key?("server")
|
||||
options["server"] = (env_table['SERVER_SOFTWARE'] or "")
|
||||
end
|
||||
|
||||
unless options.has_key?("connection")
|
||||
options["connection"] = "close"
|
||||
end
|
||||
|
||||
options.delete("status")
|
||||
end
|
||||
|
||||
if options.has_key?("status")
|
||||
buf += "Status: " +
|
||||
(HTTP_STATUS[options["status"]] or options["status"]) + EOL
|
||||
options.delete("status")
|
||||
end
|
||||
|
||||
if options.has_key?("server")
|
||||
buf += "Server: " + options.delete("server") + EOL
|
||||
end
|
||||
|
||||
if options.has_key?("connection")
|
||||
buf += "Connection: " + options.delete("connection") + EOL
|
||||
end
|
||||
|
||||
buf += "Content-Type: " + options.delete("type") + EOL
|
||||
|
||||
if options.has_key?("length")
|
||||
buf += "Content-Length: " + options.delete("length").to_s + EOL
|
||||
end
|
||||
|
||||
if options.has_key?("language")
|
||||
buf += "Content-Language: " + options.delete("language") + EOL
|
||||
end
|
||||
|
||||
if options.has_key?("expires")
|
||||
buf += "Expires: " + CGI::rfc1123_date( options.delete("expires") ) + EOL
|
||||
end
|
||||
|
||||
if options.has_key?("cookie")
|
||||
if options["cookie"].kind_of?(String) or
|
||||
options["cookie"].kind_of?(Cookie)
|
||||
buf += "Set-Cookie: " + options.delete("cookie").to_s + EOL
|
||||
elsif options["cookie"].kind_of?(Array)
|
||||
options.delete("cookie").each{|cookie|
|
||||
buf += "Set-Cookie: " + cookie.to_s + EOL
|
||||
}
|
||||
elsif options["cookie"].kind_of?(Hash)
|
||||
options.delete("cookie").each_value{|cookie|
|
||||
buf += "Set-Cookie: " + cookie.to_s + EOL
|
||||
}
|
||||
end
|
||||
end
|
||||
if @output_cookies
|
||||
for cookie in @output_cookies
|
||||
buf += "Set-Cookie: " + cookie.to_s + EOL
|
||||
end
|
||||
end
|
||||
|
||||
options.each{|key, value|
|
||||
buf += key + ": " + value.to_s + EOL
|
||||
}
|
||||
|
||||
if defined?(MOD_RUBY)
|
||||
table = Apache::request.headers_out
|
||||
buf.scan(/([^:]+): (.+)#{EOL}/){ |name, value|
|
||||
warn sprintf("name:%s value:%s\n", name, value) if $DEBUG
|
||||
case name
|
||||
when 'Set-Cookie'
|
||||
table.add(name, value)
|
||||
when /^status$/i
|
||||
Apache::request.status_line = value
|
||||
Apache::request.status = value.to_i
|
||||
when /^content-type$/i
|
||||
Apache::request.content_type = value
|
||||
when /^content-encoding$/i
|
||||
Apache::request.content_encoding = value
|
||||
when /^location$/i
|
||||
if Apache::request.status == 200
|
||||
Apache::request.status = 302
|
||||
end
|
||||
Apache::request.headers_out[name] = value
|
||||
else
|
||||
Apache::request.headers_out[name] = value
|
||||
end
|
||||
}
|
||||
Apache::request.send_http_header
|
||||
''
|
||||
else
|
||||
buf + EOL
|
||||
end
|
||||
|
||||
end # header()
|
||||
|
||||
|
||||
# Print an HTTP header and body to $DEFAULT_OUTPUT ($>)
|
||||
#
|
||||
# The header is provided by +options+, as for #header().
|
||||
# The body of the document is that returned by the passed-
|
||||
# in block. This block takes no arguments. It is required.
|
||||
#
|
||||
# cgi = CGI.new
|
||||
# cgi.out{ "string" }
|
||||
# # Content-Type: text/html
|
||||
# # Content-Length: 6
|
||||
# #
|
||||
# # string
|
||||
#
|
||||
# cgi.out("text/plain") { "string" }
|
||||
# # Content-Type: text/plain
|
||||
# # Content-Length: 6
|
||||
# #
|
||||
# # string
|
||||
#
|
||||
# cgi.out("nph" => true,
|
||||
# "status" => "OK", # == "200 OK"
|
||||
# "server" => ENV['SERVER_SOFTWARE'],
|
||||
# "connection" => "close",
|
||||
# "type" => "text/html",
|
||||
# "charset" => "iso-2022-jp",
|
||||
# # Content-Type: text/html; charset=iso-2022-jp
|
||||
# "language" => "ja",
|
||||
# "expires" => Time.now + (3600 * 24 * 30),
|
||||
# "cookie" => [cookie1, cookie2],
|
||||
# "my_header1" => "my_value",
|
||||
# "my_header2" => "my_value") { "string" }
|
||||
#
|
||||
# Content-Length is automatically calculated from the size of
|
||||
# the String returned by the content block.
|
||||
#
|
||||
# If ENV['REQUEST_METHOD'] == "HEAD", then only the header
|
||||
# is outputted (the content block is still required, but it
|
||||
# is ignored).
|
||||
#
|
||||
# If the charset is "iso-2022-jp" or "euc-jp" or "shift_jis" then
|
||||
# the content is converted to this charset, and the language is set
|
||||
# to "ja".
|
||||
def out(options = "text/html") # :yield:
|
||||
|
||||
options = { "type" => options } if options.kind_of?(String)
|
||||
content = yield
|
||||
options["length"] = content.bytesize.to_s
|
||||
output = stdoutput
|
||||
output.binmode if defined? output.binmode
|
||||
output.print header(options)
|
||||
output.print content unless "HEAD" == env_table['REQUEST_METHOD']
|
||||
end
|
||||
|
||||
|
||||
# Print an argument or list of arguments to the default output stream
|
||||
#
|
||||
# cgi = CGI.new
|
||||
# cgi.print # default: cgi.print == $DEFAULT_OUTPUT.print
|
||||
def print(*options)
|
||||
stdoutput.print(*options)
|
||||
end
|
||||
|
||||
# Parse an HTTP query string into a hash of key=>value pairs.
|
||||
#
|
||||
# params = CGI::parse("query_string")
|
||||
# # {"name1" => ["value1", "value2", ...],
|
||||
# # "name2" => ["value1", "value2", ...], ... }
|
||||
#
|
||||
def CGI::parse(query)
|
||||
params = Hash.new([].freeze)
|
||||
|
||||
query.split(/[&;]/).each do |pairs|
|
||||
key, value = pairs.split('=',2).collect{|v| CGI::unescape(v) }
|
||||
if params.has_key?(key)
|
||||
params[key].push(value)
|
||||
else
|
||||
params[key] = [value]
|
||||
end
|
||||
end
|
||||
|
||||
params
|
||||
end
|
||||
|
||||
# Mixin module. It provides the follow functionality groups:
|
||||
#
|
||||
# 1. Access to CGI environment variables as methods. See
|
||||
# documentation to the CGI class for a list of these variables.
|
||||
#
|
||||
# 2. Access to cookies, including the cookies attribute.
|
||||
#
|
||||
# 3. Access to parameters, including the params attribute, and overloading
|
||||
# [] to perform parameter value lookup by key.
|
||||
#
|
||||
# 4. The initialize_query method, for initialising the above
|
||||
# mechanisms, handling multipart forms, and allowing the
|
||||
# class to be used in "offline" mode.
|
||||
#
|
||||
module QueryExtension
|
||||
|
||||
%w[ CONTENT_LENGTH SERVER_PORT ].each do |env|
|
||||
define_method(env.sub(/^HTTP_/, '').downcase) do
|
||||
(val = env_table[env]) && Integer(val)
|
||||
end
|
||||
end
|
||||
|
||||
%w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO
|
||||
PATH_TRANSLATED QUERY_STRING REMOTE_ADDR REMOTE_HOST
|
||||
REMOTE_IDENT REMOTE_USER REQUEST_METHOD SCRIPT_NAME
|
||||
SERVER_NAME SERVER_PROTOCOL SERVER_SOFTWARE
|
||||
|
||||
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
|
||||
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST
|
||||
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
|
||||
define_method(env.sub(/^HTTP_/, '').downcase) do
|
||||
env_table[env]
|
||||
end
|
||||
end
|
||||
|
||||
# Get the raw cookies as a string.
|
||||
def raw_cookie
|
||||
env_table["HTTP_COOKIE"]
|
||||
end
|
||||
|
||||
# Get the raw RFC2965 cookies as a string.
|
||||
def raw_cookie2
|
||||
env_table["HTTP_COOKIE2"]
|
||||
end
|
||||
|
||||
# Get the cookies as a hash of cookie-name=>Cookie pairs.
|
||||
attr_accessor :cookies
|
||||
|
||||
# Get the parameters as a hash of name=>values pairs, where
|
||||
# values is an Array.
|
||||
attr_reader :params
|
||||
|
||||
# Set all the parameters.
|
||||
def params=(hash)
|
||||
@params.clear
|
||||
@params.update(hash)
|
||||
end
|
||||
|
||||
def read_multipart(boundary, content_length)
|
||||
params = Hash.new([])
|
||||
boundary = "--" + boundary
|
||||
quoted_boundary = Regexp.quote(boundary)
|
||||
buf = ""
|
||||
bufsize = 10 * 1024
|
||||
boundary_end=""
|
||||
|
||||
# start multipart/form-data
|
||||
stdinput.binmode if defined? stdinput.binmode
|
||||
boundary_size = boundary.bytesize + EOL.bytesize
|
||||
content_length -= boundary_size
|
||||
status = stdinput.read(boundary_size)
|
||||
if nil == status
|
||||
raise EOFError, "no content body"
|
||||
elsif boundary + EOL != status
|
||||
raise EOFError, "bad content body"
|
||||
end
|
||||
|
||||
loop do
|
||||
head = nil
|
||||
body = MorphingBody.new
|
||||
|
||||
until head and /#{quoted_boundary}(?:#{EOL}|--)/.match(buf)
|
||||
if (not head) and /#{EOL}#{EOL}/.match(buf)
|
||||
buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/) do
|
||||
head = $1.dup
|
||||
""
|
||||
end
|
||||
next
|
||||
end
|
||||
|
||||
if head and ( (EOL + boundary + EOL).bytesize < buf.bytesize )
|
||||
body.print buf[0 ... (buf.bytesize - (EOL + boundary + EOL).bytesize)]
|
||||
buf[0 ... (buf.bytesize - (EOL + boundary + EOL).bytesize)] = ""
|
||||
end
|
||||
|
||||
c = if bufsize < content_length
|
||||
stdinput.read(bufsize)
|
||||
else
|
||||
stdinput.read(content_length)
|
||||
end
|
||||
if c.nil? || c.empty?
|
||||
raise EOFError, "bad content body"
|
||||
end
|
||||
buf.concat(c)
|
||||
content_length -= c.bytesize
|
||||
end
|
||||
|
||||
buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/) do
|
||||
body.print $1
|
||||
if "--" == $2
|
||||
content_length = -1
|
||||
end
|
||||
boundary_end = $2.dup
|
||||
""
|
||||
end
|
||||
|
||||
body.rewind
|
||||
|
||||
/Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;\s]*))/i.match(head)
|
||||
filename = ($1 or $2 or "")
|
||||
if /Mac/i.match(env_table['HTTP_USER_AGENT']) and
|
||||
/Mozilla/i.match(env_table['HTTP_USER_AGENT']) and
|
||||
(not /MSIE/i.match(env_table['HTTP_USER_AGENT']))
|
||||
filename = CGI::unescape(filename)
|
||||
end
|
||||
|
||||
/Content-Type: ([^\s]*)/i.match(head)
|
||||
content_type = ($1 or "")
|
||||
|
||||
(class << body; self; end).class_eval do
|
||||
alias local_path path
|
||||
define_method(:original_filename) {filename.dup.taint}
|
||||
define_method(:content_type) {content_type.dup.taint}
|
||||
end
|
||||
|
||||
/Content-Disposition:.* name="?([^\";\s]*)"?/i.match(head)
|
||||
name = ($1 || "").dup
|
||||
|
||||
if params.has_key?(name)
|
||||
params[name].push(body)
|
||||
else
|
||||
params[name] = [body]
|
||||
end
|
||||
break if buf.bytesize == 0
|
||||
break if content_length == -1
|
||||
end
|
||||
raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
|
||||
|
||||
params
|
||||
end # read_multipart
|
||||
private :read_multipart
|
||||
|
||||
# offline mode. read name=value pairs on standard input.
|
||||
def read_from_cmdline
|
||||
require "shellwords"
|
||||
|
||||
string = unless ARGV.empty?
|
||||
ARGV.join(' ')
|
||||
else
|
||||
if STDIN.tty?
|
||||
STDERR.print(
|
||||
%|(offline mode: enter name=value pairs on standard input)\n|
|
||||
)
|
||||
end
|
||||
readlines.join(' ').gsub(/\n/, '')
|
||||
end.gsub(/\\=/, '%3D').gsub(/\\&/, '%26')
|
||||
|
||||
words = Shellwords.shellwords(string)
|
||||
|
||||
if words.find{|x| /=/.match(x) }
|
||||
words.join('&')
|
||||
else
|
||||
words.join('+')
|
||||
end
|
||||
end
|
||||
private :read_from_cmdline
|
||||
|
||||
# A wrapper class to use a StringIO object as the body and switch
|
||||
# to a TempFile when the passed threshold is passed.
|
||||
class MorphingBody
|
||||
begin
|
||||
require "stringio"
|
||||
@@small_buffer = lambda{StringIO.new}
|
||||
rescue LoadError
|
||||
require "tempfile"
|
||||
@@small_buffer = lambda{
|
||||
n = Tempfile.new("CGI")
|
||||
n.binmode
|
||||
n
|
||||
}
|
||||
end
|
||||
|
||||
def initialize(morph_threshold = 10240)
|
||||
@threshold = morph_threshold
|
||||
@body = @@small_buffer.call
|
||||
@cur_size = 0
|
||||
@morph_check = true
|
||||
end
|
||||
|
||||
def print(data)
|
||||
if @morph_check && (@cur_size + data.bytesize > @threshold)
|
||||
convert_body
|
||||
end
|
||||
@body.print data
|
||||
end
|
||||
def rewind
|
||||
@body.rewind
|
||||
end
|
||||
def path
|
||||
@body.path
|
||||
end
|
||||
|
||||
# returns the true body object.
|
||||
def extract
|
||||
@body
|
||||
end
|
||||
|
||||
private
|
||||
def convert_body
|
||||
new_body = TempFile.new("CGI")
|
||||
new_body.binmode if defined? @body.binmode
|
||||
new_body.binmode if defined? new_body.binmode
|
||||
|
||||
@body.rewind
|
||||
new_body.print @body.read
|
||||
@body = new_body
|
||||
@morph_check = false
|
||||
end
|
||||
end
|
||||
|
||||
# Initialize the data from the query.
|
||||
#
|
||||
# Handles multipart forms (in particular, forms that involve file uploads).
|
||||
# Reads query parameters in the @params field, and cookies into @cookies.
|
||||
def initialize_query()
|
||||
if ("POST" == env_table['REQUEST_METHOD']) and
|
||||
%r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|.match(env_table['CONTENT_TYPE'])
|
||||
boundary = $1.dup
|
||||
@multipart = true
|
||||
@params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH']))
|
||||
else
|
||||
@multipart = false
|
||||
@params = CGI::parse(
|
||||
case env_table['REQUEST_METHOD']
|
||||
when "GET", "HEAD"
|
||||
if defined?(MOD_RUBY)
|
||||
Apache::request.args or ""
|
||||
else
|
||||
env_table['QUERY_STRING'] or ""
|
||||
end
|
||||
when "POST"
|
||||
stdinput.binmode if defined? stdinput.binmode
|
||||
stdinput.read(Integer(env_table['CONTENT_LENGTH'])) or ''
|
||||
else
|
||||
read_from_cmdline
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
@cookies = CGI::Cookie::parse((env_table['HTTP_COOKIE'] or env_table['COOKIE']))
|
||||
end
|
||||
private :initialize_query
|
||||
|
||||
def multipart?
|
||||
@multipart
|
||||
end
|
||||
|
||||
# Get the value for the parameter with a given key.
|
||||
#
|
||||
# If the parameter has multiple values, only the first will be
|
||||
# retrieved; use #params() to get the array of values.
|
||||
def [](key)
|
||||
params = @params[key]
|
||||
return '' unless params
|
||||
value = params[0]
|
||||
if @multipart
|
||||
if value
|
||||
return value
|
||||
elsif defined? StringIO
|
||||
StringIO.new("")
|
||||
else
|
||||
Tempfile.new("CGI")
|
||||
end
|
||||
else
|
||||
str = if value then value.dup else "" end
|
||||
str
|
||||
end
|
||||
end
|
||||
|
||||
# Return all parameter keys as an array.
|
||||
def keys(*args)
|
||||
@params.keys(*args)
|
||||
end
|
||||
|
||||
# Returns true if a given parameter key exists in the query.
|
||||
def has_key?(*args)
|
||||
@params.has_key?(*args)
|
||||
end
|
||||
alias key? has_key?
|
||||
alias include? has_key?
|
||||
|
||||
end # QueryExtension
|
||||
|
||||
|
||||
# Creates a new CGI instance.
|
||||
#
|
||||
# +type+ specifies which version of HTML to load the HTML generation
|
||||
# methods for. The following versions of HTML are supported:
|
||||
#
|
||||
# html3:: HTML 3.x
|
||||
# html4:: HTML 4.0
|
||||
# html4Tr:: HTML 4.0 Transitional
|
||||
# html4Fr:: HTML 4.0 with Framesets
|
||||
#
|
||||
# If not specified, no HTML generation methods will be loaded.
|
||||
#
|
||||
# If the CGI object is not created in a standard CGI call environment
|
||||
# (that is, it can't locate REQUEST_METHOD in its environment), then
|
||||
# it will run in "offline" mode. In this mode, it reads its parameters
|
||||
# from the command line or (failing that) from standard input. Otherwise,
|
||||
# cookies and other parameters are parsed automatically from the standard
|
||||
# CGI locations, which varies according to the REQUEST_METHOD.
|
||||
def initialize(type = "query")
|
||||
if defined?(MOD_RUBY) && !ENV.key?("GATEWAY_INTERFACE")
|
||||
Apache.request.setup_cgi_env
|
||||
end
|
||||
|
||||
extend QueryExtension
|
||||
@multipart = false
|
||||
|
||||
initialize_query() # set @params, @cookies
|
||||
@output_cookies = nil
|
||||
@output_hidden = nil
|
||||
|
||||
case type
|
||||
when "html3"
|
||||
require 'cgi/html'
|
||||
extend Html3
|
||||
element_init()
|
||||
extend HtmlExtension
|
||||
when "html4"
|
||||
require 'cgi/html'
|
||||
extend Html4
|
||||
element_init()
|
||||
extend HtmlExtension
|
||||
when "html4Tr"
|
||||
require 'cgi/html'
|
||||
extend Html4Tr
|
||||
element_init()
|
||||
extend HtmlExtension
|
||||
when "html4Fr"
|
||||
require 'cgi/html'
|
||||
extend Html4Tr
|
||||
element_init()
|
||||
extend Html4Fr
|
||||
element_init()
|
||||
extend HtmlExtension
|
||||
end
|
||||
end
|
||||
|
||||
end # class CGI
|
||||
|
||||
|
1021
lib/cgi/html.rb
Normal file
1021
lib/cgi/html.rb
Normal file
File diff suppressed because it is too large
Load diff
181
lib/cgi/util.rb
Normal file
181
lib/cgi/util.rb
Normal file
|
@ -0,0 +1,181 @@
|
|||
class CGI
|
||||
# URL-encode a string.
|
||||
# url_encoded_string = CGI::escape("'Stop!' said Fred")
|
||||
# # => "%27Stop%21%27+said+Fred"
|
||||
def CGI::escape(string)
|
||||
string.gsub(/([^ a-zA-Z0-9_.-]+)/) do
|
||||
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
||||
end.tr(' ', '+')
|
||||
end
|
||||
|
||||
|
||||
# URL-decode a string.
|
||||
# string = CGI::unescape("%27Stop%21%27+said+Fred")
|
||||
# # => "'Stop!' said Fred"
|
||||
def CGI::unescape(string)
|
||||
enc = string.encoding
|
||||
string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/) do
|
||||
[$1.delete('%')].pack('H*').force_encoding(enc)
|
||||
end
|
||||
end
|
||||
|
||||
TABLE_FOR_ESCAPE_HTML__ = {
|
||||
'&' => '&',
|
||||
'"' => '"',
|
||||
'<' => '<',
|
||||
'>' => '>',
|
||||
}
|
||||
|
||||
# Escape special characters in HTML, namely &\"<>
|
||||
# CGI::escapeHTML('Usage: foo "bar" <baz>')
|
||||
# # => "Usage: foo "bar" <baz>"
|
||||
def CGI::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 CGI::unescapeHTML(string)
|
||||
enc = string.encoding
|
||||
if [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE].include?(enc)
|
||||
return string.gsub(Regexp.new('&(amp|quot|gt|lt|#[0-9]+|#x[0-9A-Fa-f]+);'.encode(enc))) do
|
||||
case $1.encode("US-ASCII")
|
||||
when 'amp' then '&'.encode(enc)
|
||||
when 'quot' then '"'.encode(enc)
|
||||
when 'gt' then '>'.encode(enc)
|
||||
when 'lt' then '<'.encode(enc)
|
||||
when /\A#0*(\d+)\z/ then $1.to_i.chr(enc)
|
||||
when /\A#x([0-9a-f]+)\z/i then $1.hex.chr(enc)
|
||||
end
|
||||
end
|
||||
end
|
||||
asciicompat = Encoding.compatible?(string, "a")
|
||||
string.gsub(/&(amp|quot|gt|lt|\#[0-9]+|\#x[0-9A-Fa-f]+);/) do
|
||||
match = $1.dup
|
||||
case match
|
||||
when 'amp' then '&'
|
||||
when 'quot' then '"'
|
||||
when 'gt' then '>'
|
||||
when 'lt' then '<'
|
||||
when /\A#0*(\d+)\z/
|
||||
n = $1.to_i
|
||||
if enc == Encoding::UTF_8 or
|
||||
enc == Encoding::ISO_8859_1 && n < 256 or
|
||||
asciicompat && n < 128
|
||||
n.chr(enc)
|
||||
else
|
||||
"&##{$1};"
|
||||
end
|
||||
when /\A#x([0-9a-f]+)\z/i
|
||||
n = $1.hex
|
||||
if enc == Encoding::UTF_8 or
|
||||
enc == Encoding::ISO_8859_1 && n < 256 or
|
||||
asciicompat && n < 128
|
||||
n.chr(enc)
|
||||
else
|
||||
"&#x#{$1};"
|
||||
end
|
||||
else
|
||||
"&#{match};"
|
||||
end
|
||||
end
|
||||
end
|
||||
def CGI::escape_html(str)
|
||||
escapeHTML(str)
|
||||
end
|
||||
def CGI::unescape_html(str)
|
||||
unescapeHTML(str)
|
||||
end
|
||||
|
||||
# Escape only the tags of certain HTML elements in +string+.
|
||||
#
|
||||
# Takes an element or elements or array of elements. Each element
|
||||
# is specified by the name of the element, without angle brackets.
|
||||
# This matches both the start and the end tag of that element.
|
||||
# The attribute list of the open tag will also be escaped (for
|
||||
# instance, the double-quotes surrounding attribute values).
|
||||
#
|
||||
# print CGI::escapeElement('<BR><A HREF="url"></A>', "A", "IMG")
|
||||
# # "<BR><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)*?>/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 CGI::unescapeElement(string, *elements)
|
||||
elements = elements[0] if elements[0].kind_of?(Array)
|
||||
unless elements.empty?
|
||||
string.gsub(/<\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?>/i) do
|
||||
CGI::unescapeHTML($&)
|
||||
end
|
||||
else
|
||||
string
|
||||
end
|
||||
end
|
||||
def CGI::escape_element(str)
|
||||
escapeElement(str)
|
||||
end
|
||||
def CGI::unescape_element(str)
|
||||
unescapeElement(str)
|
||||
end
|
||||
|
||||
# Format a +Time+ object as a String using the format specified by RFC 1123.
|
||||
#
|
||||
# CGI::rfc1123_date(Time.now)
|
||||
# # Sat, 01 Jan 2000 00:00:00 GMT
|
||||
def CGI::rfc1123_date(time)
|
||||
t = time.clone.gmtime
|
||||
return format("%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
|
||||
RFC822_DAYS[t.wday], t.day, RFC822_MONTHS[t.month-1], t.year,
|
||||
t.hour, t.min, t.sec)
|
||||
end
|
||||
|
||||
# Prettify (indent) an HTML string.
|
||||
#
|
||||
# +string+ is the HTML string to indent. +shift+ is the indentation
|
||||
# unit to use; it defaults to two spaces.
|
||||
#
|
||||
# print CGI::pretty("<HTML><BODY></BODY></HTML>")
|
||||
# # <HTML>
|
||||
# # <BODY>
|
||||
# # </BODY>
|
||||
# # </HTML>
|
||||
#
|
||||
# print CGI::pretty("<HTML><BODY></BODY></HTML>", "\t")
|
||||
# # <HTML>
|
||||
# # <BODY>
|
||||
# # </BODY>
|
||||
# # </HTML>
|
||||
#
|
||||
def CGI::pretty(string, shift = " ")
|
||||
lines = string.gsub(/(?!\A)<(?:.|\n)*?>/, "\n\\0").gsub(/<(?:.|\n)*?>(?!\n)/, "\\0\n")
|
||||
end_pos = 0
|
||||
while end_pos = lines.index(/^<\/(\w+)/, end_pos)
|
||||
element = $1.dup
|
||||
start_pos = lines.rindex(/^\s*<#{element}/i, end_pos)
|
||||
lines[start_pos ... end_pos] = "__" + lines[start_pos ... end_pos].gsub(/\n(?!\z)/, "\n" + shift) + "__"
|
||||
end
|
||||
lines.gsub(/^((?:#{Regexp::quote(shift)})*)__(?=<\/?\w)/, '\1')
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue