diff options
Diffstat (limited to 'lib/cgi')
| -rw-r--r-- | lib/cgi/escape.rb | 232 | ||||
| -rw-r--r-- | lib/cgi/session.rb | 176 | ||||
| -rw-r--r-- | lib/cgi/util.rb | 7 |
3 files changed, 239 insertions, 176 deletions
diff --git a/lib/cgi/escape.rb b/lib/cgi/escape.rb new file mode 100644 index 0000000000..555d24a5da --- /dev/null +++ b/lib/cgi/escape.rb @@ -0,0 +1,232 @@ +# frozen_string_literal: true + +# Since Ruby 4.0, \CGI is a small holder for various escaping methods, included from CGI::Escape +# +# require 'cgi/escape' +# +# CGI.escape("Ruby programming language") +# #=> "Ruby+programming+language" +# CGI.escapeURIComponent("Ruby programming language") +# #=> "Ruby%20programming%20language" +# +# See CGI::Escape module for methods list and their description. +class CGI + module Escape; end + include Escape + extend Escape + module EscapeExt; end # :nodoc: +end + +# Web-related escape/unescape functionality. +module CGI::Escape + @@accept_charset = Encoding::UTF_8 unless defined?(@@accept_charset) + + # URL-encode a string into application/x-www-form-urlencoded. + # Space characters (<tt>" "</tt>) are encoded with plus signs (<tt>"+"</tt>) + # url_encoded_string = CGI.escape("'Stop!' said Fred") + # # => "%27Stop%21%27+said+Fred" + def escape(string) + encoding = string.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 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('+', ' ') + 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 (<tt>" "</tt>) are encoded with (<tt>"%20"</tt>) + # 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__ = { # :nodoc: + "'" => ''', + '&' => '&', + '"' => '"', + '<' => '<', + '>' => '>', + } + + # \Escape special characters in HTML, namely <tt>'&\"<></tt> + # CGI.escapeHTML('Usage: foo "bar" <baz>') + # # => "Usage: foo "bar" <baz>" + 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 + + # Unescape a string that has been HTML-escaped + # CGI.unescapeHTML("Usage: foo "bar" <baz>") + # # => "Usage: foo \"bar\" <baz>" + def unescapeHTML(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 + 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) + 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 + string.encode!(origenc) if origenc + return string + end + 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 "'" + when 'amp' then '&' + when 'quot' then '"' + when 'gt' then '>' + when 'lt' then '<' + when /\A#0*(\d+)\z/ + n = $1.to_i + if n < charlimit + n.chr(enc) + else + "&##{$1};" + end + when /\A#x([0-9a-f]+)\z/i + n = $1.hex + if n < charlimit + n.chr(enc) + else + "&#x#{$1};" + end + else + "&#{match};" + end + end + string.force_encoding enc + end + + alias escape_html escapeHTML + alias h escapeHTML + + alias unescape_html unescapeHTML + + # TruffleRuby runs the pure-Ruby variant faster, do not use the C extension there + unless RUBY_ENGINE == 'truffleruby' + begin + require 'cgi/escape.so' + rescue LoadError + end + end + + # \Escape only the tags of certain HTML elements in +string+. + # + # Takes an element or elements or array of elements. Each element + # is specified by the name of the element, without angle brackets. + # This matches both the start and the end tag of that element. + # The attribute list of the open tag will also be escaped (for + # instance, the double-quotes surrounding attribute values). + # + # print CGI.escapeElement('<BR><A HREF="url"></A>', "A", "IMG") + # # "<BR><A HREF="url"></A>" + # + # print CGI.escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"]) + # # "<BR><A HREF="url"></A>" + def escapeElement(string, *elements) + elements = elements[0] if elements[0].kind_of?(Array) + unless elements.empty? + string.gsub(/<\/?(?:#{elements.join("|")})\b[^<>]*+>?/im) do + CGI.escapeHTML($&) + end + else + string + end + end + + # Undo escaping such as that done by CGI.escapeElement + # + # print CGI.unescapeElement( + # CGI.escapeHTML('<BR><A HREF="url"></A>'), "A", "IMG") + # # "<BR><A HREF="url"></A>" + # + # print CGI.unescapeElement( + # CGI.escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"]) + # # "<BR><A HREF="url"></A>" + def unescapeElement(string, *elements) + elements = elements[0] if elements[0].kind_of?(Array) + unless elements.empty? + string.gsub(/<\/?(?:#{elements.join("|")})\b(?>[^&]+|&(?![gl]t;)\w+;)*(?:>)?/im) do + unescapeHTML($&) + end + else + string + end + end + + alias escape_element escapeElement + + alias unescape_element unescapeElement + +end diff --git a/lib/cgi/session.rb b/lib/cgi/session.rb deleted file mode 100644 index 8c4417c490..0000000000 --- a/lib/cgi/session.rb +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright (C) 2001 Yukihiro "Matz" Matsumoto -# Copyright (C) 2000 Network Applied Communication Laboratory, Inc. -# Copyright (C) 2000 Information-technology Promotion Agency, Japan - -require 'cgi' -require 'final' - -class CGI - class Session - - attr_reader :session_id - - def Session::callback(dbman) - lambda{ - dbman.close - } - end - - def Session::create_new_id - require 'md5' - md5 = MD5::new - md5.update(String(Time::now)) - md5.update(String(rand(0))) - md5.update(String($$)) - md5.update('foobar') - md5.hexdigest[0,16] - end - private :create_new_id - - def initialize(request, option={}) - session_key = option['session_key'] || '_session_id' - id, = option['session_id'] - unless id - if option['new_session'] - id = Session::create_new_id - end - end - unless id - id, = request[session_key] - unless id - id, = request.cookies[session_key] - end - unless id - if option.key?('new_session') and not option['new_session'] - raise ArgumentError, "session_key `%s' should be supplied"%session_key - end - id = Session::create_new_id - end - end - @session_id = id - dbman = option['database_manager'] || FileStore - @dbman = dbman::new(self, option) - request.instance_eval do - @output_hidden = {session_key => id} - @output_cookies = [ - Cookie::new("name" => session_key, - "value" => id, - "path" => if option['session_path'] then - option['session_path'] - elsif ENV["SCRIPT_NAME"] then - File::dirname(ENV["SCRIPT_NAME"]) - else - "" - end) - ] - end - ObjectSpace::define_finalizer(self, Session::callback(@dbman)) - end - - def [](key) - unless @data - @data = @dbman.restore - end - @data[key] - end - - def []=(key, val) - unless @write_lock - @write_lock = true - end - unless @data - @data = @dbman.restore - end - @data[key] = String(val) - end - - def update - @dbman.update - end - - def close - @dbman.close - end - - def delete - @dbman.delete - end - - class FileStore - def initialize(session, option={}) - dir = option['tmpdir'] || ENV['TMP'] || '/tmp' - prefix = option['prefix'] || '' - path = dir+"/"+prefix+session.session_id - path.untaint - unless File::exist? path - @hash = {} - end - begin - @f = open(path, "r+") - rescue Errno::ENOENT - @f = open(path, "w+") - end - end - - def restore - unless @hash - @hash = {} - @f.flock File::LOCK_EX - @f.rewind - for line in @f - line.chomp! - k, v = line.split('=',2) - @hash[CGI::unescape(k)] = CGI::unescape(v) - end - end - @hash - end - - def update - @f.rewind - for k,v in @hash - @f.printf "%s=%s\n", CGI::escape(k), CGI::escape(v) - end - @f.truncate @f.tell - end - - def close - return unless @f.closed? - update - @f.close - end - - def delete - path = @f.path - return unless @f.closed? - @f.close - File::unlink path - end - end - - class MemoryStore - GLOBAL_HASH_TABLE = {} - - def initialize(session, option={}) - @session_id = session.session_id - GLOBAL_HASH_TABLE[@session_id] = {} - end - - def restore - GLOBAL_HASH_TABLE[@session_id] - end - - def update - # don't need to update; hash is shared - end - - def close - # don't need to close - end - - def delete - GLOBAL_HASH_TABLE[@session_id] = nil - end - end - end -end diff --git a/lib/cgi/util.rb b/lib/cgi/util.rb new file mode 100644 index 0000000000..50a2e91665 --- /dev/null +++ b/lib/cgi/util.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "cgi/escape" +warn <<-WARNING, uplevel: Gem::BUNDLED_GEMS.uplevel if $VERBOSE +CGI::Util is removed from Ruby 4.0. Please use cgi/escape instead for CGI.escape and CGI.unescape features. +If you are using CGI.parse, please install and use the cgi gem instead. +WARNING |
