diff options
Diffstat (limited to 'lib/cgi')
-rw-r--r-- | lib/cgi/cgi.gemspec | 19 | ||||
-rw-r--r-- | lib/cgi/cookie.rb | 45 | ||||
-rw-r--r-- | lib/cgi/core.rb | 45 | ||||
-rw-r--r-- | lib/cgi/session.rb | 60 | ||||
-rw-r--r-- | lib/cgi/session/pstore.rb | 16 | ||||
-rw-r--r-- | lib/cgi/util.rb | 72 |
6 files changed, 176 insertions, 81 deletions
diff --git a/lib/cgi/cgi.gemspec b/lib/cgi/cgi.gemspec index 5d23ef0f61..381c55a5ca 100644 --- a/lib/cgi/cgi.gemspec +++ b/lib/cgi/cgi.gemspec @@ -22,10 +22,21 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end - spec.bindir = "exe" 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 ae9ab58ede..9498e2f9fa 100644 --- a/lib/cgi/cookie.rb +++ b/lib/cgi/cookie.rb @@ -40,6 +40,10 @@ class CGI 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. # # :call-seq: @@ -72,8 +76,8 @@ class CGI @domain = nil @expires = nil if name.kind_of?(String) - @name = name - @path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") + self.name = name + self.path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") @secure = false @httponly = false return super(value) @@ -84,11 +88,11 @@ class CGI raise ArgumentError, "`name' required" end - @name = options["name"] + self.name = options["name"] value = Array(options["value"]) # simple support for IE - @path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") - @domain = options["domain"] + self.path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") + self.domain = options["domain"] @expires = options["expires"] @secure = options["secure"] == true @httponly = options["httponly"] == true @@ -97,11 +101,35 @@ class CGI 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 @@ -159,7 +187,6 @@ class CGI 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) diff --git a/lib/cgi/core.rb b/lib/cgi/core.rb index bec76e0749..62e606837a 100644 --- a/lib/cgi/core.rb +++ b/lib/cgi/core.rb @@ -188,17 +188,28 @@ class CGI # 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 = ''.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 @@ -213,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'] || '' @@ -223,38 +234,38 @@ 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_value {|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 diff --git a/lib/cgi/session.rb b/lib/cgi/session.rb index 29e7b3ece3..aab60869bb 100644 --- a/lib/cgi/session.rb +++ b/lib/cgi/session.rb @@ -189,6 +189,47 @@ class CGI 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). @@ -238,7 +279,7 @@ class CGI # fields are surrounded by a <fieldset> tag in HTML 4 generation, which # is _not_ invisible on many browsers; you may wish to disable the # use of fieldsets with code similar to the following - # (see http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/37805) + # (see https://blade.ruby-lang.org/ruby-list/37805) # # cgi = CGI.new("html4") # class << cgi @@ -373,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. diff --git a/lib/cgi/session/pstore.rb b/lib/cgi/session/pstore.rb index cc3006400f..45d0d8ae2c 100644 --- a/lib/cgi/session/pstore.rb +++ b/lib/cgi/session/pstore.rb @@ -44,20 +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 - 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) diff --git a/lib/cgi/util.rb b/lib/cgi/util.rb index 69a252b5e3..4986e544e0 100644 --- a/lib/cgi/util.rb +++ b/lib/cgi/util.rb @@ -5,27 +5,63 @@ class CGI extend Util end module CGI::Util - @@accept_charset="UTF-8" unless defined?(@@accept_charset) - # URL-encode a string. + @@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 escape(string) encoding = string.encoding - string.b.gsub(/([^ a-zA-Z0-9_.\-~]+)/) do |m| + buffer = string.b + buffer.gsub!(/([^ a-zA-Z0-9_.\-~]+)/) do |m| '%' + m.unpack('H2' * m.bytesize).join('%').upcase - end.tr(' ', '+').force_encoding(encoding) + end + buffer.tr!(' ', '+') + buffer.force_encoding(encoding) end - # URL-decode a string with encoding(optional). + # URL-decode an application/x-www-form-urlencoded string with encoding(optional). # string = CGI.unescape("%27Stop%21%27+said+Fred") # # => "'Stop!' said Fred" - def unescape(string,encoding=@@accept_charset) - str=string.tr('+', ' ').b.gsub(/((?:%[0-9a-fA-F]{2})+)/) do |m| + 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.force_encoding(encoding) + 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.escapeURIComponent("'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 + alias escape_uri_component escapeURIComponent + + # URL-decode a string following RFC 3986 with encoding(optional). + # string = CGI.unescapeURIComponent("%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 + + alias unescape_uri_component unescapeURIComponent + # The set of special characters and their escaped values TABLE_FOR_ESCAPE_HTML__ = { "'" => ''', @@ -57,9 +93,12 @@ module CGI::Util end end - begin - require 'cgi/escape' - rescue LoadError + # TruffleRuby runs the pure-Ruby variant faster, do not use the C extension there + unless RUBY_ENGINE == 'truffleruby' + begin + require 'cgi/escape' + rescue LoadError + end end # Unescape a string that has been HTML-escaped @@ -179,21 +218,12 @@ module CGI::Util # Synonym for CGI.unescapeElement(str) alias unescape_element unescapeElement - # Abbreviated day-of-week names specified by RFC 822 - RFC822_DAYS = %w[ Sun Mon Tue Wed Thu Fri Sat ] - - # Abbreviated month names specified by RFC 822 - RFC822_MONTHS = %w[ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ] - # Format a +Time+ object as a String using the format specified by RFC 1123. # # CGI.rfc1123_date(Time.now) # # Sat, 01 Jan 2000 00:00:00 GMT def rfc1123_date(time) - t = time.clone.gmtime - return format("%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT", - RFC822_DAYS[t.wday], t.day, RFC822_MONTHS[t.month-1], t.year, - t.hour, t.min, t.sec) + time.getgm.strftime("%a, %d %b %Y %T GMT") end # Prettify (indent) an HTML string. |