├── .document
├── ext
├── cgi
│ └── escape
│ │ ├── depend
│ │ ├── extconf.rb
│ │ └── escape.c
└── java
│ └── org
│ └── jruby
│ └── ext
│ └── cgi
│ └── escape
│ ├── lib
│ └── cgi
│ │ └── escape.rb
│ └── CGIEscape.java
├── test
├── cgi
│ ├── testdata
│ │ ├── large.png
│ │ ├── small.png
│ │ └── file1.html
│ ├── update_env.rb
│ ├── test_cgi_util.rb
│ ├── test_cgi_modruby.rb
│ ├── test_cgi_session.rb
│ ├── test_cgi_header.rb
│ ├── test_cgi_cookie.rb
│ ├── test_cgi_new.rb
│ ├── test_cgi_core.rb
│ ├── test_cgi_multipart.rb
│ ├── test_cgi_escape.rb
│ └── test_cgi_tag_helper.rb
└── lib
│ └── helper.rb
├── .github
├── release.yml
├── dependabot.yml
└── workflows
│ ├── sync-ruby.yml
│ ├── test.yml
│ └── push_gem.yml
├── rakelib
├── epoch.rake
├── version.rake
├── changelogs.rake
└── check.rake
├── Gemfile
├── .gitignore
├── .git-blame-ignore-revs
├── Rakefile
├── BSDL
├── lib
├── cgi
│ ├── util.rb
│ ├── session
│ │ └── pstore.rb
│ ├── cookie.rb
│ ├── escape.rb
│ └── session.rb
└── cgi.rb
├── cgi.gemspec
├── COPYING
└── README.md
/.document:
--------------------------------------------------------------------------------
1 | BSDL
2 | COPYING
3 | README.md
4 | ext/
5 | lib/
6 |
--------------------------------------------------------------------------------
/ext/cgi/escape/depend:
--------------------------------------------------------------------------------
1 | escape.o: $(RUBY_EXTCONF_H)
2 | escape.o: escape.c
3 |
--------------------------------------------------------------------------------
/test/cgi/testdata/large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruby/cgi/master/test/cgi/testdata/large.png
--------------------------------------------------------------------------------
/test/cgi/testdata/small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruby/cgi/master/test/cgi/testdata/small.png
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 | changelog:
2 | exclude:
3 | labels:
4 | - dependencies # Added by Dependabot
5 |
--------------------------------------------------------------------------------
/test/lib/helper.rb:
--------------------------------------------------------------------------------
1 | require "test/unit"
2 | require "core_assertions"
3 |
4 | Test::Unit::TestCase.include Test::Unit::CoreAssertions
5 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: 'github-actions'
4 | directory: '/'
5 | schedule:
6 | interval: 'weekly'
7 |
--------------------------------------------------------------------------------
/rakelib/epoch.rake:
--------------------------------------------------------------------------------
1 | task "build" => "date_epoch"
2 |
3 | task "date_epoch" do
4 | ENV["SOURCE_DATE_EPOCH"] = IO.popen(%W[git -C #{__dir__} log -1 --format=%ct], &:read).chomp
5 | end
6 |
--------------------------------------------------------------------------------
/ext/cgi/escape/extconf.rb:
--------------------------------------------------------------------------------
1 | require 'mkmf'
2 |
3 | if RUBY_ENGINE == 'truffleruby'
4 | File.write("Makefile", dummy_makefile($srcdir).join(""))
5 | else
6 | create_makefile 'cgi/escape'
7 | end
8 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | group :development do
4 | gem "bundler"
5 | gem "rake"
6 | gem "rake-compiler"
7 | gem "test-unit"
8 | gem "test-unit-ruby-core"
9 | end
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /_yardoc/
4 | /coverage/
5 | /doc/
6 | /logs/
7 | /pkg/
8 | /spec/reports/
9 | /tmp/
10 | /ChangeLog
11 | /Gemfile.lock
12 | *.jar
13 | *.bundle
14 | *.so
15 | *.dll
16 |
--------------------------------------------------------------------------------
/ext/java/org/jruby/ext/cgi/escape/lib/cgi/escape.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Load built-in cgi/escape library
4 | require 'cgi/escape.jar'
5 | JRuby::Util.load_ext("org.jruby.ext.cgi.escape.CGIEscape")
6 |
--------------------------------------------------------------------------------
/test/cgi/update_env.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: false
2 | module UpdateEnv
3 | def update_env(environ)
4 | environ.each do |key, val|
5 | @environ[key] = ENV[key] unless @environ.key?(key)
6 | ENV[key] = val
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/.git-blame-ignore-revs:
--------------------------------------------------------------------------------
1 | # This is a file used by GitHub to ignore the following commits on `git blame`.
2 | #
3 | # You can also do the same thing in your local repository with:
4 | # $ git config --local blame.ignoreRevsFile .git-blame-ignore-revs
5 |
6 | # Expand tabs
7 | 4d987cf78f7f3eaeb67ef0463d3c0d07aaa3de09
8 |
--------------------------------------------------------------------------------
/test/cgi/testdata/file1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ムスカ大佐のひとりごと
5 |
6 |
7 |
8 | バカどもにはちょうどいい目くらましだ。
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/cgi/test_cgi_util.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require 'test/unit'
3 | require 'cgi/util'
4 |
5 | class CGIUtilTest < Test::Unit::TestCase
6 |
7 | def test_cgi_pretty
8 | assert_equal("\n \n \n\n",CGI.pretty(""))
9 | assert_equal("\n\t\n\t\n\n",CGI.pretty("","\t"))
10 | end
11 |
12 | end
13 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require "rake/testtask"
3 |
4 | require 'rake/javaextensiontask'
5 | Rake::JavaExtensionTask.new("escape") do |ext|
6 | ext.source_version = '1.8'
7 | ext.target_version = '1.8'
8 | ext.ext_dir = 'ext/java'
9 | ext.lib_dir = 'lib/cgi'
10 |
11 | task :build => :compile
12 | end
13 |
14 | if RUBY_ENGINE == 'ruby'
15 | require 'rake/extensiontask'
16 | extask = Rake::ExtensionTask.new("cgi/escape") do |x|
17 | x.lib_dir.sub!(%r[(?=/|\z)], "/#{RUBY_VERSION}/#{x.platform}")
18 | end
19 | end
20 |
21 | Rake::TestTask.new(:test) do |t|
22 | t.libs << "test/lib"
23 | if RUBY_ENGINE == 'jruby'
24 | t.libs << "ext/java/org/jruby/ext/cgi/escape/lib"
25 | elsif RUBY_ENGINE == 'ruby'
26 | t.libs << "lib/#{RUBY_VERSION}/#{extask.platform}"
27 | end
28 | t.ruby_opts << "-rhelper"
29 | t.test_files = FileList['test/**/test_*.rb']
30 | end
31 |
32 | task :default => :test
33 | task :test => :compile
34 |
--------------------------------------------------------------------------------
/rakelib/version.rake:
--------------------------------------------------------------------------------
1 | class << (helper = Bundler::GemHelper.instance)
2 | def update_gemspec
3 | path = gemspec.loaded_from
4 | File.open(path, "r+b") do |f|
5 | d = f.read
6 | if d.sub!(/^(_VERSION\s*=\s*)".*"/) {$1 + gemspec.version.to_s.dump}
7 | f.rewind
8 | f.truncate(0)
9 | f.print(d)
10 | end
11 | end
12 | end
13 |
14 | def commit_bump
15 | sh(%W[git commit -m bump\ up\ to\ #{gemspec.version}
16 | #{gemspec.loaded_from}])
17 | end
18 |
19 | def version=(v)
20 | gemspec.version = v
21 | update_gemspec
22 | commit_bump
23 | end
24 | end
25 |
26 | major, minor, teeny = helper.gemspec.version.segments
27 |
28 | task "bump:teeny" do
29 | helper.version = Gem::Version.new("#{major}.#{minor}.#{teeny+1}")
30 | end
31 |
32 | task "bump:minor" do
33 | helper.version = Gem::Version.new("#{major}.#{minor+1}.0")
34 | end
35 |
36 | task "bump:major" do
37 | helper.version = Gem::Version.new("#{major+1}.0.0")
38 | end
39 |
40 | task "bump" => "bump:teeny"
41 |
42 | task "tag" do
43 | helper.__send__(:tag_version)
44 | end
45 |
--------------------------------------------------------------------------------
/.github/workflows/sync-ruby.yml:
--------------------------------------------------------------------------------
1 | name: Sync ruby
2 | on:
3 | push:
4 | branches: [master]
5 | jobs:
6 | sync:
7 | name: Sync ruby
8 | runs-on: ubuntu-latest
9 | if: ${{ github.repository_owner == 'ruby' }}
10 | steps:
11 | - uses: actions/checkout@v6
12 |
13 | - name: Create GitHub App token
14 | id: app-token
15 | uses: actions/create-github-app-token@v2
16 | with:
17 | app-id: 2060836
18 | private-key: ${{ secrets.RUBY_SYNC_DEFAULT_GEMS_PRIVATE_KEY }}
19 | owner: ruby
20 | repositories: ruby
21 |
22 | - name: Sync to ruby/ruby
23 | uses: convictional/trigger-workflow-and-wait@v1.6.5
24 | with:
25 | owner: ruby
26 | repo: ruby
27 | workflow_file_name: sync_default_gems.yml
28 | github_token: ${{ steps.app-token.outputs.token }}
29 | ref: master
30 | client_payload: |
31 | {"gem":"${{ github.event.repository.name }}","before":"${{ github.event.before }}","after":"${{ github.event.after }}"}
32 | propagate_failure: true
33 | wait_interval: 10
34 |
--------------------------------------------------------------------------------
/rakelib/changelogs.rake:
--------------------------------------------------------------------------------
1 | task "build" => "changelogs"
2 |
3 | changelog = proc do |output, ver = nil, prev = nil|
4 | ver &&= Gem::Version.new(ver)
5 | range = [[prev], [ver, "HEAD"]].map {|ver, branch| ver ? "v#{ver.to_s}" : branch}.compact.join("..")
6 | IO.popen(%W[git log --format=fuller --topo-order --no-merges #{range}]) do |log|
7 | line = log.gets
8 | FileUtils.mkpath(File.dirname(output))
9 | File.open(output, "wb") do |f|
10 | f.print "-*- coding: utf-8 -*-\n\n", line
11 | log.each_line do |line|
12 | line.sub!(/^(?!:)(?:Author|Commit)?(?:Date)?: /, ' \&')
13 | line.sub!(/ +$/, '')
14 | f.print(line)
15 | end
16 | end
17 | end
18 | end
19 |
20 | tags = IO.popen(%w[git tag -l v[0-9]*]).grep(/v(.*)/) {$1}
21 | tags.sort_by! {|tag| tag.scan(/\d+/).map(&:to_i)}
22 | tags.inject(nil) do |prev, tag|
23 | task("logs/ChangeLog-#{tag}") {|t| changelog[t.name, tag, prev]}
24 | tag
25 | end
26 |
27 | desc "Make ChangeLog"
28 | task "ChangeLog", [:ver, :prev] do |t, ver: nil, prev: tags.last|
29 | changelog[t.name, ver, prev]
30 | end
31 |
32 | changelogs = ["ChangeLog", *tags.map {|tag| "logs/ChangeLog-#{tag}"}]
33 | task "changelogs" => changelogs
34 | CLOBBER.concat(changelogs) << "logs"
35 |
--------------------------------------------------------------------------------
/rakelib/check.rake:
--------------------------------------------------------------------------------
1 | task :check do
2 | Bundler.with_unbundled_env do
3 | spec = Gem::Specification::load("cgi.gemspec")
4 | version = spec.version.to_s
5 |
6 | gem = "pkg/cgi-#{version}#{"-java" if RUBY_ENGINE == "jruby"}.gem"
7 | File.size?(gem) or abort "gem not built!"
8 |
9 | sh "gem", "install", gem
10 |
11 | require_relative "../test/lib/envutil"
12 |
13 | _, _, status = EnvUtil.invoke_ruby([], <<~EOS)
14 | version = #{version.dump}
15 | gem "cgi", version
16 | loaded_version = Gem.loaded_specs["cgi"].version.to_s
17 | if loaded_version == version
18 | puts "cgi \#{loaded_version} is loaded."
19 | else
20 | abort "cgi \#{loaded_version} is loaded instead of \#{version}!"
21 | end
22 | require "cgi/escape"
23 |
24 | string = "&<>"
25 | actual = CGI.escape(string)
26 | expected = "%26%3C%3E"
27 | puts "CGI.escape(\#{string.dump}) = \#{actual.dump}"
28 | if actual != expected
29 | abort "no! expected to be \#{expected.dump}!"
30 | end
31 | EOS
32 |
33 | if status.success?
34 | puts "check succeeded!"
35 | else
36 | warn "check failed!"
37 | exit status.exitstatus
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/BSDL:
--------------------------------------------------------------------------------
1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions
5 | are met:
6 | 1. Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | 2. Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution.
11 |
12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
22 | SUCH DAMAGE.
23 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | ruby-versions:
7 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master
8 | with:
9 | min_version: 2.5
10 | test:
11 | needs: ruby-versions
12 | name: build (${{ matrix.ruby }} / ${{ matrix.os }})
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
17 | os: [ ubuntu-latest, macos-latest, windows-latest ]
18 | exclude:
19 | - { os: macos-latest, ruby: 2.5 }
20 | - { os: windows-latest, ruby: head }
21 | - { os: macos-latest, ruby: jruby }
22 | - { os: windows-latest, ruby: jruby }
23 | - { os: macos-latest, ruby: jruby-head }
24 | - { os: windows-latest, ruby: jruby-head }
25 | - { os: windows-latest, ruby: truffleruby }
26 | - { os: windows-latest, ruby: truffleruby-head }
27 | include:
28 | - { os: windows-latest, ruby: mingw }
29 | - { os: windows-latest, ruby: mswin }
30 | runs-on: ${{ matrix.os }}
31 | steps:
32 | - uses: actions/checkout@v6
33 | - name: Set up Ruby
34 | uses: ruby/setup-ruby@v1
35 | with:
36 | ruby-version: ${{ matrix.ruby }}
37 | - name: Install dependencies
38 | run: bundle install
39 | - name: Build
40 | run: rake compile
41 | - name: Run test
42 | run: bundle exec rake test
43 |
--------------------------------------------------------------------------------
/lib/cgi/util.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | class CGI
3 | module Util; end
4 | include Util
5 | extend Util
6 | end
7 |
8 | # Utility methods for CGI.
9 | module CGI::Util
10 | # Format a +Time+ object as a String using the format specified by RFC 1123.
11 | #
12 | # CGI.rfc1123_date(Time.now)
13 | # # Sat, 01 Jan 2000 00:00:00 GMT
14 | def rfc1123_date(time)
15 | time.getgm.strftime("%a, %d %b %Y %T GMT")
16 | end
17 |
18 | # Prettify (indent) an HTML string.
19 | #
20 | # +string+ is the HTML string to indent. +shift+ is the indentation
21 | # unit to use; it defaults to two spaces.
22 | #
23 | # print CGI.pretty("")
24 | # #
25 | # #
26 | # #
27 | # #
28 | #
29 | # print CGI.pretty("", "\t")
30 | # #
31 | # #
32 | # #
33 | # #
34 | #
35 | def pretty(string, shift = " ")
36 | lines = string.gsub(/(?!\A)<.*?>/m, "\n\\0").gsub(/<.*?>(?!\n)/m, "\\0\n")
37 | end_pos = 0
38 | while end_pos = lines.index(/^<\/(\w+)/, end_pos)
39 | element = $1.dup
40 | start_pos = lines.rindex(/^\s*<#{element}/i, end_pos)
41 | lines[start_pos ... end_pos] = "__" + lines[start_pos ... end_pos].gsub(/\n(?!\z)/, "\n" + shift) + "__"
42 | end
43 | lines.gsub(/^((?:#{Regexp::quote(shift)})*)__(?=<\/?\w)/, '\1')
44 | end
45 | end
46 |
47 | # For backward compatibility
48 | require 'cgi/escape' unless defined?(CGI::EscapeExt)
49 |
--------------------------------------------------------------------------------
/cgi.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | name = File.basename(__FILE__, ".gemspec")
4 | version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir|
5 | break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
6 | /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
7 | end rescue nil
8 | end
9 |
10 | Gem::Specification.new do |spec|
11 | spec.name = name
12 | spec.version = version
13 | spec.authors = ["Yukihiro Matsumoto"]
14 | spec.email = ["matz@ruby-lang.org"]
15 |
16 | spec.summary = %q{Support for the Common Gateway Interface protocol.}
17 | spec.description = %q{Support for the Common Gateway Interface protocol.}
18 | spec.homepage = "https://github.com/ruby/cgi"
19 | spec.licenses = ["Ruby", "BSD-2-Clause"]
20 | spec.required_ruby_version = ">= 2.5.0"
21 |
22 | spec.metadata["homepage_uri"] = spec.homepage
23 | spec.metadata["source_code_uri"] = spec.homepage
24 |
25 | spec.executables = []
26 |
27 | spec.files = [
28 | "COPYING",
29 | "BSDL",
30 | "README.md",
31 | *Dir["lib{.rb,/**/*.rb}", "bin/*"] ]
32 |
33 | spec.require_paths = ["lib"]
34 |
35 | if Gem::Platform === spec.platform and spec.platform =~ 'java' or RUBY_ENGINE == 'jruby'
36 | spec.platform = 'java'
37 | spec.require_paths << "ext/java/org/jruby/ext/cgi/escape/lib"
38 | spec.files += Dir["ext/java/**/*.{rb}", "lib/cgi/escape.jar"]
39 | else
40 | spec.files += Dir["ext/cgi/**/*.{rb,c,h,sh}", "ext/cgi/escape/depend", "lib/cgi/escape.so"]
41 | spec.extensions = ["ext/cgi/escape/extconf.rb"]
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/.github/workflows/push_gem.yml:
--------------------------------------------------------------------------------
1 | name: Publish gem to rubygems.org
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | push:
13 | if: github.repository == 'ruby/cgi'
14 | runs-on: ubuntu-latest
15 |
16 | environment:
17 | name: rubygems.org
18 | url: https://rubygems.org/gems/cgi
19 |
20 | permissions:
21 | contents: write
22 | id-token: write
23 |
24 | strategy:
25 | matrix:
26 | ruby: ["ruby", "jruby"]
27 |
28 | steps:
29 | - name: Harden Runner
30 | uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
31 | with:
32 | egress-policy: audit
33 |
34 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
35 |
36 | - name: Set up Ruby
37 | uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0
38 | with:
39 | ruby-version: ${{ matrix.ruby }}
40 |
41 | # https://github.com/rubygems/rubygems/issues/5882
42 | - name: Install dependencies and build for JRuby
43 | run: |
44 | sudo apt install default-jdk maven
45 | gem update --system
46 | gem install ruby-maven rake-compiler --no-document
47 | rake compile
48 | if: matrix.ruby == 'jruby'
49 |
50 | - name: Install dependencies
51 | run: bundle install --jobs 4 --retry 3
52 |
53 | - name: Publish to RubyGems
54 | uses: rubygems/release-gem@1c162a739e8b4cb21a676e97b087e8268d8fc40b # v1.1.2
55 |
56 | - name: Create GitHub release
57 | run: |
58 | tag_name="$(git describe --tags --abbrev=0)"
59 | gh release create "${tag_name}" --verify-tag --generate-notes
60 | env:
61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
62 | if: matrix.ruby == 'ruby'
63 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | Ruby is copyrighted free software by Yukihiro Matsumoto .
2 | You can redistribute it and/or modify it under either the terms of the
3 | 2-clause BSDL (see the file BSDL), or the conditions below:
4 |
5 | 1. You may make and give away verbatim copies of the source form of the
6 | software without restriction, provided that you duplicate all of the
7 | original copyright notices and associated disclaimers.
8 |
9 | 2. You may modify your copy of the software in any way, provided that
10 | you do at least ONE of the following:
11 |
12 | a. place your modifications in the Public Domain or otherwise
13 | make them Freely Available, such as by posting said
14 | modifications to Usenet or an equivalent medium, or by allowing
15 | the author to include your modifications in the software.
16 |
17 | b. use the modified software only within your corporation or
18 | organization.
19 |
20 | c. give non-standard binaries non-standard names, with
21 | instructions on where to get the original software distribution.
22 |
23 | d. make other distribution arrangements with the author.
24 |
25 | 3. You may distribute the software in object code or binary form,
26 | provided that you do at least ONE of the following:
27 |
28 | a. distribute the binaries and library files of the software,
29 | together with instructions (in the manual page or equivalent)
30 | on where to get the original distribution.
31 |
32 | b. accompany the distribution with the machine-readable source of
33 | the software.
34 |
35 | c. give non-standard binaries non-standard names, with
36 | instructions on where to get the original software distribution.
37 |
38 | d. make other distribution arrangements with the author.
39 |
40 | 4. You may modify and include the part of the software into any other
41 | software (possibly commercial). But some files in the distribution
42 | are not written by the author, so that they are not under these terms.
43 |
44 | For the list of those files and their copying conditions, see the
45 | file LEGAL.
46 |
47 | 5. The scripts and library files supplied as input to or produced as
48 | output from the software do not automatically fall under the
49 | copyright of the software, but belong to whomever generated them,
50 | and may be sold commercially, and may be aggregated with this
51 | software.
52 |
53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
56 | PURPOSE.
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Introduction
2 |
3 | CGI is a large class, providing several categories of methods, many of which
4 | are mixed in from other modules. Some of the documentation is in this class,
5 | some in the modules CGI::QueryExtension and CGI::HtmlExtension. See
6 | CGI::Cookie for specific information on handling cookies, and cgi/session.rb
7 | (CGI::Session) for information on sessions.
8 |
9 | For queries, CGI provides methods to get at environmental variables,
10 | parameters, cookies, and multipart request data. For responses, CGI provides
11 | methods for writing output and generating HTML.
12 |
13 | Read on for more details. Examples are provided at the bottom.
14 |
15 | ## Installation
16 |
17 | Add this line to your application's Gemfile:
18 |
19 | ```ruby
20 | gem 'cgi'
21 | ```
22 |
23 | And then execute:
24 |
25 | ```bash
26 | bundle
27 | ```
28 |
29 | Or install it yourself as:
30 |
31 | ```bash
32 | gem install cgi
33 | ```
34 |
35 | ## Usage
36 |
37 | ### Get form values
38 |
39 | Given a form with the content `field_name=123`:
40 |
41 | ```ruby
42 | require "cgi"
43 | cgi = CGI.new
44 | value = cgi['field_name'] # => "123"
45 | cgi['flowerpot'] # => ""
46 | fields = cgi.keys # => [ "field_name" ]
47 |
48 | cgi.has_key?('field_name') # => true
49 | cgi.include?('field_name') # => true
50 | cgi.include?('flowerpot') # => false
51 | ```
52 |
53 | ### Get form values as hash
54 |
55 | ```ruby
56 | require "cgi"
57 | cgi = CGI.new
58 | params = cgi.params
59 | ```
60 |
61 | cgi.params is a hash.
62 |
63 | ```ruby
64 | cgi.params['new_field_name'] = ["value"] # add new param
65 | cgi.params['field_name'] = ["new_value"] # change value
66 | cgi.params.delete('field_name') # delete param
67 | cgi.params.clear # delete all params
68 | ```
69 |
70 | ### Save form values to file
71 |
72 | ```ruby
73 | require "pstore"
74 | db = PStore.new("query.db")
75 | db.transaction do
76 | db["params"] = cgi.params
77 | end
78 | ```
79 |
80 | ### Restore form values from file
81 |
82 | ```ruby
83 | require "pstore"
84 | db = PStore.new("query.db")
85 | db.transaction do
86 | cgi.params = db["params"]
87 | end
88 | ```
89 |
90 | ## Development
91 |
92 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
93 |
94 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
95 |
96 | ## Contributing
97 |
98 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/cgi.
99 |
--------------------------------------------------------------------------------
/lib/cgi/session/pstore.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | #
3 | # cgi/session/pstore.rb - persistent storage of marshalled session data
4 | #
5 | # Documentation: William Webber (william@williamwebber.com)
6 | #
7 | # == Overview
8 | #
9 | # This file provides the CGI::Session::PStore class, which builds
10 | # persistent of session data on top of the pstore library. See
11 | # cgi/session.rb for more details on session storage managers.
12 |
13 | require_relative '../session'
14 | begin
15 | require 'pstore'
16 | rescue LoadError
17 | end
18 |
19 | class CGI
20 | class Session
21 | # PStore-based session storage class.
22 | #
23 | # This builds upon the top-level PStore class provided by the
24 | # library file pstore.rb. Session data is marshalled and stored
25 | # in a file. File locking and transaction services are provided.
26 | class PStore
27 | # Create a new CGI::Session::PStore instance
28 | #
29 | # This constructor is used internally by CGI::Session. The
30 | # user does not generally need to call it directly.
31 | #
32 | # +session+ is the session for which this instance is being
33 | # created. The session id must only contain alphanumeric
34 | # characters; automatically generated session ids observe
35 | # this requirement.
36 | #
37 | # +option+ is a hash of options for the initializer. The
38 | # following options are recognised:
39 | #
40 | # tmpdir:: the directory to use for storing the PStore
41 | # file. Defaults to Dir::tmpdir (generally "/tmp"
42 | # on Unix systems).
43 | # prefix:: the prefix to add to the session id when generating
44 | # the filename for this session's PStore file.
45 | # Defaults to the empty string.
46 | #
47 | # This session's PStore file will be created if it does
48 | # not exist, or opened if it does.
49 | def initialize(session, option={})
50 | option = {'suffix'=>''}.update(option)
51 | path, @hash = session.new_store_file(option)
52 | @p = ::PStore.new(path)
53 | @p.transaction do |p|
54 | File.chmod(0600, p.path)
55 | end
56 | end
57 |
58 | # Restore session state from the session's PStore file.
59 | #
60 | # Returns the session state as a hash.
61 | def restore
62 | unless @hash
63 | @p.transaction do
64 | @hash = @p['hash'] || {}
65 | end
66 | end
67 | @hash
68 | end
69 |
70 | # Save session state to the session's PStore file.
71 | def update
72 | @p.transaction do
73 | @p['hash'] = @hash
74 | end
75 | end
76 |
77 | # Update and close the session's PStore file.
78 | def close
79 | update
80 | end
81 |
82 | # Close and delete the session's PStore file.
83 | def delete
84 | path = @p.path
85 | File::unlink path
86 | end
87 |
88 | end if defined?(::PStore)
89 | end
90 | end
91 | # :enddoc:
92 |
--------------------------------------------------------------------------------
/test/cgi/test_cgi_modruby.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require 'test/unit'
3 | require 'cgi'
4 | require_relative 'update_env'
5 |
6 |
7 | class CGIModrubyTest < Test::Unit::TestCase
8 | include UpdateEnv
9 |
10 |
11 | def setup
12 | @environ = {}
13 | update_env(
14 | 'SERVER_PROTOCOL' => 'HTTP/1.1',
15 | 'REQUEST_METHOD' => 'GET',
16 | #'QUERY_STRING' => 'a=foo&b=bar',
17 | )
18 | CGI.class_eval { const_set(:MOD_RUBY, true) }
19 | Apache._reset()
20 | #@cgi = CGI.new
21 | #@req = Apache.request
22 | end
23 |
24 |
25 | def teardown
26 | ENV.update(@environ)
27 | CGI.class_eval { remove_const(:MOD_RUBY) }
28 | end
29 |
30 |
31 | def test_cgi_modruby_simple
32 | req = Apache.request
33 | cgi = CGI.new
34 | assert(req._setup_cgi_env_invoked?)
35 | assert(! req._send_http_header_invoked?)
36 | actual = cgi.http_header
37 | assert_equal('', actual)
38 | assert_equal('text/html', req.content_type)
39 | assert(req._send_http_header_invoked?)
40 | end
41 |
42 |
43 | def test_cgi_modruby_complex
44 | req = Apache.request
45 | cgi = CGI.new
46 | options = {
47 | 'status' => 'FORBIDDEN',
48 | 'location' => 'http://www.example.com/',
49 | 'type' => 'image/gif',
50 | 'content-encoding' => 'deflate',
51 | 'cookie' => [ CGI::Cookie.new('name1', 'abc', '123'),
52 | CGI::Cookie.new('name'=>'name2', 'value'=>'value2', 'secure'=>true),
53 | ],
54 | }
55 | assert(req._setup_cgi_env_invoked?)
56 | assert(! req._send_http_header_invoked?)
57 | actual = cgi.http_header(options)
58 | assert_equal('', actual)
59 | assert_equal('image/gif', req.content_type)
60 | assert_equal('403 Forbidden', req.status_line)
61 | assert_equal(403, req.status)
62 | assert_equal('deflate', req.content_encoding)
63 | assert_equal('http://www.example.com/', req.headers_out['location'])
64 | assert_equal(["name1=abc&123; path=", "name2=value2; path=; secure"],
65 | req.headers_out['Set-Cookie'])
66 | assert(req._send_http_header_invoked?)
67 | end
68 |
69 |
70 | def test_cgi_modruby_location
71 | req = Apache.request
72 | cgi = CGI.new
73 | options = {
74 | 'status' => '200 OK',
75 | 'location' => 'http://www.example.com/',
76 | }
77 | cgi.http_header(options)
78 | assert_equal('200 OK', req.status_line) # should be '302 Found' ?
79 | assert_equal(302, req.status)
80 | assert_equal('http://www.example.com/', req.headers_out['location'])
81 | end
82 |
83 |
84 | def test_cgi_modruby_requestparams
85 | req = Apache.request
86 | req.args = 'a=foo&b=bar'
87 | cgi = CGI.new
88 | assert_equal('foo', cgi['a'])
89 | assert_equal('bar', cgi['b'])
90 | end
91 |
92 |
93 | instance_methods.each do |method|
94 | private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']
95 | end if ENV['TEST']
96 |
97 | end
98 |
99 |
100 |
101 | ## dummy class for mod_ruby
102 | class Apache #:nodoc:
103 |
104 | def self._reset
105 | @request = Request.new
106 | end
107 |
108 | def self.request
109 | return @request
110 | end
111 |
112 | class Request
113 |
114 | def initialize
115 | hash = {}
116 | def hash.add(name, value)
117 | (self[name] ||= []) << value
118 | end
119 | @http_header = nil
120 | @headers_out = hash
121 | @status_line = nil
122 | @status = nil
123 | @content_type = nil
124 | @content_encoding = nil
125 | end
126 | attr_accessor :headers_out, :status_line, :status, :content_type, :content_encoding
127 |
128 | attr_accessor :args
129 | #def args
130 | # return ENV['QUERY_STRING']
131 | #end
132 |
133 | def send_http_header
134 | @http_header = '*invoked*'
135 | end
136 | def _send_http_header_invoked?
137 | @http_header ? true : false
138 | end
139 |
140 | def setup_cgi_env
141 | @cgi_env = '*invoked*'
142 | end
143 | def _setup_cgi_env_invoked?
144 | @cgi_env ? true : false
145 | end
146 |
147 | end
148 |
149 | end
150 |
--------------------------------------------------------------------------------
/test/cgi/test_cgi_session.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require 'test/unit'
3 | require 'cgi'
4 | require 'cgi/session'
5 | require 'cgi/session/pstore'
6 | require 'stringio'
7 | require 'tmpdir'
8 | require_relative 'update_env'
9 |
10 | class CGISessionTest < Test::Unit::TestCase
11 | include UpdateEnv
12 |
13 | def setup
14 | @environ = {}
15 | @session_dir = Dir.mktmpdir(%w'session dir')
16 | end
17 |
18 | def teardown
19 | ENV.update(@environ)
20 | $stdout = STDOUT
21 | FileUtils.rm_rf(@session_dir)
22 | end
23 |
24 | def test_cgi_session_filestore
25 | update_env(
26 | 'REQUEST_METHOD' => 'GET',
27 | # 'QUERY_STRING' => 'id=123&id=456&id=&str=%40h+%3D%7E+%2F%5E%24%2F',
28 | # 'HTTP_COOKIE' => '_session_id=12345; name1=val1&val2;',
29 | 'SERVER_SOFTWARE' => 'Apache 2.2.0',
30 | 'SERVER_PROTOCOL' => 'HTTP/1.1',
31 | )
32 | value1="value1"
33 | value2="\x8F\xBC\x8D]".dup
34 | value2.force_encoding("SJIS") if defined?(::Encoding)
35 | cgi = CGI.new
36 | session = CGI::Session.new(cgi,"tmpdir"=>@session_dir)
37 | session["key1"]=value1
38 | session["key2"]=value2
39 | assert_equal(value1,session["key1"])
40 | assert_equal(value2,session["key2"])
41 | session.close
42 | $stdout = StringIO.new
43 | cgi.out{""}
44 |
45 | update_env(
46 | 'REQUEST_METHOD' => 'GET',
47 | # 'HTTP_COOKIE' => "_session_id=#{session_id}",
48 | 'QUERY_STRING' => "_session_id=#{session.session_id}",
49 | 'SERVER_SOFTWARE' => 'Apache 2.2.0',
50 | 'SERVER_PROTOCOL' => 'HTTP/1.1',
51 | )
52 | cgi = CGI.new
53 | session = CGI::Session.new(cgi,"tmpdir"=>@session_dir)
54 | $stdout = StringIO.new
55 | assert_equal(value1,session["key1"])
56 | assert_equal(value2,session["key2"])
57 | session.close
58 |
59 | end
60 | def test_cgi_session_pstore
61 | update_env(
62 | 'REQUEST_METHOD' => 'GET',
63 | # 'QUERY_STRING' => 'id=123&id=456&id=&str=%40h+%3D%7E+%2F%5E%24%2F',
64 | # 'HTTP_COOKIE' => '_session_id=12345; name1=val1&val2;',
65 | 'SERVER_SOFTWARE' => 'Apache 2.2.0',
66 | 'SERVER_PROTOCOL' => 'HTTP/1.1',
67 | )
68 | value1="value1"
69 | value2="\x8F\xBC\x8D]".dup
70 | value2.force_encoding("SJIS") if defined?(::Encoding)
71 | cgi = CGI.new
72 | session = CGI::Session.new(cgi,"tmpdir"=>@session_dir,"database_manager"=>CGI::Session::PStore)
73 | session["key1"]=value1
74 | session["key2"]=value2
75 | assert_equal(value1,session["key1"])
76 | assert_equal(value2,session["key2"])
77 | session.close
78 | $stdout = StringIO.new
79 | cgi.out{""}
80 |
81 | update_env(
82 | 'REQUEST_METHOD' => 'GET',
83 | # 'HTTP_COOKIE' => "_session_id=#{session_id}",
84 | 'QUERY_STRING' => "_session_id=#{session.session_id}",
85 | 'SERVER_SOFTWARE' => 'Apache 2.2.0',
86 | 'SERVER_PROTOCOL' => 'HTTP/1.1',
87 | )
88 | cgi = CGI.new
89 | session = CGI::Session.new(cgi,"tmpdir"=>@session_dir,"database_manager"=>CGI::Session::PStore)
90 | $stdout = StringIO.new
91 | assert_equal(value1,session["key1"])
92 | assert_equal(value2,session["key2"])
93 | session.close
94 | end if defined?(::PStore)
95 | def test_cgi_session_specify_session_id
96 | update_env(
97 | 'REQUEST_METHOD' => 'GET',
98 | # 'QUERY_STRING' => 'id=123&id=456&id=&str=%40h+%3D%7E+%2F%5E%24%2F',
99 | # 'HTTP_COOKIE' => '_session_id=12345; name1=val1&val2;',
100 | 'SERVER_SOFTWARE' => 'Apache 2.2.0',
101 | 'SERVER_PROTOCOL' => 'HTTP/1.1',
102 | )
103 | value1="value1"
104 | value2="\x8F\xBC\x8D]".dup
105 | value2.force_encoding("SJIS") if defined?(::Encoding)
106 | cgi = CGI.new
107 | session = CGI::Session.new(cgi,"tmpdir"=>@session_dir,"session_id"=>"foo")
108 | session["key1"]=value1
109 | session["key2"]=value2
110 | assert_equal(value1,session["key1"])
111 | assert_equal(value2,session["key2"])
112 | assert_equal("foo",session.session_id)
113 | #session_id=session.session_id
114 | session.close
115 | $stdout = StringIO.new
116 | cgi.out{""}
117 |
118 | update_env(
119 | 'REQUEST_METHOD' => 'GET',
120 | # 'HTTP_COOKIE' => "_session_id=#{session_id}",
121 | 'QUERY_STRING' => "_session_id=#{session.session_id}",
122 | 'SERVER_SOFTWARE' => 'Apache 2.2.0',
123 | 'SERVER_PROTOCOL' => 'HTTP/1.1',
124 | )
125 | cgi = CGI.new
126 | session = CGI::Session.new(cgi,"tmpdir"=>@session_dir)
127 | $stdout = StringIO.new
128 | assert_equal(value1,session["key1"])
129 | assert_equal(value2,session["key2"])
130 | assert_equal("foo",session.session_id)
131 | session.close
132 | end
133 | def test_cgi_session_specify_session_key
134 | update_env(
135 | 'REQUEST_METHOD' => 'GET',
136 | # 'QUERY_STRING' => 'id=123&id=456&id=&str=%40h+%3D%7E+%2F%5E%24%2F',
137 | # 'HTTP_COOKIE' => '_session_id=12345; name1=val1&val2;',
138 | 'SERVER_SOFTWARE' => 'Apache 2.2.0',
139 | 'SERVER_PROTOCOL' => 'HTTP/1.1',
140 | )
141 | value1="value1"
142 | value2="\x8F\xBC\x8D]".dup
143 | value2.force_encoding("SJIS") if defined?(::Encoding)
144 | cgi = CGI.new
145 | session = CGI::Session.new(cgi,"tmpdir"=>@session_dir,"session_key"=>"bar")
146 | session["key1"]=value1
147 | session["key2"]=value2
148 | assert_equal(value1,session["key1"])
149 | assert_equal(value2,session["key2"])
150 | session_id=session.session_id
151 | session.close
152 | $stdout = StringIO.new
153 | cgi.out{""}
154 |
155 | update_env(
156 | 'REQUEST_METHOD' => 'GET',
157 | 'HTTP_COOKIE' => "bar=#{session_id}",
158 | # 'QUERY_STRING' => "bar=#{session.session_id}",
159 | 'SERVER_SOFTWARE' => 'Apache 2.2.0',
160 | 'SERVER_PROTOCOL' => 'HTTP/1.1',
161 | )
162 | cgi = CGI.new
163 | session = CGI::Session.new(cgi,"tmpdir"=>@session_dir,"session_key"=>"bar")
164 | $stdout = StringIO.new
165 | assert_equal(value1,session["key1"])
166 | assert_equal(value2,session["key2"])
167 | session.close
168 | end
169 | end
170 |
--------------------------------------------------------------------------------
/test/cgi/test_cgi_header.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require 'test/unit'
3 | require 'cgi'
4 | require 'time'
5 | require_relative 'update_env'
6 |
7 |
8 | class CGIHeaderTest < Test::Unit::TestCase
9 | include UpdateEnv
10 |
11 |
12 | def setup
13 | @environ = {}
14 | update_env(
15 | 'SERVER_PROTOCOL' => 'HTTP/1.1',
16 | 'REQUEST_METHOD' => 'GET',
17 | 'SERVER_SOFTWARE' => 'Apache 2.2.0',
18 | )
19 | end
20 |
21 |
22 | def teardown
23 | ENV.update(@environ)
24 | end
25 |
26 |
27 | def test_cgi_http_header_simple
28 | cgi = CGI.new
29 | ## default content type
30 | expected = "Content-Type: text/html\r\n\r\n"
31 | actual = cgi.http_header
32 | assert_equal(expected, actual)
33 | ## content type specified as string
34 | expected = "Content-Type: text/xhtml; charset=utf8\r\n\r\n"
35 | actual = cgi.http_header('text/xhtml; charset=utf8')
36 | assert_equal(expected, actual)
37 | ## content type specified as hash
38 | expected = "Content-Type: image/png\r\n\r\n"
39 | actual = cgi.http_header('type'=>'image/png')
40 | assert_equal(expected, actual)
41 | ## charset specified
42 | expected = "Content-Type: text/html; charset=utf8\r\n\r\n"
43 | actual = cgi.http_header('charset'=>'utf8')
44 | assert_equal(expected, actual)
45 | end
46 |
47 |
48 | def test_cgi_http_header_complex
49 | cgi = CGI.new
50 | options = {
51 | 'type' => 'text/xhtml',
52 | 'charset' => 'utf8',
53 | 'status' => 'REDIRECT',
54 | 'server' => 'webrick',
55 | 'connection' => 'close',
56 | 'length' => 123,
57 | 'language' => 'ja',
58 | 'expires' => Time.gm(2000, 1, 23, 12, 34, 56),
59 | 'location' => 'http://www.ruby-lang.org/',
60 | }
61 | expected = "Status: 302 Found\r\n".dup
62 | expected << "Server: webrick\r\n"
63 | expected << "Connection: close\r\n"
64 | expected << "Content-Type: text/xhtml; charset=utf8\r\n"
65 | expected << "Content-Length: 123\r\n"
66 | expected << "Content-Language: ja\r\n"
67 | expected << "Expires: Sun, 23 Jan 2000 12:34:56 GMT\r\n"
68 | expected << "location: http://www.ruby-lang.org/\r\n"
69 | expected << "\r\n"
70 | actual = cgi.http_header(options)
71 | assert_equal(expected, actual)
72 | end
73 |
74 |
75 | def test_cgi_http_header_argerr
76 | cgi = CGI.new
77 | expected = ArgumentError
78 |
79 | assert_raise(expected) do
80 | cgi.http_header(nil)
81 | end
82 | end
83 |
84 |
85 | def test_cgi_http_header_cookie
86 | cgi = CGI.new
87 | cookie1 = CGI::Cookie.new('name1', 'abc', '123')
88 | cookie2 = CGI::Cookie.new('name'=>'name2', 'value'=>'value2', 'secure'=>true)
89 | ctype = "Content-Type: text/html\r\n"
90 | sep = "\r\n"
91 | c1 = "Set-Cookie: name1=abc&123; path=\r\n"
92 | c2 = "Set-Cookie: name2=value2; path=; secure\r\n"
93 | ## CGI::Cookie object
94 | actual = cgi.http_header('cookie'=>cookie1)
95 | expected = ctype + c1 + sep
96 | assert_equal(expected, actual)
97 | ## String
98 | actual = cgi.http_header('cookie'=>cookie2.to_s)
99 | expected = ctype + c2 + sep
100 | assert_equal(expected, actual)
101 | ## Array
102 | actual = cgi.http_header('cookie'=>[cookie1, cookie2])
103 | expected = ctype + c1 + c2 + sep
104 | assert_equal(expected, actual)
105 | ## Hash
106 | actual = cgi.http_header('cookie'=>{'name1'=>cookie1, 'name2'=>cookie2})
107 | expected = ctype + c1 + c2 + sep
108 | assert_equal(expected, actual)
109 | end
110 |
111 |
112 | def test_cgi_http_header_output_cookies
113 | cgi = CGI.new
114 | ## output cookies
115 | cookies = [ CGI::Cookie.new('name1', 'abc', '123'),
116 | CGI::Cookie.new('name'=>'name2', 'value'=>'value2', 'secure'=>true),
117 | ]
118 | cgi.instance_variable_set('@output_cookies', cookies)
119 | expected = "Content-Type: text/html; charset=utf8\r\n".dup
120 | expected << "Set-Cookie: name1=abc&123; path=\r\n"
121 | expected << "Set-Cookie: name2=value2; path=; secure\r\n"
122 | expected << "\r\n"
123 | ## header when string
124 | actual = cgi.http_header('text/html; charset=utf8')
125 | assert_equal(expected, actual)
126 | ## _header_for_string
127 | actual = cgi.http_header('type'=>'text/html', 'charset'=>'utf8')
128 | assert_equal(expected, actual)
129 | end
130 |
131 |
132 | def test_cgi_http_header_nph
133 | time_start = Time.now.to_i
134 | cgi = CGI.new
135 | ## 'nph' is true
136 | ENV['SERVER_SOFTWARE'] = 'Apache 2.2.0'
137 | actual1 = cgi.http_header('nph'=>true)
138 | ## when old IIS, NPH-mode is forced
139 | ENV['SERVER_SOFTWARE'] = 'IIS/4.0'
140 | actual2 = cgi.http_header
141 | actual3 = cgi.http_header('status'=>'REDIRECT', 'location'=>'http://www.example.com/')
142 | ## newer IIS doesn't require NPH-mode ## [ruby-dev:30537]
143 | ENV['SERVER_SOFTWARE'] = 'IIS/5.0'
144 | actual4 = cgi.http_header
145 | actual5 = cgi.http_header('status'=>'REDIRECT', 'location'=>'http://www.example.com/')
146 | time_end = Time.now.to_i
147 | date = /^Date: ([A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d\d:\d\d:\d\d GMT)\r\n/
148 | [actual1, actual2, actual3].each do |actual|
149 | assert_match(date, actual)
150 | assert_include(time_start..time_end, date =~ actual && Time.parse($1).to_i)
151 | actual.sub!(date, "Date: DATE_IS_REMOVED\r\n")
152 | end
153 | ## assertion
154 | expected = "HTTP/1.1 200 OK\r\n".dup
155 | expected << "Date: DATE_IS_REMOVED\r\n"
156 | expected << "Server: Apache 2.2.0\r\n"
157 | expected << "Connection: close\r\n"
158 | expected << "Content-Type: text/html\r\n"
159 | expected << "\r\n"
160 | assert_equal(expected, actual1)
161 | expected.sub!(/^Server: .*?\r\n/, "Server: IIS/4.0\r\n")
162 | assert_equal(expected, actual2)
163 | expected.sub!(/^HTTP\/1.1 200 OK\r\n/, "HTTP/1.1 302 Found\r\n")
164 | expected.sub!(/\r\n\r\n/, "\r\nlocation: http://www.example.com/\r\n\r\n")
165 | assert_equal(expected, actual3)
166 | expected = "Content-Type: text/html\r\n".dup
167 | expected << "\r\n"
168 | assert_equal(expected, actual4)
169 | expected = "Status: 302 Found\r\n".dup
170 | expected << "Content-Type: text/html\r\n"
171 | expected << "location: http://www.example.com/\r\n"
172 | expected << "\r\n"
173 | assert_equal(expected, actual5)
174 | ensure
175 | ENV.delete('SERVER_SOFTWARE')
176 | end
177 |
178 |
179 | def test_cgi_http_header_crlf_injection
180 | cgi = CGI.new
181 | assert_raise(RuntimeError) { cgi.http_header("text/xhtml\r\nBOO") }
182 | assert_raise(RuntimeError) { cgi.http_header("type" => "text/xhtml\r\nBOO") }
183 | assert_raise(RuntimeError) { cgi.http_header("status" => "200 OK\r\nBOO") }
184 | assert_raise(RuntimeError) { cgi.http_header("location" => "text/xhtml\r\nBOO") }
185 | end
186 |
187 |
188 | instance_methods.each do |method|
189 | private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']
190 | end if ENV['TEST']
191 |
192 | end
193 |
--------------------------------------------------------------------------------
/test/cgi/test_cgi_cookie.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require 'test/unit'
3 | require 'cgi'
4 | require 'stringio'
5 | require_relative 'update_env'
6 |
7 |
8 | class CGICookieTest < Test::Unit::TestCase
9 | include UpdateEnv
10 |
11 |
12 | def setup
13 | @environ = {}
14 | update_env(
15 | 'REQUEST_METHOD' => 'GET',
16 | 'SCRIPT_NAME' => nil,
17 | )
18 | @str1="\xE3\x82\x86\xE3\x82\x93\xE3\x82\x86\xE3\x82\x93".dup
19 | @str1.force_encoding("UTF-8") if defined?(::Encoding)
20 | end
21 |
22 | def teardown
23 | ENV.update(@environ)
24 | end
25 |
26 |
27 | def test_cgi_cookie_new_simple
28 | cookie = CGI::Cookie.new('name1', 'val1', '&<>"', @str1)
29 | assert_equal('name1', cookie.name)
30 | assert_equal(['val1', '&<>"', @str1], cookie.value)
31 | assert_nil(cookie.domain)
32 | assert_nil(cookie.expires)
33 | assert_equal('', cookie.path)
34 | assert_equal(false, cookie.secure)
35 | assert_equal(false, cookie.httponly)
36 | assert_equal("name1=val1&%26%3C%3E%22&%E3%82%86%E3%82%93%E3%82%86%E3%82%93; path=", cookie.to_s)
37 | end
38 |
39 |
40 | def test_cgi_cookie_new_complex
41 | t = Time.gm(2030, 12, 31, 23, 59, 59)
42 | value = ['val1', '&<>"', "\xA5\xE0\xA5\xB9\xA5\xAB".dup]
43 | value[2].force_encoding("EUC-JP") if defined?(::Encoding)
44 | cookie = CGI::Cookie.new('name'=>'name1',
45 | 'value'=>value,
46 | 'path'=>'/cgi-bin/myapp/',
47 | 'domain'=>'www.example.com',
48 | 'expires'=>t,
49 | 'secure'=>true,
50 | 'httponly'=>true
51 | )
52 | assert_equal('name1', cookie.name)
53 | assert_equal(value, cookie.value)
54 | assert_equal('www.example.com', cookie.domain)
55 | assert_equal(t, cookie.expires)
56 | assert_equal('/cgi-bin/myapp/', cookie.path)
57 | assert_equal(true, cookie.secure)
58 | assert_equal(true, cookie.httponly)
59 | assert_equal('name1=val1&%26%3C%3E%22&%A5%E0%A5%B9%A5%AB; domain=www.example.com; path=/cgi-bin/myapp/; expires=Tue, 31 Dec 2030 23:59:59 GMT; secure; HttpOnly', cookie.to_s)
60 | end
61 |
62 |
63 | def test_cgi_cookie_new_with_domain
64 | h = {'name'=>'name1', 'value'=>'value1'}
65 | cookie = CGI::Cookie.new(h.merge('domain'=>'a.example.com'))
66 | assert_equal('a.example.com', cookie.domain)
67 |
68 | cookie = CGI::Cookie.new(h.merge('domain'=>'.example.com'))
69 | assert_equal('.example.com', cookie.domain)
70 |
71 | cookie = CGI::Cookie.new(h.merge('domain'=>'1.example.com'))
72 | assert_equal('1.example.com', cookie.domain, 'enhanced by RFC 1123')
73 |
74 | assert_raise(ArgumentError) {
75 | CGI::Cookie.new(h.merge('domain'=>'-a.example.com'))
76 | }
77 |
78 | assert_raise(ArgumentError) {
79 | CGI::Cookie.new(h.merge('domain'=>'a-.example.com'))
80 | }
81 | end
82 |
83 |
84 | def test_cgi_cookie_scriptname
85 | cookie = CGI::Cookie.new('name1', 'value1')
86 | assert_equal('', cookie.path)
87 | cookie = CGI::Cookie.new('name'=>'name1', 'value'=>'value1')
88 | assert_equal('', cookie.path)
89 | ## when ENV['SCRIPT_NAME'] is set, cookie.path is set automatically
90 | ENV['SCRIPT_NAME'] = '/cgi-bin/app/example.cgi'
91 | cookie = CGI::Cookie.new('name1', 'value1')
92 | assert_equal('/cgi-bin/app/', cookie.path)
93 | cookie = CGI::Cookie.new('name'=>'name1', 'value'=>'value1')
94 | assert_equal('/cgi-bin/app/', cookie.path)
95 | end
96 |
97 |
98 | def test_cgi_cookie_parse
99 | ## ';' separator
100 | cookie_str = 'name1=val1&val2; name2=val2&%26%3C%3E%22&%E3%82%86%E3%82%93%E3%82%86%E3%82%93;_session_id=12345'
101 | cookies = CGI::Cookie.parse(cookie_str)
102 | list = [
103 | ['name1', ['val1', 'val2']],
104 | ['name2', ['val2', '&<>"',@str1]],
105 | ['_session_id', ['12345']],
106 | ]
107 | list.each do |name, value|
108 | cookie = cookies[name]
109 | assert_equal(name, cookie.name)
110 | assert_equal(value, cookie.value)
111 | end
112 | ## don't allow ',' separator
113 | cookie_str = 'name1=val1&val2, name2=val2'
114 | cookies = CGI::Cookie.parse(cookie_str)
115 | list = [
116 | ['name1', ['val1', 'val2, name2=val2']],
117 | ]
118 | list.each do |name, value|
119 | cookie = cookies[name]
120 | assert_equal(name, cookie.name)
121 | assert_equal(value, cookie.value)
122 | end
123 | end
124 |
125 | def test_cgi_cookie_parse_not_decode_name
126 | cookie_str = "%66oo=baz;foo=bar"
127 | cookies = CGI::Cookie.parse(cookie_str)
128 | assert_equal({"%66oo" => ["baz"], "foo" => ["bar"]}, cookies)
129 | end
130 |
131 | def test_cgi_cookie_arrayinterface
132 | cookie = CGI::Cookie.new('name1', 'a', 'b', 'c')
133 | assert_equal('a', cookie[0])
134 | assert_equal('c', cookie[2])
135 | assert_nil(cookie[3])
136 | assert_equal('a', cookie.first)
137 | assert_equal('c', cookie.last)
138 | assert_equal(['A', 'B', 'C'], cookie.collect{|e| e.upcase})
139 | end
140 |
141 |
142 | def test_cgi_cookie_domain_injection_into_name
143 | name = "a=b; domain=example.com;"
144 | path = "/"
145 | domain = "example.jp"
146 | assert_raise(ArgumentError) do
147 | CGI::Cookie.new('name' => name,
148 | 'value' => "value",
149 | 'domain' => domain,
150 | 'path' => path)
151 | end
152 | end
153 |
154 |
155 | def test_cgi_cookie_newline_injection_into_name
156 | name = "a=b;\r\nLocation: http://example.com#"
157 | path = "/"
158 | domain = "example.jp"
159 | assert_raise(ArgumentError) do
160 | CGI::Cookie.new('name' => name,
161 | 'value' => "value",
162 | 'domain' => domain,
163 | 'path' => path)
164 | end
165 | end
166 |
167 |
168 | def test_cgi_cookie_multibyte_injection_into_name
169 | name = "a=b;\u3042"
170 | path = "/"
171 | domain = "example.jp"
172 | assert_raise(ArgumentError) do
173 | CGI::Cookie.new('name' => name,
174 | 'value' => "value",
175 | 'domain' => domain,
176 | 'path' => path)
177 | end
178 | end
179 |
180 |
181 | def test_cgi_cookie_injection_into_path
182 | name = "name"
183 | path = "/; samesite=none"
184 | domain = "example.jp"
185 | assert_raise(ArgumentError) do
186 | CGI::Cookie.new('name' => name,
187 | 'value' => "value",
188 | 'domain' => domain,
189 | 'path' => path)
190 | end
191 | end
192 |
193 |
194 | def test_cgi_cookie_injection_into_domain
195 | name = "name"
196 | path = "/"
197 | domain = "example.jp; samesite=none"
198 | assert_raise(ArgumentError) do
199 | CGI::Cookie.new('name' => name,
200 | 'value' => "value",
201 | 'domain' => domain,
202 | 'path' => path)
203 | end
204 | end
205 |
206 |
207 | instance_methods.each do |method|
208 | private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']
209 | end if ENV['TEST']
210 |
211 | end
212 |
--------------------------------------------------------------------------------
/lib/cgi/cookie.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | require_relative 'util'
3 | class CGI
4 | # Class representing an HTTP cookie.
5 | #
6 | # In addition to its specific fields and methods, a Cookie instance
7 | # is a delegator to the array of its values.
8 | #
9 | # See RFC 2965.
10 | #
11 | # == Examples of use
12 | # cookie1 = CGI::Cookie.new("name", "value1", "value2", ...)
13 | # cookie1 = CGI::Cookie.new("name" => "name", "value" => "value")
14 | # cookie1 = CGI::Cookie.new('name' => 'name',
15 | # 'value' => ['value1', 'value2', ...],
16 | # 'path' => 'path', # optional
17 | # 'domain' => 'domain', # optional
18 | # 'expires' => Time.now, # optional
19 | # 'secure' => true, # optional
20 | # 'httponly' => true # optional
21 | # )
22 | #
23 | # cgi.out("cookie" => [cookie1, cookie2]) { "string" }
24 | #
25 | # name = cookie1.name
26 | # values = cookie1.value
27 | # path = cookie1.path
28 | # domain = cookie1.domain
29 | # expires = cookie1.expires
30 | # secure = cookie1.secure
31 | # httponly = cookie1.httponly
32 | #
33 | # cookie1.name = 'name'
34 | # cookie1.value = ['value1', 'value2', ...]
35 | # cookie1.path = 'path'
36 | # cookie1.domain = 'domain'
37 | # cookie1.expires = Time.now + 30
38 | # cookie1.secure = true
39 | # cookie1.httponly = true
40 | class Cookie < Array
41 | @@accept_charset="UTF-8" unless defined?(@@accept_charset)
42 |
43 | # :stopdoc:
44 | TOKEN_RE = %r"\A[[!-~]&&[^()<>@,;:\\\"/?=\[\]{}]]+\z"
45 | PATH_VALUE_RE = %r"\A[[ -~]&&[^;]]*\z"
46 | DOMAIN_VALUE_RE = %r"\A\.?(?