diff options
| author | Hiroshi SHIBATA <hsbt@ruby-lang.org> | 2025-05-08 19:21:47 +0900 |
|---|---|---|
| committer | Hiroshi SHIBATA <hsbt@ruby-lang.org> | 2025-05-09 14:27:28 +0900 |
| commit | 600c616507b258cdf9dbfbc822deb267f3202325 (patch) | |
| tree | aff8f5b0847a0301391f2dc5463049b0d7cd4e33 /lib | |
| parent | a61f51f66d7dff966dd17915c854fea08763722d (diff) | |
Removed CGI library without CGI::Escape features
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/13275
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/cgi.rb | 298 | ||||
| -rw-r--r-- | lib/cgi/cgi.gemspec | 43 | ||||
| -rw-r--r-- | lib/cgi/cookie.rb | 210 | ||||
| -rw-r--r-- | lib/cgi/core.rb | 900 | ||||
| -rw-r--r-- | lib/cgi/html.rb | 1035 | ||||
| -rw-r--r-- | lib/cgi/session.rb | 562 | ||||
| -rw-r--r-- | lib/cgi/session/pstore.rb | 91 | ||||
| -rw-r--r-- | lib/cgi/util.rb | 44 |
8 files changed, 0 insertions, 3183 deletions
diff --git a/lib/cgi.rb b/lib/cgi.rb deleted file mode 100644 index d6f3ed5b49..0000000000 --- a/lib/cgi.rb +++ /dev/null @@ -1,298 +0,0 @@ -# frozen_string_literal: true -# -# cgi.rb - cgi support library -# -# Copyright (C) 2000 Network Applied Communication Laboratory, Inc. -# -# Copyright (C) 2000 Information-technology Promotion Agency, Japan -# -# Author: Wakou Aoyama <wakou@ruby-lang.org> -# -# Documentation: Wakou Aoyama (RDoc'd and embellished by William Webber) -# - -# == Overview -# -# The Common Gateway Interface (CGI) is a simple protocol for passing an HTTP -# request from a web server to a standalone program, and returning the output -# to the web browser. Basically, a CGI program is called with the parameters -# of the request passed in either in the environment (GET) or via $stdin -# (POST), and everything it prints to $stdout is returned to the client. -# -# This file holds the CGI class. This class provides functionality for -# retrieving HTTP request parameters, managing cookies, and generating HTML -# output. -# -# The file CGI::Session provides session management functionality; see that -# class for more details. -# -# See http://www.w3.org/CGI/ for more information on the CGI protocol. -# -# == Introduction -# -# CGI is a large class, providing several categories of methods, many of which -# are mixed in from other modules. Some of the documentation is in this class, -# some in the modules CGI::QueryExtension and CGI::HtmlExtension. See -# CGI::Cookie for specific information on handling cookies, and cgi/session.rb -# (CGI::Session) for information on sessions. -# -# For queries, CGI provides methods to get at environmental variables, -# parameters, cookies, and multipart request data. For responses, CGI provides -# methods for writing output and generating HTML. -# -# Read on for more details. Examples are provided at the bottom. -# -# == Queries -# -# The CGI class dynamically mixes in parameter and cookie-parsing -# functionality, environmental variable access, and support for -# parsing multipart requests (including uploaded files) from the -# CGI::QueryExtension module. -# -# === Environmental Variables -# -# The standard CGI environmental variables are available as read-only -# attributes of a CGI object. The following is a list of these variables: -# -# -# AUTH_TYPE HTTP_HOST REMOTE_IDENT -# CONTENT_LENGTH HTTP_NEGOTIATE REMOTE_USER -# CONTENT_TYPE HTTP_PRAGMA REQUEST_METHOD -# GATEWAY_INTERFACE HTTP_REFERER SCRIPT_NAME -# HTTP_ACCEPT HTTP_USER_AGENT SERVER_NAME -# HTTP_ACCEPT_CHARSET PATH_INFO SERVER_PORT -# HTTP_ACCEPT_ENCODING PATH_TRANSLATED SERVER_PROTOCOL -# HTTP_ACCEPT_LANGUAGE QUERY_STRING SERVER_SOFTWARE -# HTTP_CACHE_CONTROL REMOTE_ADDR -# HTTP_FROM REMOTE_HOST -# -# -# For each of these variables, there is a corresponding attribute with the -# same name, except all lower case and without a preceding HTTP_. -# +content_length+ and +server_port+ are integers; the rest are strings. -# -# === Parameters -# -# The method #params() returns a hash of all parameters in the request as -# name/value-list pairs, where the value-list is an Array of one or more -# values. The CGI object itself also behaves as a hash of parameter names -# to values, but only returns a single value (as a String) for each -# parameter name. -# -# For instance, suppose the request contains the parameter -# "favourite_colours" with the multiple values "blue" and "green". The -# following behavior would occur: -# -# cgi.params["favourite_colours"] # => ["blue", "green"] -# cgi["favourite_colours"] # => "blue" -# -# If a parameter does not exist, the former method will return an empty -# array, the latter an empty string. The simplest way to test for existence -# of a parameter is by the #has_key? method. -# -# === Cookies -# -# HTTP Cookies are automatically parsed from the request. They are available -# from the #cookies() accessor, which returns a hash from cookie name to -# CGI::Cookie object. -# -# === Multipart requests -# -# If a request's method is POST and its content type is multipart/form-data, -# then it may contain uploaded files. These are stored by the QueryExtension -# module in the parameters of the request. The parameter name is the name -# attribute of the file input field, as usual. However, the value is not -# a string, but an IO object, either an IOString for small files, or a -# Tempfile for larger ones. This object also has the additional singleton -# methods: -# -# #local_path():: the path of the uploaded file on the local filesystem -# #original_filename():: the name of the file on the client computer -# #content_type():: the content type of the file -# -# == Responses -# -# The CGI class provides methods for sending header and content output to -# the HTTP client, and mixes in methods for programmatic HTML generation -# from CGI::HtmlExtension and CGI::TagMaker modules. The precise version of HTML -# to use for HTML generation is specified at object creation time. -# -# === Writing output -# -# The simplest way to send output to the HTTP client is using the #out() method. -# This takes the HTTP headers as a hash parameter, and the body content -# via a block. The headers can be generated as a string using the #http_header() -# method. The output stream can be written directly to using the #print() -# method. -# -# === Generating HTML -# -# Each HTML element has a corresponding method for generating that -# element as a String. The name of this method is the same as that -# of the element, all lowercase. The attributes of the element are -# passed in as a hash, and the body as a no-argument block that evaluates -# to a String. The HTML generation module knows which elements are -# always empty, and silently drops any passed-in body. It also knows -# which elements require matching closing tags and which don't. However, -# it does not know what attributes are legal for which elements. -# -# There are also some additional HTML generation methods mixed in from -# the CGI::HtmlExtension module. These include individual methods for the -# different types of form inputs, and methods for elements that commonly -# take particular attributes where the attributes can be directly specified -# as arguments, rather than via a hash. -# -# === Utility HTML escape and other methods like a function. -# -# There are some utility tool defined in cgi/util.rb . -# And when include, you can use utility methods like a function. -# -# == Examples of use -# -# === Get form values -# -# require "cgi" -# cgi = CGI.new -# value = cgi['field_name'] # <== value string for 'field_name' -# # if not 'field_name' included, then return "". -# fields = cgi.keys # <== array of field names -# -# # returns true if form has 'field_name' -# cgi.has_key?('field_name') -# cgi.has_key?('field_name') -# cgi.include?('field_name') -# -# CAUTION! <code>cgi['field_name']</code> returned an Array with the old -# cgi.rb(included in Ruby 1.6) -# -# === Get form values as hash -# -# require "cgi" -# cgi = CGI.new -# params = cgi.params -# -# cgi.params is a hash. -# -# cgi.params['new_field_name'] = ["value"] # add new param -# cgi.params['field_name'] = ["new_value"] # change value -# cgi.params.delete('field_name') # delete param -# cgi.params.clear # delete all params -# -# -# === Save form values to file -# -# require "pstore" -# db = PStore.new("query.db") -# db.transaction do -# db["params"] = cgi.params -# end -# -# -# === Restore form values from file -# -# require "pstore" -# db = PStore.new("query.db") -# db.transaction do -# cgi.params = db["params"] -# end -# -# -# === Get multipart form values -# -# require "cgi" -# cgi = CGI.new -# value = cgi['field_name'] # <== value string for 'field_name' -# value.read # <== body of value -# value.local_path # <== path to local file of value -# value.original_filename # <== original filename of value -# value.content_type # <== content_type of value -# -# and value has StringIO or Tempfile class methods. -# -# === Get cookie values -# -# require "cgi" -# cgi = CGI.new -# values = cgi.cookies['name'] # <== array of 'name' -# # if not 'name' included, then return []. -# names = cgi.cookies.keys # <== array of cookie names -# -# and cgi.cookies is a hash. -# -# === Get cookie objects -# -# require "cgi" -# cgi = CGI.new -# for name, cookie in cgi.cookies -# cookie.expires = Time.now + 30 -# end -# cgi.out("cookie" => cgi.cookies) {"string"} -# -# cgi.cookies # { "name1" => cookie1, "name2" => cookie2, ... } -# -# require "cgi" -# cgi = CGI.new -# cgi.cookies['name'].expires = Time.now + 30 -# cgi.out("cookie" => cgi.cookies['name']) {"string"} -# -# === Print http header and html string to $DEFAULT_OUTPUT ($>) -# -# require "cgi" -# cgi = CGI.new("html4") # add HTML generation methods -# cgi.out do -# cgi.html do -# cgi.head do -# cgi.title { "TITLE" } -# end + -# cgi.body do -# cgi.form("ACTION" => "uri") do -# cgi.p do -# cgi.textarea("get_text") + -# cgi.br + -# cgi.submit -# end -# end + -# cgi.pre do -# CGI.escapeHTML( -# "params: #{cgi.params.inspect}\n" + -# "cookies: #{cgi.cookies.inspect}\n" + -# ENV.collect do |key, value| -# "#{key} --> #{value}\n" -# end.join("") -# ) -# end -# end -# end -# end -# -# # add HTML generation methods -# CGI.new("html3") # html3.2 -# CGI.new("html4") # html4.01 (Strict) -# CGI.new("html4Tr") # html4.01 Transitional -# CGI.new("html4Fr") # html4.01 Frameset -# CGI.new("html5") # html5 -# -# === Some utility methods -# -# require 'cgi/util' -# CGI.escapeHTML('Usage: foo "bar" <baz>') -# -# -# === Some utility methods like a function -# -# require 'cgi/util' -# include CGI::Util -# escapeHTML('Usage: foo "bar" <baz>') -# h('Usage: foo "bar" <baz>') # alias -# -# - -class CGI - VERSION = "0.4.2" -end - -require 'cgi/core' -require 'cgi/cookie' -require 'cgi/util' -require 'cgi/escape' -CGI.autoload(:HtmlExtension, 'cgi/html') diff --git a/lib/cgi/cgi.gemspec b/lib/cgi/cgi.gemspec deleted file mode 100644 index 5ef00d591d..0000000000 --- a/lib/cgi/cgi.gemspec +++ /dev/null @@ -1,43 +0,0 @@ -# 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 = [ - "COPYING", - "BSDL", - "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 deleted file mode 100644 index 1c4ef6a600..0000000000 --- a/lib/cgi/cookie.rb +++ /dev/null @@ -1,210 +0,0 @@ -# frozen_string_literal: true -require_relative 'util' -class CGI - # 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 - # '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 - # 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.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. - # - # :call-seq: - # Cookie.new(name_string,*value) - # Cookie.new(options_hash) - # - # +name_string+:: - # The name of the cookie; in this form, there is no #domain or - # #expiration. The #path is gleaned from the +SCRIPT_NAME+ environment - # variable, and #secure is false. - # <tt>*value</tt>:: - # value or list of values of the cookie - # +options_hash+:: - # A Hash of options to initialize this Cookie. Possible options are: - # - # 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 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) - self.name = name - self.path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") - @secure = false - @httponly = false - return super(value) - end - - options = name - unless options.has_key?("name") - raise ArgumentError, "`name' required" - end - - self.name = options["name"] - value = Array(options["value"]) - # simple support for IE - 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 - - super(value) - end - - # Name of this cookie, as a +String+ - 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_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_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 - # True if this cookie is httponly; false otherwise - attr_reader :httponly - - # Returns the value or list of values for this cookie. - def value - self - end - - # Replaces the value of this cookie with a new value or list of values. - def value=(val) - replace(Array(val)) - end - - # 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 - - # 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}".dup - buf << "; domain=#{@domain}" if @domain - buf << "; path=#{@path}" if @path - buf << "; expires=#{CGI.rfc1123_date(@expires)}" if @expires - buf << "; secure" if @secure - buf << "; HttpOnly" if @httponly - buf - end - - # 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 - - 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 - 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 deleted file mode 100644 index e6c19bbaa5..0000000000 --- a/lib/cgi/core.rb +++ /dev/null @@ -1,900 +0,0 @@ -# frozen_string_literal: true -#-- -# Methods for generating HTML, parsing CGI-related parameters, and -# generating HTTP responses. -#++ -class CGI - unless const_defined?(:Escape) - module Escape - @@accept_charset = "UTF-8" # :nodoc: - end - include Escape - extend Escape - end - - $CGI_ENV = ENV # for FCGI support - - # String for carriage return - CR = "\015" - - # String for linefeed - LF = "\012" - - # Standard internet newline sequence - EOL = CR + LF - - REVISION = '$Id$' #:nodoc: - - # Whether processing will be required in binary vs text - NEEDS_BINMODE = File::BINARY != 0 - - # 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 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" - } - - # :startdoc: - - # Synonym for ENV. - def env_table - ENV - end - - # Synonym for $stdin. - def stdinput - $stdin - end - - # Synonym for $stdout. - def stdoutput - $stdout - end - - private :env_table, :stdinput, :stdoutput - - # Create an HTTP header block as a string. - # - # :call-seq: - # http_header(content_type_string="text/html") - # http_header(headers_hash) - # - # Includes the empty line that ends the header block. - # - # +content_type_string+:: - # If this form is used, this string is the <tt>Content-Type</tt> - # +headers_hash+:: - # A Hash of header values. 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 as a String, returned as the Status header. The - # values 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 - # - # 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 headers can also be set; they are appended as key: value. - # - # Examples: - # - # http_header - # # Content-Type: text/html - # - # http_header("text/plain") - # # Content-Type: text/plain - # - # 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 http_header(options='text/html') - if options.is_a?(String) - content_type = options - buf = _header_for_string(content_type) - elsif options.is_a?(Hash) - if options.size == 1 && options.has_key?('type') - content_type = options['type'] - buf = _header_for_string(content_type) - else - buf = _header_for_hash(options.dup) - end - else - raise ArgumentError.new("expected String or Hash but got #{options.class}") - end - if defined?(MOD_RUBY) - _header_for_modruby(buf) - return '' - else - buf << EOL # empty line of separator - return buf - end - 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 = ''.dup - if nph?() - buf << "#{_no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'} 200 OK#{EOL}" - buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}" - buf << "Server: #{_no_crlf_check($CGI_ENV['SERVER_SOFTWARE'])}#{EOL}" - buf << "Connection: close#{EOL}" - end - buf << "Content-Type: #{_no_crlf_check(content_type)}#{EOL}" - if @output_cookies - @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 = ''.dup - ## add charset to option['type'] - options['type'] ||= 'text/html' - charset = options.delete('charset') - options['type'] += "; charset=#{charset}" if charset - ## NPH - options.delete('nph') if defined?(MOD_RUBY) - if options.delete('nph') || nph?() - protocol = _no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0' - status = options.delete('status') - 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'] || '' - options['connection'] ||= 'close' - end - ## common headers - status = options.delete('status') - buf << "Status: #{HTTP_STATUS[status] || _no_crlf_check(status)}#{EOL}" if status - server = options.delete('server') - buf << "Server: #{_no_crlf_check(server)}#{EOL}" if server - connection = options.delete('connection') - buf << "Connection: #{_no_crlf_check(connection)}#{EOL}" if connection - type = options.delete('type') - buf << "Content-Type: #{_no_crlf_check(type)}#{EOL}" #if type - length = options.delete('length') - buf << "Content-Length: #{_no_crlf_check(length)}#{EOL}" if length - language = options.delete('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: #{_no_crlf_check(cookie)}#{EOL}" - when Array - arr = cookie - arr.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } - when Hash - hash = cookie - hash.each_value {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } - end - end - if @output_cookies - @output_cookies.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } - end - ## other headers - options.each do |key, value| - 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+)/ =~ $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| - $stderr.printf("name:%s value:%s\n", name, value) if $DEBUG - case name - when 'Set-Cookie' - request.headers_out.add(name, value) - when /^status$/i - request.status_line = value - request.status = value.to_i - when /^content-type$/i - request.content_type = value - when /^content-encoding$/i - request.content_encoding = value - when /^location$/i - request.status = 302 if request.status == 200 - request.headers_out[name] = value - else - request.headers_out[name] = value - end - end - request.send_http_header - return '' - end - private :_header_for_modruby - - # Print an HTTP header and body to $DEFAULT_OUTPUT ($>) - # - # :call-seq: - # cgi.out(content_type_string='text/html') - # cgi.out(headers_hash) - # - # +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 #http_header. - # +block+:: - # A block is required and should evaluate to the body of the response. - # - # <tt>Content-Length</tt> is automatically calculated from the size of - # the String returned by the content block. - # - # If <tt>ENV['REQUEST_METHOD'] == "HEAD"</tt>, then only the header - # is output (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". - # - # Example: - # - # 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" } - # # HTTP/1.1 200 OK - # # Date: Sun, 15 May 2011 17:35:54 GMT - # # Server: Apache 2.2.0 - # # Connection: close - # # Content-Type: text/html; charset=iso-2022-jp - # # Content-Length: 6 - # # Content-Language: ja - # # Expires: Tue, 14 Jun 2011 17:35:54 GMT - # # Set-Cookie: foo - # # Set-Cookie: bar - # # my_header1: my_value - # # my_header2: my_value - # # - # # string - 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 http_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 self.parse(query) - params = {} - query.split(/[&;]/).each do |pairs| - key, value = pairs.split('=',2).collect{|v| CGI.unescape(v) } - - next unless key - - params[key] ||= [] - params[key].push(value) if value - end - - params.default=[].freeze - params - end - - # Maximum content length of post data - ##MAX_CONTENT_LENGTH = 2 * 1024 * 1024 - - # Maximum number of request parameters when multipart - MAX_MULTIPART_COUNT = 128 - - # Mixin module that provides the following: - # - # 1. Access to the CGI environment variables as methods. See - # documentation to the CGI class for a list of these variables. The - # methods are exposed by removing the leading +HTTP_+ (if it exists) and - # downcasing the name. For example, +auth_type+ will return the - # environment variable +AUTH_TYPE+, and +accept+ will return the value - # for +HTTP_ACCEPT+. - # - # 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 initializing 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.delete_prefix('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.delete_prefix('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 - - # Get the uploaded files as a hash of name=>values pairs - attr_reader :files - - # Set all the parameters. - def params=(hash) - @params.clear - @params.update(hash) - end - - ## - # Parses multipart form elements according to - # http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 - # - # Returns a hash of multipart form parameters with bodies of type StringIO or - # Tempfile depending on whether the multipart form element exceeds 10 KB - # - # params[name => body] - # - def read_multipart(boundary, content_length) - ## read first boundary - stdin = stdinput - first_line = "--#{boundary}#{EOL}" - content_length -= first_line.bytesize - status = stdin.read(first_line.bytesize) - raise EOFError.new("no content body") unless status - raise EOFError.new("bad content body") unless first_line == status - ## parse and set params - params = {} - @files = {} - boundary_rexp = /--#{Regexp.quote(boundary)}(#{EOL}|--)/ - boundary_size = "#{EOL}--#{boundary}#{EOL}".bytesize - 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 - else - def local_path - nil - end - end - attr_reader :original_filename, :content_type - end - ## find head and boundary - head = nil - separator = EOL * 2 - until head && matched = boundary_rexp.match(buf) - if !head && pos = buf.index(separator) - len = pos + EOL.bytesize - head = buf[0, len] - buf = buf[(pos+separator.bytesize)..-1] - else - if head && buf.size > boundary_size - len = buf.size - boundary_size - body.print(buf[0, len]) - buf[0, len] = '' - end - c = stdin.read(bufsize < content_length ? bufsize : content_length) - raise EOFError.new("bad content body") if c.nil? || c.empty? - buf << c - content_length -= c.bytesize - end - end - ## read to end of boundary - m = matched - len = m.begin(0) - s = buf[0, len] - if s =~ /(\r?\n)\z/ - s = buf[0, len - $1.bytesize] - end - body.print(s) - buf = buf[m.end(0)..-1] - boundary_end = m[1] - content_length = -1 if boundary_end == '--' - ## reset file cursor position - body.rewind - ## original filename - /Content-Disposition:.* filename=(?:"(.*?)"|([^;\r\n]*))/i.match(head) - filename = $1 || $2 || ''.dup - filename = CGI.unescape(filename) if unescape_filename?() - body.instance_variable_set(:@original_filename, filename) - ## content type - /Content-Type: (.*)/i.match(head) - (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 - @accept_charset_error_block.call(name,value) - else - raise InvalidEncoding,"Accept-Charset encoding error" - end - end - class << params[name].last;self;end.class_eval do - define_method(:read){self} - define_method(:original_filename){""} - define_method(:content_type){""} - end - else - (params[name] ||= []) << body - @files[name]=body - end - ## break loop - break if content_length == -1 - end - 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: Encoding::ASCII_8BIT) - else - begin - require 'stringio' - body = StringIO.new("".b) - rescue LoadError - require 'tempfile' - body = Tempfile.new('CGI', encoding: Encoding::ASCII_8BIT) - end - end - body.binmode if defined? body.binmode - return body - 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 - - # 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 - array = readlines rescue nil - if not array.nil? - array.join(' ').gsub(/\n/n, '') - else - "" - end - end.gsub(/\\=/n, '%3D').gsub(/\\&/n, '%26') - - words = Shellwords.shellwords(string) - - if words.find{|x| /=/n.match(x) } - words.join('&') - else - words.join('+') - end - end - private :read_from_cmdline - - # A wrapper class to use a StringIO object as the body and switch - # to a TempFile when the passed threshold is passed. - # 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=\"?([^\";,]+)\"?| =~ 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( - 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.dup.force_encoding(@accept_charset) - ) - unless Encoding.find(@accept_charset) == Encoding::ASCII_8BIT - @params.each do |key,values| - values.each do |value| - unless value.valid_encoding? - if @accept_charset_error_block - @accept_charset_error_block.call(key,value) - else - raise InvalidEncoding,"Accept-Charset encoding error" - end - end - end - end - end - end - - @cookies = CGI::Cookie.parse((env_table['HTTP_COOKIE'] or env_table['COOKIE'])) - end - private :initialize_query - - # Returns whether the form contained multipart/form-data - 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("".b) - else - Tempfile.new("CGI",encoding: Encoding::ASCII_8BIT) - end - else - str = if value then value.dup else "" end - str - end - end - - # Return all query parameter names as an array of String. - def keys(*args) - @params.keys(*args) - end - - # Returns true if a given query string parameter exists. - def has_key?(*args) - @params.has_key?(*args) - end - alias key? has_key? - alias include? has_key? - - end # QueryExtension - - # Exception raised when there is an invalid encoding detected - class InvalidEncoding < Exception; end - - # @@accept_charset is default accept character set. - # This default value default is "UTF-8" - # If you want to change the default accept character set - # when create a new CGI instance, set this: - # - # CGI.accept_charset = "EUC-JP" - # - @@accept_charset="UTF-8" if false # needed for rdoc? - - # Return the accept character set for all new CGI instances. - def self.accept_charset - @@accept_charset - end - - # Set the accept character set for all new CGI instances. - def self.accept_charset=(accept_charset) - @@accept_charset=accept_charset - end - - # 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: - # CGI.new(tag_maker) { block } - # CGI.new(options_hash = {}) { block } - # - # - # <tt>tag_maker</tt>:: - # This is the same as using the +options_hash+ form with the value <tt>{ - # :tag_maker => tag_maker }</tt> Note that it is recommended to use the - # +options_hash+ form, since it also allows you specify the charset you - # will accept. - # <tt>options_hash</tt>:: - # A Hash that recognizes three options: - # - # <tt>:accept_charset</tt>:: - # specifies encoding of received query string. If omitted, - # <tt>@@accept_charset</tt> is used. If the encoding is not valid, a - # CGI::InvalidEncoding will be raised. - # - # Example. Suppose <tt>@@accept_charset</tt> is "UTF-8" - # - # when not specified: - # - # cgi=CGI.new # @accept_charset # => "UTF-8" - # - # when specified as "EUC-JP": - # - # cgi=CGI.new(:accept_charset => "EUC-JP") # => "EUC-JP" - # - # <tt>:tag_maker</tt>:: - # String that specifies which version of the HTML generation methods to - # use. If not specified, no HTML generation methods will be loaded. - # - # The following values are supported: - # - # "html3":: HTML 3.x - # "html4":: HTML 4.0 - # "html4Tr":: HTML 4.0 Transitional - # "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: - # - # encoding_errors={} - # cgi=CGI.new(:accept_charset=>"EUC-JP") do |name,value| - # encoding_errors[name] = value - # end - # - # Finally, 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(options = {}, &block) # :yields: name, value - @accept_charset_error_block = block_given? ? block : nil - @options={ - :accept_charset=>@@accept_charset, - :max_multipart_length=>@@max_multipart_length - } - case options - when Hash - @options.merge!(options) - when String - @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 - - extend QueryExtension - @multipart = false - - initialize_query() # set @params, @cookies - @output_cookies = nil - @output_hidden = nil - - case @options[:tag_maker] - when "html3" - require_relative 'html' - extend Html3 - extend HtmlExtension - when "html4" - require_relative 'html' - extend Html4 - extend HtmlExtension - when "html4Tr" - require_relative 'html' - extend Html4Tr - extend HtmlExtension - when "html4Fr" - require_relative 'html' - extend Html4Tr - extend Html4Fr - extend HtmlExtension - when "html5" - require_relative 'html' - extend Html5 - extend HtmlExtension - end - end - -end # class CGI diff --git a/lib/cgi/html.rb b/lib/cgi/html.rb deleted file mode 100644 index 1543943320..0000000000 --- a/lib/cgi/html.rb +++ /dev/null @@ -1,1035 +0,0 @@ -# frozen_string_literal: true -class CGI - # Base module for HTML-generation mixins. - # - # Provides methods for code generation for tags following - # the various DTD element types. - module TagMaker # :nodoc: - - # Generate code for an element with required start and end tags. - # - # - - - def nn_element(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(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(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, - # cgi.a("http://www.example.com") { "Example" } - # # => "<A HREF=\"http://www.example.com\">Example</A>" - # - # Modules Html3, Html4, etc., contain more basic HTML-generation methods - # (+#title+, +#h1+, etc.). - # - # See class CGI for a detailed example. - # - module HtmlExtension - - - # Generate an Anchor element as a string. - # - # +href+ can either be a string, giving the URL - # for the HREF attribute, or it can be a hash of - # the element's attributes. - # - # The body of the element is the string returned by the no-argument - # block passed in. - # - # a("http://www.example.com") { "Example" } - # # => "<A HREF=\"http://www.example.com\">Example</A>" - # - # a("HREF" => "http://www.example.com", "TARGET" => "_top") { "Example" } - # # => "<A HREF=\"http://www.example.com\" TARGET=\"_top\">Example</A>" - # - def a(href = "") # :yield: - attributes = if href.kind_of?(String) - { "HREF" => href } - else - href - end - super(attributes) - end - - # Generate a Document Base URI element as a String. - # - # +href+ can either by a string, giving the base URL for the HREF - # attribute, or it can be a has of the element's attributes. - # - # The passed-in no-argument block is ignored. - # - # base("http://www.example.com/cgi") - # # => "<BASE HREF=\"http://www.example.com/cgi\">" - def base(href = "") # :yield: - attributes = if href.kind_of?(String) - { "HREF" => href } - else - href - end - super(attributes) - end - - # Generate a BlockQuote element as a string. - # - # +cite+ can either be a string, give the URI for the source of - # the quoted text, or a hash, giving all attributes of the element, - # or it can be omitted, in which case the element has no attributes. - # - # The body is provided by the passed-in no-argument block - # - # blockquote("http://www.example.com/quotes/foo.html") { "Foo!" } - # #=> "<BLOCKQUOTE CITE=\"http://www.example.com/quotes/foo.html\">Foo!</BLOCKQUOTE> - def blockquote(cite = {}) # :yield: - attributes = if cite.kind_of?(String) - { "CITE" => cite } - else - cite - end - super(attributes) - end - - - # Generate a Table Caption element as a string. - # - # +align+ can be a string, giving the alignment of the caption - # (one of top, bottom, left, or right). It can be a hash of - # all the attributes of the element. Or it can be omitted. - # - # The body of the element is provided by the passed-in no-argument block. - # - # caption("left") { "Capital Cities" } - # # => <CAPTION ALIGN=\"left\">Capital Cities</CAPTION> - def caption(align = {}) # :yield: - attributes = if align.kind_of?(String) - { "ALIGN" => align } - else - align - end - super(attributes) - end - - - # Generate a Checkbox Input element as a string. - # - # The attributes of the element can be specified as three arguments, - # +name+, +value+, and +checked+. +checked+ is a boolean value; - # if true, the CHECKED attribute will be included in the element. - # - # Alternatively, the attributes can be specified as a hash. - # - # checkbox("name") - # # = checkbox("NAME" => "name") - # - # checkbox("name", "value") - # # = checkbox("NAME" => "name", "VALUE" => "value") - # - # checkbox("name", "value", true) - # # = checkbox("NAME" => "name", "VALUE" => "value", "CHECKED" => true) - def checkbox(name = "", value = nil, checked = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "checkbox", "NAME" => name, - "VALUE" => value, "CHECKED" => checked } - else - name["TYPE"] = "checkbox" - name - end - input(attributes) - end - - # Generate a sequence of checkbox elements, as a String. - # - # The checkboxes will all have the same +name+ attribute. - # Each checkbox is followed by a label. - # There will be one checkbox for each value. Each value - # can be specified as a String, which will be used both - # as the value of the VALUE attribute and as the label - # for that checkbox. A single-element array has the - # same effect. - # - # Each value can also be specified as a three-element array. - # The first element is the VALUE attribute; the second is the - # label; and the third is a boolean specifying whether this - # checkbox is CHECKED. - # - # Each value can also be specified as a two-element - # array, by omitting either the value element (defaults - # to the same as the label), or the boolean checked element - # (defaults to false). - # - # checkbox_group("name", "foo", "bar", "baz") - # # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo - # # <INPUT TYPE="checkbox" NAME="name" VALUE="bar">bar - # # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz - # - # checkbox_group("name", ["foo"], ["bar", true], "baz") - # # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo - # # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="bar">bar - # # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz - # - # checkbox_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz") - # # <INPUT TYPE="checkbox" NAME="name" VALUE="1">Foo - # # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="2">Bar - # # <INPUT TYPE="checkbox" NAME="name" VALUE="Baz">Baz - # - # checkbox_group("NAME" => "name", - # "VALUES" => ["foo", "bar", "baz"]) - # - # checkbox_group("NAME" => "name", - # "VALUES" => [["foo"], ["bar", true], "baz"]) - # - # checkbox_group("NAME" => "name", - # "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"]) - def checkbox_group(name = "", *values) - if name.kind_of?(Hash) - values = name["VALUES"] - name = name["NAME"] - end - values.collect{|value| - if value.kind_of?(String) - checkbox(name, value) + value - else - if value[-1] == true || value[-1] == false - checkbox(name, value[0], value[-1]) + - value[-2] - else - checkbox(name, value[0]) + - value[-1] - end - end - }.join - end - - - # Generate an File Upload Input element as a string. - # - # The attributes of the element can be specified as three arguments, - # +name+, +size+, and +maxlength+. +maxlength+ is the maximum length - # of the file's _name_, not of the file's _contents_. - # - # Alternatively, the attributes can be specified as a hash. - # - # See #multipart_form() for forms that include file uploads. - # - # file_field("name") - # # <INPUT TYPE="file" NAME="name" SIZE="20"> - # - # file_field("name", 40) - # # <INPUT TYPE="file" NAME="name" SIZE="40"> - # - # file_field("name", 40, 100) - # # <INPUT TYPE="file" NAME="name" SIZE="40" MAXLENGTH="100"> - # - # file_field("NAME" => "name", "SIZE" => 40) - # # <INPUT TYPE="file" NAME="name" SIZE="40"> - def file_field(name = "", size = 20, maxlength = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "file", "NAME" => name, - "SIZE" => size.to_s } - else - name["TYPE"] = "file" - name - end - attributes["MAXLENGTH"] = maxlength.to_s if maxlength - input(attributes) - end - - - # Generate a Form element as a string. - # - # +method+ should be either "get" or "post", and defaults to the latter. - # +action+ defaults to the current CGI script name. +enctype+ - # defaults to "application/x-www-form-urlencoded". - # - # Alternatively, the attributes can be specified as a hash. - # - # See also #multipart_form() for forms that include file uploads. - # - # form{ "string" } - # # <FORM METHOD="post" ENCTYPE="application/x-www-form-urlencoded">string</FORM> - # - # form("get") { "string" } - # # <FORM METHOD="get" ENCTYPE="application/x-www-form-urlencoded">string</FORM> - # - # form("get", "url") { "string" } - # # <FORM METHOD="get" ACTION="url" ENCTYPE="application/x-www-form-urlencoded">string</FORM> - # - # form("METHOD" => "post", "ENCTYPE" => "enctype") { "string" } - # # <FORM METHOD="post" ENCTYPE="enctype">string</FORM> - def form(method = "post", action = script_name, enctype = "application/x-www-form-urlencoded") - attributes = if method.kind_of?(String) - { "METHOD" => method, "ACTION" => action, - "ENCTYPE" => enctype } - else - unless method.has_key?("METHOD") - method["METHOD"] = "post" - end - unless method.has_key?("ENCTYPE") - method["ENCTYPE"] = enctype - end - method - end - if block_given? - body = yield - else - body = "" - end - if @output_hidden - body << @output_hidden.collect{|k,v| - "<INPUT TYPE=\"HIDDEN\" NAME=\"#{k}\" VALUE=\"#{v}\">" - }.join - end - super(attributes){body} - end - - # Generate a Hidden Input element as a string. - # - # The attributes of the element can be specified as two arguments, - # +name+ and +value+. - # - # Alternatively, the attributes can be specified as a hash. - # - # hidden("name") - # # <INPUT TYPE="hidden" NAME="name"> - # - # hidden("name", "value") - # # <INPUT TYPE="hidden" NAME="name" VALUE="value"> - # - # hidden("NAME" => "name", "VALUE" => "reset", "ID" => "foo") - # # <INPUT TYPE="hidden" NAME="name" VALUE="value" ID="foo"> - def hidden(name = "", value = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "hidden", "NAME" => name, "VALUE" => value } - else - name["TYPE"] = "hidden" - name - end - input(attributes) - end - - # Generate a top-level HTML element as a string. - # - # The attributes of the element are specified as a hash. The - # pseudo-attribute "PRETTY" can be used to specify that the generated - # HTML string should be indented. "PRETTY" can also be specified as - # a string as the sole argument to this method. The pseudo-attribute - # "DOCTYPE", if given, is used as the leading DOCTYPE SGML tag; it - # should include the entire text of this tag, including angle brackets. - # - # The body of the html element is supplied as a block. - # - # html{ "string" } - # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML>string</HTML> - # - # html("LANG" => "ja") { "string" } - # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML LANG="ja">string</HTML> - # - # html("DOCTYPE" => false) { "string" } - # # <HTML>string</HTML> - # - # html("DOCTYPE" => '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">') { "string" } - # # <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"><HTML>string</HTML> - # - # html("PRETTY" => " ") { "<BODY></BODY>" } - # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> - # # <HTML> - # # <BODY> - # # </BODY> - # # </HTML> - # - # html("PRETTY" => "\t") { "<BODY></BODY>" } - # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> - # # <HTML> - # # <BODY> - # # </BODY> - # # </HTML> - # - # html("PRETTY") { "<BODY></BODY>" } - # # = html("PRETTY" => " ") { "<BODY></BODY>" } - # - # html(if $VERBOSE then "PRETTY" end) { "HTML string" } - # - def html(attributes = {}) # :yield: - if nil == attributes - attributes = {} - elsif "PRETTY" == attributes - attributes = { "PRETTY" => true } - end - pretty = attributes.delete("PRETTY") - pretty = " " if true == pretty - buf = "".dup - - if attributes.has_key?("DOCTYPE") - if attributes["DOCTYPE"] - buf << attributes.delete("DOCTYPE") - else - attributes.delete("DOCTYPE") - end - else - buf << doctype - end - - buf << super(attributes) - - if pretty - CGI.pretty(buf, pretty) - else - buf - end - - end - - # Generate an Image Button Input element as a string. - # - # +src+ is the URL of the image to use for the button. +name+ - # is the input name. +alt+ is the alternative text for the image. - # - # Alternatively, the attributes can be specified as a hash. - # - # image_button("url") - # # <INPUT TYPE="image" SRC="url"> - # - # image_button("url", "name", "string") - # # <INPUT TYPE="image" SRC="url" NAME="name" ALT="string"> - # - # image_button("SRC" => "url", "ALT" => "string") - # # <INPUT TYPE="image" SRC="url" ALT="string"> - def image_button(src = "", name = nil, alt = nil) - attributes = if src.kind_of?(String) - { "TYPE" => "image", "SRC" => src, "NAME" => name, - "ALT" => alt } - else - src["TYPE"] = "image" - src["SRC"] ||= "" - src - end - input(attributes) - end - - - # Generate an Image element as a string. - # - # +src+ is the URL of the image. +alt+ is the alternative text for - # the image. +width+ is the width of the image, and +height+ is - # its height. - # - # Alternatively, the attributes can be specified as a hash. - # - # img("src", "alt", 100, 50) - # # <IMG SRC="src" ALT="alt" WIDTH="100" HEIGHT="50"> - # - # img("SRC" => "src", "ALT" => "alt", "WIDTH" => 100, "HEIGHT" => 50) - # # <IMG SRC="src" ALT="alt" WIDTH="100" HEIGHT="50"> - def img(src = "", alt = "", width = nil, height = nil) - attributes = if src.kind_of?(String) - { "SRC" => src, "ALT" => alt } - else - src - end - attributes["WIDTH"] = width.to_s if width - attributes["HEIGHT"] = height.to_s if height - super(attributes) - end - - - # Generate a Form element with multipart encoding as a String. - # - # Multipart encoding is used for forms that include file uploads. - # - # +action+ is the action to perform. +enctype+ is the encoding - # type, which defaults to "multipart/form-data". - # - # Alternatively, the attributes can be specified as a hash. - # - # multipart_form{ "string" } - # # <FORM METHOD="post" ENCTYPE="multipart/form-data">string</FORM> - # - # multipart_form("url") { "string" } - # # <FORM METHOD="post" ACTION="url" ENCTYPE="multipart/form-data">string</FORM> - def multipart_form(action = nil, enctype = "multipart/form-data") - attributes = if action == nil - { "METHOD" => "post", "ENCTYPE" => enctype } - elsif action.kind_of?(String) - { "METHOD" => "post", "ACTION" => action, - "ENCTYPE" => enctype } - else - unless action.has_key?("METHOD") - action["METHOD"] = "post" - end - unless action.has_key?("ENCTYPE") - action["ENCTYPE"] = enctype - end - action - end - if block_given? - form(attributes){ yield } - else - form(attributes) - end - end - - - # Generate a Password Input element as a string. - # - # +name+ is the name of the input field. +value+ is its default - # value. +size+ is the size of the input field display. +maxlength+ - # is the maximum length of the inputted password. - # - # Alternatively, attributes can be specified as a hash. - # - # password_field("name") - # # <INPUT TYPE="password" NAME="name" SIZE="40"> - # - # password_field("name", "value") - # # <INPUT TYPE="password" NAME="name" VALUE="value" SIZE="40"> - # - # password_field("password", "value", 80, 200) - # # <INPUT TYPE="password" NAME="name" VALUE="value" SIZE="80" MAXLENGTH="200"> - # - # password_field("NAME" => "name", "VALUE" => "value") - # # <INPUT TYPE="password" NAME="name" VALUE="value"> - def password_field(name = "", value = nil, size = 40, maxlength = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "password", "NAME" => name, - "VALUE" => value, "SIZE" => size.to_s } - else - name["TYPE"] = "password" - name - end - attributes["MAXLENGTH"] = maxlength.to_s if maxlength - input(attributes) - end - - # Generate a Select element as a string. - # - # +name+ is the name of the element. The +values+ are the options that - # can be selected from the Select menu. Each value can be a String or - # a one, two, or three-element Array. If a String or a one-element - # Array, this is both the value of that option and the text displayed for - # it. If a three-element Array, the elements are the option value, displayed - # text, and a boolean value specifying whether this option starts as selected. - # The two-element version omits either the option value (defaults to the same - # as the display text) or the boolean selected specifier (defaults to false). - # - # The attributes and options can also be specified as a hash. In this - # case, options are specified as an array of values as described above, - # with the hash key of "VALUES". - # - # popup_menu("name", "foo", "bar", "baz") - # # <SELECT NAME="name"> - # # <OPTION VALUE="foo">foo</OPTION> - # # <OPTION VALUE="bar">bar</OPTION> - # # <OPTION VALUE="baz">baz</OPTION> - # # </SELECT> - # - # popup_menu("name", ["foo"], ["bar", true], "baz") - # # <SELECT NAME="name"> - # # <OPTION VALUE="foo">foo</OPTION> - # # <OPTION VALUE="bar" SELECTED>bar</OPTION> - # # <OPTION VALUE="baz">baz</OPTION> - # # </SELECT> - # - # popup_menu("name", ["1", "Foo"], ["2", "Bar", true], "Baz") - # # <SELECT NAME="name"> - # # <OPTION VALUE="1">Foo</OPTION> - # # <OPTION SELECTED VALUE="2">Bar</OPTION> - # # <OPTION VALUE="Baz">Baz</OPTION> - # # </SELECT> - # - # popup_menu("NAME" => "name", "SIZE" => 2, "MULTIPLE" => true, - # "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"]) - # # <SELECT NAME="name" MULTIPLE SIZE="2"> - # # <OPTION VALUE="1">Foo</OPTION> - # # <OPTION SELECTED VALUE="2">Bar</OPTION> - # # <OPTION VALUE="Baz">Baz</OPTION> - # # </SELECT> - def popup_menu(name = "", *values) - - if name.kind_of?(Hash) - values = name["VALUES"] - size = name["SIZE"].to_s if name["SIZE"] - multiple = name["MULTIPLE"] - name = name["NAME"] - else - size = nil - multiple = nil - end - - select({ "NAME" => name, "SIZE" => size, - "MULTIPLE" => multiple }){ - values.collect{|value| - if value.kind_of?(String) - option({ "VALUE" => value }){ value } - else - if value[value.size - 1] == true - option({ "VALUE" => value[0], "SELECTED" => true }){ - value[value.size - 2] - } - else - option({ "VALUE" => value[0] }){ - value[value.size - 1] - } - end - end - }.join - } - - end - - # Generates a radio-button Input element. - # - # +name+ is the name of the input field. +value+ is the value of - # the field if checked. +checked+ specifies whether the field - # starts off checked. - # - # Alternatively, the attributes can be specified as a hash. - # - # radio_button("name", "value") - # # <INPUT TYPE="radio" NAME="name" VALUE="value"> - # - # radio_button("name", "value", true) - # # <INPUT TYPE="radio" NAME="name" VALUE="value" CHECKED> - # - # radio_button("NAME" => "name", "VALUE" => "value", "ID" => "foo") - # # <INPUT TYPE="radio" NAME="name" VALUE="value" ID="foo"> - def radio_button(name = "", value = nil, checked = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "radio", "NAME" => name, - "VALUE" => value, "CHECKED" => checked } - else - name["TYPE"] = "radio" - name - end - input(attributes) - end - - # Generate a sequence of radio button Input elements, as a String. - # - # This works the same as #checkbox_group(). However, it is not valid - # to have more than one radiobutton in a group checked. - # - # radio_group("name", "foo", "bar", "baz") - # # <INPUT TYPE="radio" NAME="name" VALUE="foo">foo - # # <INPUT TYPE="radio" NAME="name" VALUE="bar">bar - # # <INPUT TYPE="radio" NAME="name" VALUE="baz">baz - # - # radio_group("name", ["foo"], ["bar", true], "baz") - # # <INPUT TYPE="radio" NAME="name" VALUE="foo">foo - # # <INPUT TYPE="radio" CHECKED NAME="name" VALUE="bar">bar - # # <INPUT TYPE="radio" NAME="name" VALUE="baz">baz - # - # radio_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz") - # # <INPUT TYPE="radio" NAME="name" VALUE="1">Foo - # # <INPUT TYPE="radio" CHECKED NAME="name" VALUE="2">Bar - # # <INPUT TYPE="radio" NAME="name" VALUE="Baz">Baz - # - # radio_group("NAME" => "name", - # "VALUES" => ["foo", "bar", "baz"]) - # - # radio_group("NAME" => "name", - # "VALUES" => [["foo"], ["bar", true], "baz"]) - # - # radio_group("NAME" => "name", - # "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"]) - def radio_group(name = "", *values) - if name.kind_of?(Hash) - values = name["VALUES"] - name = name["NAME"] - end - values.collect{|value| - if value.kind_of?(String) - radio_button(name, value) + value - else - if value[-1] == true || value[-1] == false - radio_button(name, value[0], value[-1]) + - value[-2] - else - radio_button(name, value[0]) + - value[-1] - end - end - }.join - end - - # Generate a reset button Input element, as a String. - # - # This resets the values on a form to their initial values. +value+ - # is the text displayed on the button. +name+ is the name of this button. - # - # Alternatively, the attributes can be specified as a hash. - # - # reset - # # <INPUT TYPE="reset"> - # - # reset("reset") - # # <INPUT TYPE="reset" VALUE="reset"> - # - # reset("VALUE" => "reset", "ID" => "foo") - # # <INPUT TYPE="reset" VALUE="reset" ID="foo"> - def reset(value = nil, name = nil) - attributes = if (not value) or value.kind_of?(String) - { "TYPE" => "reset", "VALUE" => value, "NAME" => name } - else - value["TYPE"] = "reset" - value - end - input(attributes) - end - - alias scrolling_list popup_menu - - # Generate a submit button Input element, as a String. - # - # +value+ is the text to display on the button. +name+ is the name - # of the input. - # - # Alternatively, the attributes can be specified as a hash. - # - # submit - # # <INPUT TYPE="submit"> - # - # submit("ok") - # # <INPUT TYPE="submit" VALUE="ok"> - # - # submit("ok", "button1") - # # <INPUT TYPE="submit" VALUE="ok" NAME="button1"> - # - # submit("VALUE" => "ok", "NAME" => "button1", "ID" => "foo") - # # <INPUT TYPE="submit" VALUE="ok" NAME="button1" ID="foo"> - def submit(value = nil, name = nil) - attributes = if (not value) or value.kind_of?(String) - { "TYPE" => "submit", "VALUE" => value, "NAME" => name } - else - value["TYPE"] = "submit" - value - end - input(attributes) - end - - # Generate a text field Input element, as a String. - # - # +name+ is the name of the input field. +value+ is its initial - # value. +size+ is the size of the input area. +maxlength+ - # is the maximum length of input accepted. - # - # Alternatively, the attributes can be specified as a hash. - # - # text_field("name") - # # <INPUT TYPE="text" NAME="name" SIZE="40"> - # - # text_field("name", "value") - # # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="40"> - # - # text_field("name", "value", 80) - # # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="80"> - # - # text_field("name", "value", 80, 200) - # # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="80" MAXLENGTH="200"> - # - # text_field("NAME" => "name", "VALUE" => "value") - # # <INPUT TYPE="text" NAME="name" VALUE="value"> - def text_field(name = "", value = nil, size = 40, maxlength = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "text", "NAME" => name, "VALUE" => value, - "SIZE" => size.to_s } - else - name["TYPE"] = "text" - name - end - attributes["MAXLENGTH"] = maxlength.to_s if maxlength - input(attributes) - end - - # Generate a TextArea element, as a String. - # - # +name+ is the name of the textarea. +cols+ is the number of - # columns and +rows+ is the number of rows in the display. - # - # Alternatively, the attributes can be specified as a hash. - # - # The body is provided by the passed-in no-argument block - # - # textarea("name") - # # = textarea("NAME" => "name", "COLS" => 70, "ROWS" => 10) - # - # textarea("name", 40, 5) - # # = textarea("NAME" => "name", "COLS" => 40, "ROWS" => 5) - def textarea(name = "", cols = 70, rows = 10) # :yield: - attributes = if name.kind_of?(String) - { "NAME" => name, "COLS" => cols.to_s, - "ROWS" => rows.to_s } - else - name - end - super(attributes) - end - - end # HtmlExtension - - - # 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 - - 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 ] - 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 ] - 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 ] - define_method(element.downcase, m) - end - end - - end # Html3 - - - # 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 - - # 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 ] - define_method(element.downcase, m) - end - end - - # - O EMPTY - instance_method(:nOE_element_def).tap do |m| - for element in %w[ IMG BASE BR AREA LINK PARAM HR INPUT COL META ] - define_method(element.downcase, m) - end - end - - # 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 ] - define_method(element.downcase, m) - end - end - - end # Html4 - - - # Mixin module for HTML version 4 transitional generation methods. - module Html4Tr # :nodoc: - include TagMaker - - # The DOCTYPE declaration for this version of HTML - def doctype - %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">| - end - - # Initialise the HTML generation methods for this version. - # - - - 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 ] - define_method(element.downcase, m) - end - end - - # - 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 ] - define_method(element.downcase, m) - end - end - - # 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 ] - define_method(element.downcase, m) - end - end - - end # Html4Tr - - - # Mixin module for generating HTML version 4 with framesets. - module Html4Fr # :nodoc: - include TagMaker - - # The DOCTYPE declaration for this version of HTML - def doctype - %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">| - end - - # Initialise the HTML generation methods for this version. - # - - - instance_method(:nn_element_def).tap do |m| - for element in %w[ FRAMESET ] - define_method(element.downcase, m) - end - end - - # - O EMPTY - instance_method(:nOE_element_def).tap do |m| - for element in %w[ FRAME ] - define_method(element.downcase, m) - end - end - - end # Html4Fr - - - # Mixin module for HTML version 5 generation methods. - module Html5 # :nodoc: - include TagMaker - - # The DOCTYPE declaration for this version of HTML - def doctype - %|<!DOCTYPE HTML>| - end - - # Initialise the HTML generation methods for this version. - # - - - 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 - VAR CITE ABBR SUB SUP SPAN BDO ADDRESS DIV MAP OBJECT - 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 ] - define_method(element.downcase, m) - end - end - - # - 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 ] - define_method(element.downcase, m) - end - end - - # 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 ] - define_method(element.downcase, m) - end - 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 deleted file mode 100644 index aab60869bb..0000000000 --- a/lib/cgi/session.rb +++ /dev/null @@ -1,562 +0,0 @@ -# frozen_string_literal: true -# -# cgi/session.rb - session support for cgi scripts -# -# Copyright (C) 2001 Yukihiro "Matz" Matsumoto -# Copyright (C) 2000 Network Applied Communication Laboratory, Inc. -# Copyright (C) 2000 Information-technology Promotion Agency, Japan -# -# Author: Yukihiro "Matz" Matsumoto -# -# Documentation: William Webber (william@williamwebber.com) - -require 'cgi' -require 'tmpdir' - -class CGI - - # == Overview - # - # This file provides the CGI::Session class, which provides session - # support for CGI scripts. A session is a sequence of HTTP requests - # and responses linked together and associated with a single client. - # Information associated with the session is stored - # on the server between requests. A session id is passed between client - # and server with every request and response, transparently - # to the user. This adds state information to the otherwise stateless - # HTTP request/response protocol. - # - # == Lifecycle - # - # A CGI::Session instance is created from a CGI object. By default, - # this CGI::Session instance will start a new session if none currently - # exists, or continue the current session for this client if one does - # exist. The +new_session+ option can be used to either always or - # never create a new session. See #new() for more details. - # - # #delete() deletes a session from session storage. It - # does not however remove the session id from the client. If the client - # makes another request with the same id, the effect will be to start - # a new session with the old session's id. - # - # == Setting and retrieving session data. - # - # The Session class associates data with a session as key-value pairs. - # This data can be set and retrieved by indexing the Session instance - # using '[]', much the same as hashes (although other hash methods - # are not supported). - # - # When session processing has been completed for a request, the - # session should be closed using the close() method. This will - # store the session's state to persistent storage. If you want - # to store the session's state to persistent storage without - # finishing session processing for this request, call the update() - # method. - # - # == Storing session state - # - # The caller can specify what form of storage to use for the session's - # data with the +database_manager+ option to CGI::Session::new. The - # following storage classes are provided as part of the standard library: - # - # CGI::Session::FileStore:: stores data as plain text in a flat file. Only - # 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 - # interpreter instance does. - # CGI::Session::PStore:: stores data in Marshalled format. Provided by - # cgi/session/pstore.rb. Supports data of any type, - # and provides file-locking and transaction support. - # - # Custom storage types can also be created by defining a class with - # the following methods: - # - # new(session, options) - # restore # returns hash of session data. - # update - # close - # delete - # - # Changing storage type mid-session does not work. Note in particular - # that by default the FileStore and PStore session data files have the - # same name. If your application switches from one to the other without - # making sure that filenames will be different - # and clients still have old sessions lying around in cookies, then - # things will break nastily! - # - # == Maintaining the session id. - # - # Most session state is maintained on the server. However, a session - # id must be passed backwards and forwards between client and server - # to maintain a reference to this session state. - # - # The simplest way to do this is via cookies. The CGI::Session class - # provides transparent support for session id communication via cookies - # if the client has cookies enabled. - # - # If the client has cookies disabled, the session id must be included - # as a parameter of all requests sent by the client to the server. The - # CGI::Session class in conjunction with the CGI class will transparently - # add the session id as a hidden input field to all forms generated - # using the CGI#form() HTML generation method. No built-in support is - # provided for other mechanisms, such as URL re-writing. The caller is - # responsible for extracting the session id from the session_id - # attribute and manually encoding it in URLs and adding it as a hidden - # input to HTML forms created by other mechanisms. Also, session expiry - # is not automatically handled. - # - # == Examples of use - # - # === Setting the user's name - # - # require 'cgi' - # require 'cgi/session' - # require 'cgi/session/pstore' # provides CGI::Session::PStore - # - # cgi = CGI.new("html4") - # - # session = CGI::Session.new(cgi, - # 'database_manager' => CGI::Session::PStore, # use PStore - # 'session_key' => '_rb_sess_id', # custom session key - # 'session_expires' => Time.now + 30 * 60, # 30 minute timeout - # 'prefix' => 'pstore_sid_') # PStore option - # if cgi.has_key?('user_name') and cgi['user_name'] != '' - # # coerce to String: cgi[] returns the - # # string-like CGI::QueryExtension::Value - # session['user_name'] = cgi['user_name'].to_s - # elsif !session['user_name'] - # session['user_name'] = "guest" - # end - # session.close - # - # === Creating a new session safely - # - # require 'cgi' - # require 'cgi/session' - # - # cgi = CGI.new("html4") - # - # # We make sure to delete an old session if one exists, - # # not just to free resources, but to prevent the session - # # from being maliciously hijacked later on. - # begin - # session = CGI::Session.new(cgi, 'new_session' => false) - # session.delete - # rescue ArgumentError # if no old session - # end - # session = CGI::Session.new(cgi, 'new_session' => true) - # session.close - # - class Session - - class NoSession < RuntimeError #:nodoc: - end - - # The id of this session. - attr_reader :session_id, :new_session - - def Session::callback(dbman) #:nodoc: - Proc.new{ - dbman[0].close unless dbman.empty? - } - end - - # Create a new session id. - # - # 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 - # never happens on modern systems - require 'digest' - d = Digest('SHA512').new - now = Time::now - 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). - # +option+ is a hash of options for initialising this - # CGI::Session instance. The following options are - # recognised: - # - # session_key:: the parameter name used for the session id. - # Defaults to '_session_id'. - # session_id:: the session id to use. If not provided, then - # it is retrieved from the +session_key+ parameter - # of the request, or automatically generated for - # a new session. - # new_session:: if true, force creation of a new session. If not set, - # a new session is only created if none currently - # exists. If false, a new session is never created, - # and if none currently exists and the +session_id+ - # option is not set, an ArgumentError is raised. - # database_manager:: the name of the class providing storage facilities - # for session state persistence. Built-in support - # is provided for +FileStore+ (the default), - # +MemoryStore+, and +PStore+ (from - # cgi/session/pstore.rb). See the documentation for - # these classes for more details. - # - # The following options are also recognised, but only apply if the - # session id is stored in a cookie. - # - # session_expires:: the time the current session expires, as a - # +Time+ object. If not set, the session will terminate - # when the user's browser is closed. - # session_domain:: the hostname domain for which this session is valid. - # If not set, defaults to the hostname of the server. - # session_secure:: if +true+, this session will only work over HTTPS. - # session_path:: the path for which this session applies. Defaults - # to the directory of the CGI script. - # - # +option+ is also passed on to the session storage class initializer; see - # the documentation for each session storage class for the options - # they support. - # - # The retrieved or created session is automatically added to +request+ - # as a cookie, and also to its +output_hidden+ table, which is used - # to add hidden input elements to forms. - # - # *WARNING* the +output_hidden+ - # 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 https://blade.ruby-lang.org/ruby-list/37805) - # - # cgi = CGI.new("html4") - # class << cgi - # undef_method :fieldset - # end - # - def initialize(request, option={}) - @new_session = false - session_key = option['session_key'] || '_session_id' - session_id = option['session_id'] - unless session_id - if option['new_session'] - session_id = create_new_id - @new_session = true - end - end - unless session_id - if request.key?(session_key) - session_id = request[session_key] - session_id = session_id.read if session_id.respond_to?(:read) - end - unless session_id - session_id, = request.cookies[session_key] - end - unless session_id - unless option.fetch('new_session', true) - raise ArgumentError, "session_key `%s' should be supplied"%session_key - end - session_id = create_new_id - @new_session = true - end - end - @session_id = session_id - dbman = option['database_manager'] || FileStore - begin - @dbman = dbman::new(self, option) - rescue NoSession - unless option.fetch('new_session', true) - raise ArgumentError, "invalid session_id `%s'"%session_id - end - session_id = @session_id = create_new_id unless session_id - @new_session=true - retry - end - request.instance_eval do - @output_hidden = {session_key => session_id} unless option['no_hidden'] - @output_cookies = [ - Cookie::new("name" => session_key, - "value" => session_id, - "expires" => option['session_expires'], - "domain" => option['session_domain'], - "secure" => option['session_secure'], - "path" => - if option['session_path'] - option['session_path'] - elsif ENV["SCRIPT_NAME"] - File::dirname(ENV["SCRIPT_NAME"]) - else - "" - end) - ] unless option['no_cookies'] - end - @dbprot = [@dbman] - ObjectSpace::define_finalizer(self, Session::callback(@dbprot)) - end - - # Retrieve the session data for key +key+. - def [](key) - @data ||= @dbman.restore - @data[key] - end - - # Set the session data for key +key+. - def []=(key, val) - @write_lock ||= true - @data ||= @dbman.restore - @data[key] = val - end - - # Store session data on the server. For some session storage types, - # this is a no-op. - def update - @dbman.update - end - - # Store session data on the server and close the session storage. - # For some session storage types, this is a no-op. - def close - @dbman.close - @dbprot.clear - end - - # Delete the session from storage. Also closes the storage. - # - # Note that the session's data is _not_ automatically deleted - # upon the session expiring. - def delete - @dbman.delete - @dbprot.clear - end - - # File-based session storage class. - # - # Implements session storage as a flat file of 'key=value' values. - # This storage type only works directly with String values; the - # user is responsible for converting other types to Strings when - # storing and from Strings when retrieving. - class FileStore - # Create a new FileStore instance. - # - # This constructor is used internally by CGI::Session. The - # user does not generally need to call it directly. - # - # +session+ is the session for which this instance is being - # created. The session id must only contain alphanumeric - # characters; automatically generated session ids observe - # this requirement. - # - # +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. - # - # This session's FileStore file will be created if it does - # not exist, or opened if it does. - def initialize(session, option={}) - option = {'prefix' => 'cgi_sid_'}.update(option) - @path, @hash = session.new_store_file(option) - end - - # Restore session state from the session's FileStore file. - # - # Returns the session state as a hash. - def restore - unless @hash - @hash = {} - begin - lockf = File.open(@path+".lock", "r") - lockf.flock File::LOCK_SH - f = File.open(@path, 'r') - for line in f - line.chomp! - k, v = line.split('=',2) - @hash[CGI.unescape(k)] = Marshal.restore(CGI.unescape(v)) - end - ensure - f&.close - lockf&.close - end - end - @hash - end - - # Save session state to the session's FileStore file. - def update - return unless @hash - begin - lockf = File.open(@path+".lock", File::CREAT|File::RDWR, 0600) - 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))) - end - f.close - File.rename @path+".new", @path - ensure - f&.close - lockf&.close - end - end - - # Update and close the session's FileStore file. - def close - update - end - - # Close and delete the session's FileStore file. - def delete - File::unlink @path+".lock" rescue nil - File::unlink @path+".new" rescue nil - 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 - # instance does. - class MemoryStore - GLOBAL_HASH_TABLE = {} #:nodoc: - - # Create a new MemoryStore instance. - # - # +session+ is the session this instance is associated with. - # +option+ is a list of initialisation options. None are - # currently recognized. - def initialize(session, option=nil) - @session_id = session.session_id - unless GLOBAL_HASH_TABLE.key?(@session_id) - unless session.new_session - raise CGI::Session::NoSession, "uninitialized session" - end - GLOBAL_HASH_TABLE[@session_id] = {} - end - end - - # Restore session state. - # - # Returns session data as a hash. - def restore - GLOBAL_HASH_TABLE[@session_id] - end - - # Update session state. - # - # A no-op. - def update - # don't need to update; hash is shared - end - - # Close session storage. - # - # A no-op. - def close - # don't need to close - end - - # Delete the session state. - def delete - GLOBAL_HASH_TABLE.delete(@session_id) - end - end - - # Dummy session storage class. - # - # Implements session storage place holder. No actual storage - # will be done. - class NullStore - # Create a new NullStore instance. - # - # +session+ is the session this instance is associated with. - # +option+ is a list of initialisation options. None are - # currently recognised. - def initialize(session, option=nil) - end - - # Restore (empty) session state. - def restore - {} - end - - # Update session state. - # - # A no-op. - def update - end - - # Close session storage. - # - # A no-op. - def close - end - - # Delete the session state. - # - # A no-op. - def delete - end - end - end -end diff --git a/lib/cgi/session/pstore.rb b/lib/cgi/session/pstore.rb deleted file mode 100644 index 6e3d10f075..0000000000 --- a/lib/cgi/session/pstore.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true -# -# cgi/session/pstore.rb - persistent storage of marshalled session data -# -# Documentation: William Webber (william@williamwebber.com) -# -# == Overview -# -# This file provides the CGI::Session::PStore class, which builds -# persistent of session data on top of the pstore library. See -# cgi/session.rb for more details on session storage managers. - -require_relative '../session' -begin - require 'pstore' -rescue LoadError -end - -class CGI - class Session - # PStore-based session storage class. - # - # This builds upon the top-level PStore class provided by the - # library file pstore.rb. Session data is marshalled and stored - # in a file. File locking and transaction services are provided. - class PStore - # Create a new CGI::Session::PStore instance - # - # This constructor is used internally by CGI::Session. The - # user does not generally need to call it directly. - # - # +session+ is the session for which this instance is being - # created. The session id must only contain alphanumeric - # characters; automatically generated session ids observe - # this requirement. - # - # +option+ is a hash of options for the initializer. The - # following options are recognised: - # - # tmpdir:: the directory to use for storing the PStore - # 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 PStore file. - # Defaults to the empty string. - # - # This session's PStore file will be created if it does - # not exist, or opened if it does. - def initialize(session, option={}) - option = {'suffix'=>''}.update(option) - path, @hash = session.new_store_file(option) - @p = ::PStore.new(path) - @p.transaction do |p| - File.chmod(0600, p.path) - end - end - - # Restore session state from the session's PStore file. - # - # Returns the session state as a hash. - def restore - unless @hash - @p.transaction do - @hash = @p['hash'] || {} - end - end - @hash - end - - # Save session state to the session's PStore file. - def update - @p.transaction do - @p['hash'] = @hash - end - end - - # Update and close the session's PStore file. - def close - update - end - - # Close and delete the session's PStore file. - def delete - path = @p.path - File::unlink path - end - - end if defined?(::PStore) - end -end -# :enddoc: diff --git a/lib/cgi/util.rb b/lib/cgi/util.rb deleted file mode 100644 index 5d02b9d09f..0000000000 --- a/lib/cgi/util.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true -class CGI - module Util; end - include Util - extend Util -end - -module CGI::Util - # 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) - time.getgm.strftime("%a, %d %b %Y %T GMT") - 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 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) - 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 |
