summaryrefslogtreecommitdiff
path: root/lib/cgi
diff options
context:
space:
mode:
Diffstat (limited to 'lib/cgi')
-rw-r--r--lib/cgi/cgi.gemspec42
-rw-r--r--lib/cgi/cookie.rb174
-rw-r--r--lib/cgi/core.rb219
-rw-r--r--lib/cgi/html.rb303
-rw-r--r--lib/cgi/session.rb107
-rw-r--r--lib/cgi/session/pstore.rb33
-rw-r--r--lib/cgi/util.rb202
7 files changed, 622 insertions, 458 deletions
diff --git a/lib/cgi/cgi.gemspec b/lib/cgi/cgi.gemspec
new file mode 100644
index 0000000000..381c55a5ca
--- /dev/null
+++ b/lib/cgi/cgi.gemspec
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+name = File.basename(__FILE__, ".gemspec")
+version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir|
+ break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
+ /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
+ end rescue nil
+end
+
+Gem::Specification.new do |spec|
+ spec.name = name
+ spec.version = version
+ spec.authors = ["Yukihiro Matsumoto"]
+ spec.email = ["matz@ruby-lang.org"]
+
+ spec.summary = %q{Support for the Common Gateway Interface protocol.}
+ spec.description = %q{Support for the Common Gateway Interface protocol.}
+ spec.homepage = "https://github.com/ruby/cgi"
+ spec.licenses = ["Ruby", "BSD-2-Clause"]
+ spec.required_ruby_version = ">= 2.5.0"
+
+ spec.metadata["homepage_uri"] = spec.homepage
+ spec.metadata["source_code_uri"] = spec.homepage
+
+ spec.executables = []
+
+ spec.files = [
+ "LICENSE.txt",
+ "README.md",
+ *Dir["lib{.rb,/**/*.rb}", "bin/*"] ]
+
+ spec.require_paths = ["lib"]
+
+ if Gem::Platform === spec.platform and spec.platform =~ 'java' or RUBY_ENGINE == 'jruby'
+ spec.platform = 'java'
+ spec.require_paths << "ext/java/org/jruby/ext/cgi/escape/lib"
+ spec.files += Dir["ext/java/**/*.{rb}", "lib/cgi/escape.jar"]
+ else
+ spec.files += Dir["ext/cgi/**/*.{rb,c,h,sh}", "ext/cgi/escape/depend", "lib/cgi/escape.so"]
+ spec.extensions = ["ext/cgi/escape/extconf.rb"]
+ end
+end
diff --git a/lib/cgi/cookie.rb b/lib/cgi/cookie.rb
index c2526931d5..1c4ef6a600 100644
--- a/lib/cgi/cookie.rb
+++ b/lib/cgi/cookie.rb
@@ -1,5 +1,6 @@
+# frozen_string_literal: true
+require_relative 'util'
class CGI
- @@accept_charset="UTF-8" unless defined?(@@accept_charset)
# Class representing an HTTP cookie.
#
# In addition to its specific fields and methods, a Cookie instance
@@ -8,32 +9,40 @@ class CGI
# 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
+ # 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
+ # 'httponly' => 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
+ # name = cookie1.name
+ # values = cookie1.value
+ # path = cookie1.path
+ # domain = cookie1.domain
+ # expires = cookie1.expires
+ # secure = cookie1.secure
+ # httponly = cookie1.httponly
#
- # cookie1.name = 'name'
- # cookie1.value = ['value1', 'value2', ...]
- # cookie1.path = 'path'
- # cookie1.domain = 'domain'
- # cookie1.expires = Time.now + 30
- # cookie1.secure = true
+ # cookie1.name = 'name'
+ # cookie1.value = ['value1', 'value2', ...]
+ # cookie1.path = 'path'
+ # cookie1.domain = 'domain'
+ # cookie1.expires = Time.now + 30
+ # cookie1.secure = true
+ # cookie1.httponly = true
class Cookie < Array
+ @@accept_charset="UTF-8" unless defined?(@@accept_charset)
+
+ TOKEN_RE = %r"\A[[!-~]&&[^()<>@,;:\\\"/?=\[\]{}]]+\z"
+ PATH_VALUE_RE = %r"\A[[ -~]&&[^;]]*\z"
+ DOMAIN_VALUE_RE = %r"\A\.?(?<label>(?!-)[-A-Za-z0-9]+(?<!-))(?:\.\g<label>)*\z"
# Create a new CGI::Cookie object.
#
@@ -52,23 +61,25 @@ class CGI
#
# 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
+ # path:: the path for which this cookie applies. Defaults to
# 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.
+ # httponly:: whether this cookie is a HttpOnly cookie or not (default to
+ # false). HttpOnly cookies are not available to javascript.
#
# 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 "")
+ self.name = name
+ self.path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
@secure = false
+ @httponly = false
return super(value)
end
@@ -77,32 +88,54 @@ class CGI
raise ArgumentError, "`name' required"
end
- @name = options["name"]
+ self.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"]
+ self.path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
+ self.domain = options["domain"]
@expires = options["expires"]
- @secure = options["secure"] == true ? true : false
+ @secure = options["secure"] == true
+ @httponly = options["httponly"] == true
super(value)
end
# Name of this cookie, as a +String+
- attr_accessor :name
+ attr_reader :name
+ # Set name of this cookie
+ def name=(str)
+ if str and !TOKEN_RE.match?(str)
+ raise ArgumentError, "invalid name: #{str.dump}"
+ end
+ @name = str
+ end
+
# Path for which this cookie applies, as a +String+
- attr_accessor :path
+ attr_reader :path
+ # Set path for which this cookie applies
+ def path=(str)
+ if str and !PATH_VALUE_RE.match?(str)
+ raise ArgumentError, "invalid path: #{str.dump}"
+ end
+ @path = str
+ end
+
# Domain for which this cookie applies, as a +String+
- attr_accessor :domain
+ attr_reader :domain
+ # Set domain for which this cookie applies
+ def domain=(str)
+ if str and ((str = str.b).bytesize > 255 or !DOMAIN_VALUE_RE.match?(str))
+ raise ArgumentError, "invalid domain: #{str.dump}"
+ end
+ @domain = str
+ end
+
# Time at which this cookie expires, as a +Time+
attr_accessor :expires
# True if this cookie is secure; false otherwise
- attr_reader("secure")
+ attr_reader :secure
+ # True if this cookie is httponly; false otherwise
+ attr_reader :httponly
# Returns the value or list of values for this cookie.
def value
@@ -122,43 +155,56 @@ class CGI
@secure
end
+ # Set whether the Cookie is a httponly cookie or not.
+ #
+ # +val+ must be a boolean.
+ def httponly=(val)
+ @httponly = !!val
+ end
+
# Convert the Cookie to its string representation.
def to_s
- val = collect{|v| CGI::escape(v) }.join("&")
- buf = "#{@name}=#{val}"
+ val = collect{|v| CGI.escape(v) }.join("&")
+ buf = "#{@name}=#{val}".dup
buf << "; domain=#{@domain}" if @domain
buf << "; path=#{@path}" if @path
- buf << "; expires=#{CGI::rfc1123_date(@expires)}" if @expires
- buf << "; secure" if @secure == true
+ buf << "; expires=#{CGI.rfc1123_date(@expires)}" if @expires
+ buf << "; secure" if @secure
+ buf << "; HttpOnly" if @httponly
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 self.parse(raw_cookie)
+ cookies = Hash.new([])
+ return cookies unless raw_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,@@accept_charset) }
- if cookies.has_key?(name)
- values = cookies[name].value + values
+ raw_cookie.split(/;\s?/).each do |pairs|
+ name, values = pairs.split('=',2)
+ next unless name and values
+ values ||= ""
+ values = values.split('&').collect{|v| CGI.unescape(v,@@accept_charset) }
+ if cookies.has_key?(name)
+ cookies[name].concat(values)
+ else
+ cookies[name] = Cookie.new(name, *values)
+ end
end
- cookies[name] = Cookie::new(name, *values)
+
+ cookies
end
- cookies
- end
+ # A summary of cookie string.
+ def inspect
+ "#<CGI::Cookie: #{self.to_s.inspect}>"
+ end
+
+ end # class Cookie
end
diff --git a/lib/cgi/core.rb b/lib/cgi/core.rb
index f1e8d3467a..62e606837a 100644
--- a/lib/cgi/core.rb
+++ b/lib/cgi/core.rb
@@ -1,8 +1,16 @@
+# frozen_string_literal: true
#--
# Methods for generating HTML, parsing CGI-related parameters, and
# generating HTTP responses.
#++
class CGI
+ unless const_defined?(:Util)
+ module Util
+ @@accept_charset = "UTF-8" # :nodoc:
+ end
+ include Util
+ extend Util
+ end
$CGI_ENV = ENV # for FCGI support
@@ -67,8 +75,8 @@ class CGI
# Create an HTTP header block as a string.
#
# :call-seq:
- # header(content_type_string="text/html")
- # header(headers_hash)
+ # http_header(content_type_string="text/html")
+ # http_header(headers_hash)
#
# Includes the empty line that ends the header block.
#
@@ -127,29 +135,29 @@ class CGI
#
# Examples:
#
- # header
+ # http_header
# # Content-Type: text/html
#
- # header("text/plain")
+ # http_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")
+ # 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 header(options='text/html')
+ def http_header(options='text/html')
if options.is_a?(String)
content_type = options
buf = _header_for_string(content_type)
@@ -170,26 +178,45 @@ class CGI
buf << EOL # empty line of separator
return buf
end
- end # header()
+ 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 _no_crlf_check(str)
+ if str
+ str = str.to_s
+ raise "A HTTP status or header field must not include CR and LF" if str =~ /[\r\n]/
+ str
+ else
+ nil
+ end
+ end
+ private :_no_crlf_check
def _header_for_string(content_type) #:nodoc:
- buf = ''
+ buf = ''.dup
if nph?()
- buf << "#{$CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0'} 200 OK#{EOL}"
+ buf << "#{_no_crlf_check($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 << "Server: #{_no_crlf_check($CGI_ENV['SERVER_SOFTWARE'])}#{EOL}"
buf << "Connection: close#{EOL}"
end
- buf << "Content-Type: #{content_type}#{EOL}"
+ buf << "Content-Type: #{_no_crlf_check(content_type)}#{EOL}"
if @output_cookies
- @output_cookies.each {|cookie| buf << "Set-Cookie: #{cookie}#{EOL}" }
+ @output_cookies.each {|cookie| buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}" }
end
return buf
end # _header_for_string
private :_header_for_string
def _header_for_hash(options) #:nodoc:
- buf = ''
+ buf = ''.dup
## add charset to option['type']
options['type'] ||= 'text/html'
charset = options.delete('charset')
@@ -197,9 +224,9 @@ class CGI
## NPH
options.delete('nph') if defined?(MOD_RUBY)
if options.delete('nph') || nph?()
- protocol = $CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0'
+ protocol = _no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'
status = options.delete('status')
- status = HTTP_STATUS[status] || status || '200 OK'
+ status = HTTP_STATUS[status] || _no_crlf_check(status) || '200 OK'
buf << "#{protocol} #{status}#{EOL}"
buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
options['server'] ||= $CGI_ENV['SERVER_SOFTWARE'] || ''
@@ -207,51 +234,51 @@ class CGI
end
## common headers
status = options.delete('status')
- buf << "Status: #{HTTP_STATUS[status] || status}#{EOL}" if status
+ buf << "Status: #{HTTP_STATUS[status] || _no_crlf_check(status)}#{EOL}" if status
server = options.delete('server')
- buf << "Server: #{server}#{EOL}" if server
+ buf << "Server: #{_no_crlf_check(server)}#{EOL}" if server
connection = options.delete('connection')
- buf << "Connection: #{connection}#{EOL}" if connection
+ buf << "Connection: #{_no_crlf_check(connection)}#{EOL}" if connection
type = options.delete('type')
- buf << "Content-Type: #{type}#{EOL}" #if type
+ buf << "Content-Type: #{_no_crlf_check(type)}#{EOL}" #if type
length = options.delete('length')
- buf << "Content-Length: #{length}#{EOL}" if length
+ buf << "Content-Length: #{_no_crlf_check(length)}#{EOL}" if length
language = options.delete('language')
- buf << "Content-Language: #{language}#{EOL}" if language
+ buf << "Content-Language: #{_no_crlf_check(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}"
+ buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}"
when Array
arr = cookie
- arr.each {|c| buf << "Set-Cookie: #{c}#{EOL}" }
+ arr.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
when Hash
hash = cookie
- hash.each {|name, c| buf << "Set-Cookie: #{c}#{EOL}" }
+ hash.each_value {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
end
end
if @output_cookies
- @output_cookies.each {|c| buf << "Set-Cookie: #{c}#{EOL}" }
+ @output_cookies.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
end
## other headers
options.each do |key, value|
- buf << "#{key}: #{value}#{EOL}"
+ buf << "#{_no_crlf_check(key)}: #{_no_crlf_check(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
+ return /IIS\/(\d+)/ =~ $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
+ $stderr.printf("name:%s value:%s\n", name, value) if $DEBUG
case name
when 'Set-Cookie'
request.headers_out.add(name, value)
@@ -283,7 +310,7 @@ class CGI
# +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 #header.
+ # 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.
#
@@ -344,7 +371,7 @@ class CGI
options["length"] = content.bytesize.to_s
output = stdoutput
output.binmode if defined? output.binmode
- output.print header(options)
+ output.print http_header(options)
output.print content unless "HEAD" == env_table['REQUEST_METHOD']
end
@@ -359,14 +386,14 @@ class CGI
# Parse an HTTP query string into a hash of key=>value pairs.
#
- # params = CGI::parse("query_string")
+ # params = CGI.parse("query_string")
# # {"name1" => ["value1", "value2", ...],
# # "name2" => ["value1", "value2", ...], ... }
#
- def CGI::parse(query)
+ def self.parse(query)
params = {}
query.split(/[&;]/).each do |pairs|
- key, value = pairs.split('=',2).collect{|v| CGI::unescape(v) }
+ key, value = pairs.split('=',2).collect{|v| CGI.unescape(v) }
next unless key
@@ -381,9 +408,6 @@ class CGI
# Maximum content length of post data
##MAX_CONTENT_LENGTH = 2 * 1024 * 1024
- # Maximum content length of multipart data
- MAX_MULTIPART_LENGTH = 128 * 1024 * 1024
-
# Maximum number of request parameters when multipart
MAX_MULTIPART_COUNT = 128
@@ -408,7 +432,7 @@ class CGI
module QueryExtension
%w[ CONTENT_LENGTH SERVER_PORT ].each do |env|
- define_method(env.sub(/^HTTP_/, '').downcase) do
+ define_method(env.delete_prefix('HTTP_').downcase) do
(val = env_table[env]) && Integer(val)
end
end
@@ -421,7 +445,7 @@ class CGI
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
+ define_method(env.delete_prefix('HTTP_').downcase) do
env_table[env]
end
end
@@ -474,15 +498,16 @@ class CGI
@files = {}
boundary_rexp = /--#{Regexp.quote(boundary)}(#{EOL}|--)/
boundary_size = "#{EOL}--#{boundary}#{EOL}".bytesize
- boundary_end = nil
- buf = ''
+ buf = ''.dup
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
@@ -528,18 +553,19 @@ class CGI
body.rewind
## original filename
/Content-Disposition:.* filename=(?:"(.*?)"|([^;\r\n]*))/i.match(head)
- filename = $1 || $2 || ''
+ filename = $1 || $2 || ''.dup
filename = CGI.unescape(filename) if unescape_filename?()
- body.instance_variable_set('@original_filename', filename.taint)
+ body.instance_variable_set(:@original_filename, filename)
## content type
/Content-Type: (.*)/i.match(head)
- (content_type = $1 || '').chomp!
- body.instance_variable_set('@content_type', content_type.taint)
+ (content_type = $1 || ''.dup).chomp!
+ body.instance_variable_set(:@content_type, content_type)
## 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
@@ -563,19 +589,28 @@ class CGI
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: "ascii-8bit")
+ body = Tempfile.new('CGI', encoding: Encoding::ASCII_8BIT)
else
begin
require 'stringio'
- body = StringIO.new("".force_encoding("ascii-8bit"))
+ body = StringIO.new("".b)
rescue LoadError
require 'tempfile'
- body = Tempfile.new('CGI', encoding: "ascii-8bit")
+ body = Tempfile.new('CGI', encoding: Encoding::ASCII_8BIT)
end
end
body.binmode if defined? body.binmode
@@ -583,6 +618,7 @@ class CGI
end
def unescape_filename? #:nodoc:
user_agent = $CGI_ENV['HTTP_USER_AGENT']
+ return false unless user_agent
return /Mac/i.match(user_agent) && /Mozilla/i.match(user_agent) && !/MSIE/i.match(user_agent)
end
@@ -624,14 +660,15 @@ class CGI
# Reads query parameters in the @params field, and cookies into @cookies.
def initialize_query()
if ("POST" == env_table['REQUEST_METHOD']) and
- %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|.match(env_table['CONTENT_TYPE'])
- raise StandardError.new("too large multipart data.") if env_table['CONTENT_LENGTH'].to_i > MAX_MULTIPART_LENGTH
+ %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?| =~ 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(
+ @params = CGI.parse(
case env_table['REQUEST_METHOD']
when "GET", "HEAD"
if defined?(MOD_RUBY)
@@ -661,7 +698,7 @@ class CGI
end
end
- @cookies = CGI::Cookie::parse((env_table['HTTP_COOKIE'] or env_table['COOKIE']))
+ @cookies = CGI::Cookie.parse((env_table['HTTP_COOKIE'] or env_table['COOKIE']))
end
private :initialize_query
@@ -682,9 +719,9 @@ class CGI
if value
return value
elsif defined? StringIO
- StringIO.new("".force_encoding("ascii-8bit"))
+ StringIO.new("".b)
else
- Tempfile.new("CGI",encoding:"ascii-8bit")
+ Tempfile.new("CGI",encoding: Encoding::ASCII_8BIT)
end
else
str = if value then value.dup else "" end
@@ -716,7 +753,7 @@ class CGI
#
# CGI.accept_charset = "EUC-JP"
#
- @@accept_charset="UTF-8"
+ @@accept_charset="UTF-8" if false # needed for rdoc?
# Return the accept character set for all new CGI instances.
def self.accept_charset
@@ -731,6 +768,16 @@ class CGI
# 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:
@@ -744,7 +791,7 @@ class CGI
# +options_hash+ form, since it also allows you specify the charset you
# will accept.
# <tt>options_hash</tt>::
- # A Hash that recognizes two options:
+ # A Hash that recognizes three options:
#
# <tt>:accept_charset</tt>::
# specifies encoding of received query string. If omitted,
@@ -773,6 +820,18 @@ class CGI
# "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:
@@ -790,7 +849,10 @@ class CGI
# 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}
+ @options={
+ :accept_charset=>@@accept_charset,
+ :max_multipart_length=>@@max_multipart_length
+ }
case options
when Hash
@options.merge!(options)
@@ -798,6 +860,7 @@ class CGI
@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
@@ -811,35 +874,27 @@ class CGI
case @options[:tag_maker]
when "html3"
- require 'cgi/html'
+ require_relative 'html'
extend Html3
- element_init()
extend HtmlExtension
when "html4"
- require 'cgi/html'
+ require_relative 'html'
extend Html4
- element_init()
extend HtmlExtension
when "html4Tr"
- require 'cgi/html'
+ require_relative 'html'
extend Html4Tr
- element_init()
extend HtmlExtension
when "html4Fr"
- require 'cgi/html'
+ require_relative 'html'
extend Html4Tr
- element_init()
extend Html4Fr
- element_init()
extend HtmlExtension
when "html5"
- require 'cgi/html'
+ require_relative 'html'
extend Html5
- element_init()
extend HtmlExtension
end
end
end # class CGI
-
-
diff --git a/lib/cgi/html.rb b/lib/cgi/html.rb
index 3054279b54..1543943320 100644
--- a/lib/cgi/html.rb
+++ b/lib/cgi/html.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
class CGI
# Base module for HTML-generation mixins.
#
@@ -8,55 +9,62 @@ class CGI
# 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
+ 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_def(element, append = nil)
- s = <<-END
- attributes={attributes=>nil} if attributes.kind_of?(String)
- "<#{element.upcase}" + attributes.collect{|name, value|
- next unless value
- " " + CGI::escapeHTML(name.to_s) +
- if true == value
- ""
- else
- '="' + CGI::escapeHTML(value.to_s) + '"'
- end
- }.join + ">"
- END
- s.sub!(/\Z/, " +") << append if append
- s
+ def nOE_element(element, attributes = {})
+ attributes={attributes=>nil} if attributes.kind_of?(String)
+ s = "<#{element.upcase}".dup
+ 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_def(element)
- nOE_element_def(element, <<-END)
- if block_given?
- yield.to_s + "</#{element.upcase}>"
- else
- ""
- end
- END
+ 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,
@@ -92,11 +100,7 @@ class CGI
else
href
end
- if block_given?
- super(attributes){ yield }
- else
- super(attributes)
- end
+ super(attributes)
end
# Generate a Document Base URI element as a String.
@@ -114,11 +118,7 @@ class CGI
else
href
end
- if block_given?
- super(attributes){ yield }
- else
- super(attributes)
- end
+ super(attributes)
end
# Generate a BlockQuote element as a string.
@@ -137,11 +137,7 @@ class CGI
else
cite
end
- if block_given?
- super(attributes){ yield }
- else
- super(attributes)
- end
+ super(attributes)
end
@@ -161,11 +157,7 @@ class CGI
else
align
end
- if block_given?
- super(attributes){ yield }
- else
- super(attributes)
- end
+ super(attributes)
end
@@ -416,7 +408,7 @@ class CGI
end
pretty = attributes.delete("PRETTY")
pretty = " " if true == pretty
- buf = ""
+ buf = "".dup
if attributes.has_key?("DOCTYPE")
if attributes["DOCTYPE"]
@@ -428,14 +420,10 @@ class CGI
buf << doctype
end
- if block_given?
- buf << super(attributes){ yield }
- else
- buf << super(attributes)
- end
+ buf << super(attributes)
if pretty
- CGI::pretty(buf, pretty)
+ CGI.pretty(buf, pretty)
else
buf
end
@@ -824,11 +812,7 @@ class CGI
else
name
end
- if block_given?
- super(attributes){ yield }
- else
- super(attributes)
- end
+ super(attributes)
end
end # HtmlExtension
@@ -836,50 +820,38 @@ class CGI
# 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
- # Initialise the HTML generation methods for this version.
- def element_init
- extend TagMaker
- return if defined?(html)
- methods = ""
+ 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 ]
- methods << <<-BEGIN + nn_element_def(element) + <<-END
- def #{element.downcase}(attributes = {})
- BEGIN
- end
- END
+ 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 ]
- methods << <<-BEGIN + nOE_element_def(element) + <<-END
- def #{element.downcase}(attributes = {})
- BEGIN
- end
- END
+ 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 ]
- methods << <<-BEGIN + nO_element_def(element) + <<-END
- def #{element.downcase}(attributes = {})
- BEGIN
- end
- END
+ define_method(element.downcase, m)
end
- eval(methods)
end
end # Html3
@@ -887,49 +859,38 @@ class CGI
# 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
- # Initialise the HTML generation methods for this version.
- def element_init
- extend TagMaker
- return if defined?(html)
- methods = ""
- # - -
+ # 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 ]
- methods << <<-BEGIN + nn_element_def(element) + <<-END
- def #{element.downcase}(attributes = {})
- BEGIN
- end
- END
+ define_method(element.downcase, m)
end
+ end
- # - O EMPTY
+ # - O EMPTY
+ instance_method(:nOE_element_def).tap do |m|
for element in %w[ IMG BASE BR AREA LINK PARAM HR INPUT COL META ]
- methods << <<-BEGIN + nOE_element_def(element) + <<-END
- def #{element.downcase}(attributes = {})
- BEGIN
- end
- END
+ define_method(element.downcase, m)
end
+ end
- # O O or - O
+ # 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 ]
- methods << <<-BEGIN + nO_element_def(element) + <<-END
- def #{element.downcase}(attributes = {})
- BEGIN
- end
- END
+ define_method(element.downcase, m)
end
- eval(methods)
end
end # Html4
@@ -937,6 +898,7 @@ class CGI
# Mixin module for HTML version 4 transitional generation methods.
module Html4Tr # :nodoc:
+ include TagMaker
# The DOCTYPE declaration for this version of HTML
def doctype
@@ -944,44 +906,32 @@ class CGI
end
# Initialise the HTML generation methods for this version.
- def element_init
- extend TagMaker
- return if defined?(html)
- methods = ""
- # - -
+ # - -
+ 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 ]
- methods << <<-BEGIN + nn_element_def(element) + <<-END
- def #{element.downcase}(attributes = {})
- BEGIN
- end
- END
+ define_method(element.downcase, m)
end
+ end
- # - O EMPTY
+ # - 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 ]
- methods << <<-BEGIN + nOE_element_def(element) + <<-END
- def #{element.downcase}(attributes = {})
- BEGIN
- end
- END
+ define_method(element.downcase, m)
end
+ end
- # O O or - O
+ # 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 ]
- methods << <<-BEGIN + nO_element_def(element) + <<-END
- def #{element.downcase}(attributes = {})
- BEGIN
- end
- END
+ define_method(element.downcase, m)
end
- eval(methods)
end
end # Html4Tr
@@ -989,6 +939,7 @@ class CGI
# Mixin module for generating HTML version 4 with framesets.
module Html4Fr # :nodoc:
+ include TagMaker
# The DOCTYPE declaration for this version of HTML
def doctype
@@ -996,27 +947,18 @@ class CGI
end
# Initialise the HTML generation methods for this version.
- def element_init
- return if defined?(frameset)
- methods = ""
- # - -
+ # - -
+ instance_method(:nn_element_def).tap do |m|
for element in %w[ FRAMESET ]
- methods << <<-BEGIN + nn_element_def(element) + <<-END
- def #{element.downcase}(attributes = {})
- BEGIN
- end
- END
+ define_method(element.downcase, m)
end
+ end
- # - O EMPTY
+ # - O EMPTY
+ instance_method(:nOE_element_def).tap do |m|
for element in %w[ FRAME ]
- methods << <<-BEGIN + nOE_element_def(element) + <<-END
- def #{element.downcase}(attributes = {})
- BEGIN
- end
- END
+ define_method(element.downcase, m)
end
- eval(methods)
end
end # Html4Fr
@@ -1024,6 +966,7 @@ class CGI
# Mixin module for HTML version 5 generation methods.
module Html5 # :nodoc:
+ include TagMaker
# The DOCTYPE declaration for this version of HTML
def doctype
@@ -1031,11 +974,9 @@ class CGI
end
# Initialise the HTML generation methods for this version.
- def element_init
- extend TagMaker
- methods = ""
- # - -
- for element in %w[ SECTION NAV ARTICLE ASIDE HGROUP
+ # - -
+ 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
@@ -1043,34 +984,52 @@ class CGI
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 ]
- methods += <<-BEGIN + nn_element_def(element) + <<-END
- def #{element.downcase}(attributes = {})
- BEGIN
- end
- END
+ define_method(element.downcase, m)
end
+ end
- # - O EMPTY
+ # - 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 ]
- methods += <<-BEGIN + nOE_element_def(element) + <<-END
- def #{element.downcase}(attributes = {})
- BEGIN
- end
- END
+ define_method(element.downcase, m)
end
+ end
- # O O or - O
+ # 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 ]
- methods += <<-BEGIN + nO_element_def(element) + <<-END
- def #{element.downcase}(attributes = {})
- BEGIN
- end
- END
+ define_method(element.downcase, m)
end
- eval(methods)
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/lib/cgi/session.rb b/lib/cgi/session.rb
index 42b5ead81a..70c7ebca42 100644
--- a/lib/cgi/session.rb
+++ b/lib/cgi/session.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
#
# cgi/session.rb - session support for cgi scripts
#
@@ -62,7 +63,7 @@ class CGI
# 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
+ # 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,
@@ -163,29 +164,72 @@ class CGI
# 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.
+ # The session id is a secure random number by SecureRandom
+ # if possible, otherwise an SHA512 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
+ # by OpenSSL, or system provided entropy pool
session_id = SecureRandom.hex(16)
rescue NotImplementedError
- require 'digest/md5'
- md5 = Digest::MD5::new
+ # never happens on modern systems
+ require 'digest'
+ d = Digest('SHA512').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
+ d.update(now.to_s)
+ d.update(String(now.usec))
+ d.update(String(rand(0)))
+ d.update(String($$))
+ d.update('foobar')
+ session_id = d.hexdigest[0, 32]
end
session_id
end
private :create_new_id
+
+ # Create a new file to store the session data.
+ #
+ # This file will be created if it does not exist, or opened if it
+ # does.
+ #
+ # This path is generated under _tmpdir_ from _prefix_, the
+ # digested session id, and _suffix_.
+ #
+ # +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.
+ def new_store_file(option={}) # :nodoc:
+ dir = option['tmpdir'] || Dir::tmpdir
+ prefix = option['prefix']
+ suffix = option['suffix']
+ require 'digest/md5'
+ md5 = Digest::MD5.hexdigest(session_id)[0,16]
+ path = dir+"/"
+ path << prefix if prefix
+ path << md5
+ path << suffix if suffix
+ if File::exist? path
+ hash = nil
+ elsif new_session
+ hash = {}
+ else
+ raise NoSession, "uninitialized session"
+ end
+ return path, hash
+ end
+
# Create a new CGI::Session object for +request+.
#
# +request+ is an instance of the +CGI+ class (see cgi.rb).
@@ -308,7 +352,7 @@ class CGI
@data[key]
end
- # Set the session date for key +key+.
+ # Set the session data for key +key+.
def []=(key, val)
@write_lock ||= true
@data ||= @dbman.restore
@@ -370,21 +414,8 @@ class CGI
# 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
+ option = {'prefix' => 'cgi_sid_'}.update(option)
+ @path, @hash = session.new_store_file(option)
end
# Restore session state from the session's FileStore file.
@@ -400,11 +431,11 @@ class CGI
for line in f
line.chomp!
k, v = line.split('=',2)
- @hash[CGI::unescape(k)] = Marshal.restore(CGI::unescape(v))
+ @hash[CGI.unescape(k)] = Marshal.restore(CGI.unescape(v))
end
ensure
- f.close unless f.nil?
- lockf.close if lockf
+ f&.close
+ lockf&.close
end
end
@hash
@@ -418,13 +449,13 @@ class CGI
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)))
+ 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
+ f&.close
+ lockf&.close
end
end
@@ -437,14 +468,14 @@ class CGI
def delete
File::unlink @path+".lock" rescue nil
File::unlink @path+".new" rescue nil
- File::unlink @path rescue Errno::ENOENT
+ 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
+ # data will only persist for as long as the Ruby interpreter
# instance does.
class MemoryStore
GLOBAL_HASH_TABLE = {} #:nodoc:
@@ -453,7 +484,7 @@ class CGI
#
# +session+ is the session this instance is associated with.
# +option+ is a list of initialisation options. None are
- # currently recognised.
+ # currently recognized.
def initialize(session, option=nil)
@session_id = session.session_id
unless GLOBAL_HASH_TABLE.key?(@session_id)
diff --git a/lib/cgi/session/pstore.rb b/lib/cgi/session/pstore.rb
index a63d7d3984..45d0d8ae2c 100644
--- a/lib/cgi/session/pstore.rb
+++ b/lib/cgi/session/pstore.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
#
# cgi/session/pstore.rb - persistent storage of marshalled session data
#
@@ -9,7 +10,7 @@
# 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_relative '../session'
require 'pstore'
class CGI
@@ -43,21 +44,8 @@ class CGI
# 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
+ option = {'suffix'=>''}.update(option)
+ path, @hash = session.new_store_file(option)
@p = ::PStore.new(path)
@p.transaction do |p|
File.chmod(0600, p.path)
@@ -97,15 +85,4 @@ class CGI
end
end
end
-
-if $0 == __FILE__
- # :enddoc:
- STDIN.reopen("/dev/null")
- cgi = CGI.new
- session = CGI::Session.new(cgi, 'database_manager' => CGI::Session::PStore)
- session['key'] = {'k' => 'v'}
- puts session['key'].class
- fail unless Hash === session['key']
- puts session['key'].inspect
- fail unless session['key'].inspect == '{"k"=>"v"}'
-end
+# :enddoc:
diff --git a/lib/cgi/util.rb b/lib/cgi/util.rb
index 41ae724c8c..ce77a0ccd5 100644
--- a/lib/cgi/util.rb
+++ b/lib/cgi/util.rb
@@ -1,22 +1,61 @@
+# frozen_string_literal: true
class CGI
- @@accept_charset="UTF-8" unless defined?(@@accept_charset)
- # URL-encode a string.
- # url_encoded_string = CGI::escape("'Stop!' said Fred")
+ module Util; end
+ include Util
+ extend Util
+end
+module CGI::Util
+ @@accept_charset = Encoding::UTF_8 unless defined?(@@accept_charset)
+
+ # URL-encode a string into application/x-www-form-urlencoded.
+ # Space characters (+" "+) are encoded with plus signs (+"+"+)
+ # url_encoded_string = CGI.escape("'Stop!' said Fred")
# # => "%27Stop%21%27+said+Fred"
- def CGI::escape(string)
+ def escape(string)
encoding = string.encoding
- string.dup.force_encoding('ASCII-8BIT').gsub(/([^ a-zA-Z0-9_.-]+)/) do
- '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
- end.tr(' ', '+').force_encoding(encoding)
+ buffer = string.b
+ buffer.gsub!(/([^ a-zA-Z0-9_.\-~]+)/) do |m|
+ '%' + m.unpack('H2' * m.bytesize).join('%').upcase
+ end
+ buffer.tr!(' ', '+')
+ buffer.force_encoding(encoding)
end
- # URL-decode a string with encoding(optional).
- # string = CGI::unescape("%27Stop%21%27+said+Fred")
+ # URL-decode an application/x-www-form-urlencoded string with encoding(optional).
+ # string = CGI.unescape("%27Stop%21%27+said+Fred")
# # => "'Stop!' said Fred"
- def CGI::unescape(string,encoding=@@accept_charset)
- str=string.tr('+', ' ').force_encoding(Encoding::ASCII_8BIT).gsub(/((?:%[0-9a-fA-F]{2})+)/) do
- [$1.delete('%')].pack('H*')
- end.force_encoding(encoding)
+ def unescape(string, encoding = @@accept_charset)
+ str = string.tr('+', ' ')
+ str = str.b
+ str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m|
+ [m.delete('%')].pack('H*')
+ end
+ str.force_encoding(encoding)
+ str.valid_encoding? ? str : str.force_encoding(string.encoding)
+ end
+
+ # URL-encode a string following RFC 3986
+ # Space characters (+" "+) are encoded with (+"%20"+)
+ # url_encoded_string = CGI.escape("'Stop!' said Fred")
+ # # => "%27Stop%21%27%20said%20Fred"
+ def escapeURIComponent(string)
+ encoding = string.encoding
+ buffer = string.b
+ buffer.gsub!(/([^a-zA-Z0-9_.\-~]+)/) do |m|
+ '%' + m.unpack('H2' * m.bytesize).join('%').upcase
+ end
+ buffer.force_encoding(encoding)
+ end
+
+ # URL-decode a string following RFC 3986 with encoding(optional).
+ # string = CGI.unescape("%27Stop%21%27+said%20Fred")
+ # # => "'Stop!'+said Fred"
+ def unescapeURIComponent(string, encoding = @@accept_charset)
+ str = string.b
+ str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m|
+ [m.delete('%')].pack('H*')
+ end
+ str.force_encoding(encoding)
str.valid_encoding? ? str : str.force_encoding(string.encoding)
end
@@ -29,21 +68,46 @@ class CGI
'>' => '&gt;',
}
- # Escape special characters in HTML, namely &\"<>
- # CGI::escapeHTML('Usage: foo "bar" <baz>')
+ # Escape special characters in HTML, namely '&\"<>
+ # CGI.escapeHTML('Usage: foo "bar" <baz>')
# # => "Usage: foo &quot;bar&quot; &lt;baz&gt;"
- def CGI::escapeHTML(string)
- string.gsub(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__)
+ def escapeHTML(string)
+ enc = string.encoding
+ unless enc.ascii_compatible?
+ if enc.dummy?
+ origenc = enc
+ enc = Encoding::Converter.asciicompat_encoding(enc)
+ string = enc ? string.encode(enc) : string.b
+ end
+ table = Hash[TABLE_FOR_ESCAPE_HTML__.map {|pair|pair.map {|s|s.encode(enc)}}]
+ string = string.gsub(/#{"['&\"<>]".encode(enc)}/, table)
+ string.encode!(origenc) if origenc
+ string
+ else
+ string = string.b
+ string.gsub!(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__)
+ string.force_encoding(enc)
+ end
+ end
+
+ begin
+ require 'cgi/escape'
+ rescue LoadError
end
# Unescape a string that has been HTML-escaped
- # CGI::unescapeHTML("Usage: foo &quot;bar&quot; &lt;baz&gt;")
+ # CGI.unescapeHTML("Usage: foo &quot;bar&quot; &lt;baz&gt;")
# # => "Usage: foo \"bar\" <baz>"
- def CGI::unescapeHTML(string)
+ def 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('&(apos|amp|quot|gt|lt|#[0-9]+|#x[0-9A-Fa-f]+);'.encode(enc))) do
- case $1.encode("US-ASCII")
+ unless enc.ascii_compatible?
+ if enc.dummy?
+ origenc = enc
+ enc = Encoding::Converter.asciicompat_encoding(enc)
+ string = enc ? string.encode(enc) : string.b
+ end
+ string = 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)
@@ -53,9 +117,17 @@ class CGI
when /\A#x([0-9a-f]+)\z/i then $1.hex.chr(enc)
end
end
+ string.encode!(origenc) if origenc
+ return string
end
- asciicompat = Encoding.compatible?(string, "a")
- string.gsub(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#x[0-9A-Fa-f]+);/) do
+ return string unless string.include? '&'
+ charlimit = case enc
+ when Encoding::UTF_8; 0x10ffff
+ when Encoding::ISO_8859_1; 256
+ else 128
+ end
+ string = string.b
+ string.gsub!(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do
match = $1.dup
case match
when 'apos' then "'"
@@ -65,18 +137,14 @@ class CGI
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
+ if n < charlimit
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
+ if n < charlimit
n.chr(enc)
else
"&#x#{$1};"
@@ -85,17 +153,14 @@ class CGI
"&#{match};"
end
end
+ string.force_encoding enc
end
- # Synonym for CGI::escapeHTML(str)
- def CGI::escape_html(str)
- escapeHTML(str)
- end
+ # Synonym for CGI.escapeHTML(str)
+ alias escape_html escapeHTML
- # Synonym for CGI::unescapeHTML(str)
- def CGI::unescape_html(str)
- unescapeHTML(str)
- end
+ # Synonym for CGI.unescapeHTML(str)
+ alias unescape_html unescapeHTML
# Escape only the tags of certain HTML elements in +string+.
#
@@ -105,67 +170,54 @@ class CGI
# 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")
+ # print CGI.escapeElement('<BR><A HREF="url"></A>', "A", "IMG")
# # "<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt"
#
- # print CGI::escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"])
+ # print CGI.escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"])
# # "<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt"
- def CGI::escapeElement(string, *elements)
+ 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($&)
+ string.gsub(/<\/?(?:#{elements.join("|")})\b[^<>]*+>?/im) do
+ CGI.escapeHTML($&)
end
else
string
end
end
- # Undo escaping such as that done by CGI::escapeElement()
+ # Undo escaping such as that done by CGI.escapeElement()
#
- # print CGI::unescapeElement(
- # CGI::escapeHTML('<BR><A HREF="url"></A>'), "A", "IMG")
+ # print CGI.unescapeElement(
+ # CGI.escapeHTML('<BR><A HREF="url"></A>'), "A", "IMG")
# # "&lt;BR&gt;<A HREF="url"></A>"
#
- # print CGI::unescapeElement(
- # CGI::escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"])
+ # print CGI.unescapeElement(
+ # CGI.escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"])
# # "&lt;BR&gt;<A HREF="url"></A>"
- def CGI::unescapeElement(string, *elements)
+ def unescapeElement(string, *elements)
elements = elements[0] if elements[0].kind_of?(Array)
unless elements.empty?
- string.gsub(/&lt;\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?&gt;/i) do
- CGI::unescapeHTML($&)
+ string.gsub(/&lt;\/?(?:#{elements.join("|")})\b(?>[^&]+|&(?![gl]t;)\w+;)*(?:&gt;)?/im) do
+ unescapeHTML($&)
end
else
string
end
end
- # Synonym for CGI::escapeElement(str)
- def CGI::escape_element(str)
- escapeElement(str)
- end
-
- # Synonym for CGI::unescapeElement(str)
- def CGI::unescape_element(str)
- unescapeElement(str)
- end
-
- # Abbreviated day-of-week names specified by RFC 822
- RFC822_DAYS = %w[ Sun Mon Tue Wed Thu Fri Sat ]
+ # Synonym for CGI.escapeElement(str)
+ alias escape_element escapeElement
- # Abbreviated month names specified by RFC 822
- RFC822_MONTHS = %w[ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ]
+ # Synonym for CGI.unescapeElement(str)
+ alias unescape_element unescapeElement
# Format a +Time+ object as a String using the format specified by RFC 1123.
#
- # CGI::rfc1123_date(Time.now)
+ # 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)
+ def rfc1123_date(time)
+ time.getgm.strftime("%a, %d %b %Y %T GMT")
end
# Prettify (indent) an HTML string.
@@ -173,19 +225,19 @@ class CGI
# +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>")
+ # print CGI.pretty("<HTML><BODY></BODY></HTML>")
# # <HTML>
# # <BODY>
# # </BODY>
# # </HTML>
#
- # print CGI::pretty("<HTML><BODY></BODY></HTML>", "\t")
+ # print CGI.pretty("<HTML><BODY></BODY></HTML>", "\t")
# # <HTML>
# # <BODY>
# # </BODY>
# # </HTML>
#
- def CGI::pretty(string, shift = " ")
+ 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)
@@ -195,4 +247,6 @@ class CGI
end
lines.gsub(/^((?:#{Regexp::quote(shift)})*)__(?=<\/?\w)/, '\1')
end
+
+ alias h escapeHTML
end