module CGI::QueryExtension

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.

Attributes

accept_charset[R]

Return the accept character set for this CGI instance.

cookies[RW]

Get the cookies as a hash of cookie-name=>Cookie pairs.

files[R]

Get the uploaded files as a hash of name=>values pairs

params[R]

Get the parameters as a hash of name=>values pairs, where values is an Array.

Public Class Methods

accept_charset() click to toggle source

Return the accept character set for all new CGI instances.

# File lib/cgi/core.rb, line 747
def self.accept_charset
  @@accept_charset
end
accept_charset=(accept_charset) click to toggle source

Set the accept character set for all new CGI instances.

# File lib/cgi/core.rb, line 752
def self.accept_charset=(accept_charset)
  @@accept_charset=accept_charset
end
new(tag_maker) { block } click to toggle source
new(options_hash = {}) { block }

Create a new CGI instance.

tag_maker

This is the same as using the options_hash form with the value { :tag_maker => tag_maker } Note that it is recommended to use the options_hash form, since it also allows you specify the charset you will accept.

options_hash

A Hash that recognizes three options:

:accept_charset

specifies encoding of received query string. If omitted, @@accept_charset is used. If the encoding is not valid, a CGI::InvalidEncoding will be raised.

Example. Suppose @@accept_charset 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"
:tag_maker

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

:max_multipart_length

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
block

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.

# File lib/cgi/core.rb, line 838
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 'cgi/html'
    extend Html3
    extend HtmlExtension
  when "html4"
    require 'cgi/html'
    extend Html4
    extend HtmlExtension
  when "html4Tr"
    require 'cgi/html'
    extend Html4Tr
    extend HtmlExtension
  when "html4Fr"
    require 'cgi/html'
    extend Html4Tr
    extend Html4Fr
    extend HtmlExtension
  when "html5"
    require 'cgi/html'
    extend Html5
    extend HtmlExtension
  end
end

Public Instance Methods

[](key) click to toggle source

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.

# File lib/cgi/core.rb, line 702
def [](key)
  params = @params[key]
  return '' unless params
  value = params[0]
  if @multipart
    if value
      return value
    elsif defined? StringIO
      StringIO.new("".force_encoding(Encoding::ASCII_8BIT))
    else
      Tempfile.new("CGI",encoding: Encoding::ASCII_8BIT)
    end
  else
    str = if value then value.dup else "" end
    str
  end
end
has_key?(*args) click to toggle source

Returns true if a given query string parameter exists.

# File lib/cgi/core.rb, line 726
def has_key?(*args)
  @params.has_key?(*args)
end
initialize_query() click to toggle source

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.

# File lib/cgi/core.rb, line 649
def initialize_query()
  if ("POST" == env_table['REQUEST_METHOD']) and
    %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|.match(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
keys(*args) click to toggle source

Return all query parameter names as an array of String.

# File lib/cgi/core.rb, line 721
def keys(*args)
  @params.keys(*args)
end
multipart?() click to toggle source

Returns whether the form contained multipart/form-data

# File lib/cgi/core.rb, line 694
def multipart?
  @multipart
end
params=(hash) click to toggle source

Set all the parameters.

# File lib/cgi/core.rb, line 463
def params=(hash)
  @params.clear
  @params.update(hash)
end
raw_cookie2() click to toggle source

Get the raw RFC2965 cookies as a string.

# File lib/cgi/core.rb, line 448
def raw_cookie2
  env_table["HTTP_COOKIE2"]
end
read_from_cmdline() click to toggle source

offline mode. read name=value pairs on standard input.

# File lib/cgi/core.rb, line 614
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
read_multipart(boundary, content_length) click to toggle source

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]
# File lib/cgi/core.rb, line 477
  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 = ''
    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 || ''
      filename = CGI.unescape(filename) if unescape_filename?()
      body.instance_variable_set(:@original_filename, filename.taint)
      ## content type
      /Content-Type: (.*)/i.match(head)
      (content_type = $1 || '').chomp!
      body.instance_variable_set(:@content_type, content_type.taint)
      ## 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("".force_encoding(Encoding::ASCII_8BIT))
      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 /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=\"?([^\";,]+)\"?|.match(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("".force_encoding(Encoding::ASCII_8BIT))
      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