Skip to content

Commit 03d1124

Browse files
committed
[GR-42970] Update to cgi 0.2.2
PullRequest: truffleruby/3581
2 parents e340de8 + b6a8ba6 commit 03d1124

File tree

7 files changed

+166
-37
lines changed

7 files changed

+166
-37
lines changed

lib/mri/cgi.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@
288288
#
289289

290290
class CGI
291-
VERSION = "0.2.1"
291+
VERSION = "0.2.2"
292292
end
293293

294294
require 'cgi/core'

lib/mri/cgi/cookie.rb

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ class CGI
4040
class Cookie < Array
4141
@@accept_charset="UTF-8" unless defined?(@@accept_charset)
4242

43+
TOKEN_RE = %r"\A[[!-~]&&[^()<>@,;:\\\"/?=\[\]{}]]+\z"
44+
PATH_VALUE_RE = %r"\A[[ -~]&&[^;]]*\z"
45+
DOMAIN_VALUE_RE = %r"\A(?<label>(?!-)[-A-Za-z0-9]+(?<!-))(?:\.\g<label>)*\z"
46+
4347
# Create a new CGI::Cookie object.
4448
#
4549
# :call-seq:
@@ -72,8 +76,8 @@ def initialize(name = "", *value)
7276
@domain = nil
7377
@expires = nil
7478
if name.kind_of?(String)
75-
@name = name
76-
@path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
79+
self.name = name
80+
self.path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
7781
@secure = false
7882
@httponly = false
7983
return super(value)
@@ -84,11 +88,11 @@ def initialize(name = "", *value)
8488
raise ArgumentError, "`name' required"
8589
end
8690

87-
@name = options["name"]
91+
self.name = options["name"]
8892
value = Array(options["value"])
8993
# simple support for IE
90-
@path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
91-
@domain = options["domain"]
94+
self.path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
95+
self.domain = options["domain"]
9296
@expires = options["expires"]
9397
@secure = options["secure"] == true
9498
@httponly = options["httponly"] == true
@@ -97,11 +101,35 @@ def initialize(name = "", *value)
97101
end
98102

99103
# Name of this cookie, as a +String+
100-
attr_accessor :name
104+
attr_reader :name
105+
# Set name of this cookie
106+
def name=(str)
107+
if str and !TOKEN_RE.match?(str)
108+
raise ArgumentError, "invalid name: #{str.dump}"
109+
end
110+
@name = str
111+
end
112+
101113
# Path for which this cookie applies, as a +String+
102-
attr_accessor :path
114+
attr_reader :path
115+
# Set path for which this cookie applies
116+
def path=(str)
117+
if str and !PATH_VALUE_RE.match?(str)
118+
raise ArgumentError, "invalid path: #{str.dump}"
119+
end
120+
@path = str
121+
end
122+
103123
# Domain for which this cookie applies, as a +String+
104-
attr_accessor :domain
124+
attr_reader :domain
125+
# Set domain for which this cookie applies
126+
def domain=(str)
127+
if str and ((str = str.b).bytesize > 255 or !DOMAIN_VALUE_RE.match?(str))
128+
raise ArgumentError, "invalid domain: #{str.dump}"
129+
end
130+
@domain = str
131+
end
132+
105133
# Time at which this cookie expires, as a +Time+
106134
attr_accessor :expires
107135
# True if this cookie is secure; false otherwise

lib/mri/cgi/core.rb

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -188,17 +188,28 @@ def http_header(options='text/html')
188188
# Using #header with the HTML5 tag maker will create a <header> element.
189189
alias :header :http_header
190190

191+
def _no_crlf_check(str)
192+
if str
193+
str = str.to_s
194+
raise "A HTTP status or header field must not include CR and LF" if str =~ /[\r\n]/
195+
str
196+
else
197+
nil
198+
end
199+
end
200+
private :_no_crlf_check
201+
191202
def _header_for_string(content_type) #:nodoc:
192203
buf = ''.dup
193204
if nph?()
194-
buf << "#{$CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0'} 200 OK#{EOL}"
205+
buf << "#{_no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'} 200 OK#{EOL}"
195206
buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
196-
buf << "Server: #{$CGI_ENV['SERVER_SOFTWARE']}#{EOL}"
207+
buf << "Server: #{_no_crlf_check($CGI_ENV['SERVER_SOFTWARE'])}#{EOL}"
197208
buf << "Connection: close#{EOL}"
198209
end
199-
buf << "Content-Type: #{content_type}#{EOL}"
210+
buf << "Content-Type: #{_no_crlf_check(content_type)}#{EOL}"
200211
if @output_cookies
201-
@output_cookies.each {|cookie| buf << "Set-Cookie: #{cookie}#{EOL}" }
212+
@output_cookies.each {|cookie| buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}" }
202213
end
203214
return buf
204215
end # _header_for_string
@@ -213,48 +224,48 @@ def _header_for_hash(options) #:nodoc:
213224
## NPH
214225
options.delete('nph') if defined?(MOD_RUBY)
215226
if options.delete('nph') || nph?()
216-
protocol = $CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0'
227+
protocol = _no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'
217228
status = options.delete('status')
218-
status = HTTP_STATUS[status] || status || '200 OK'
229+
status = HTTP_STATUS[status] || _no_crlf_check(status) || '200 OK'
219230
buf << "#{protocol} #{status}#{EOL}"
220231
buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
221232
options['server'] ||= $CGI_ENV['SERVER_SOFTWARE'] || ''
222233
options['connection'] ||= 'close'
223234
end
224235
## common headers
225236
status = options.delete('status')
226-
buf << "Status: #{HTTP_STATUS[status] || status}#{EOL}" if status
237+
buf << "Status: #{HTTP_STATUS[status] || _no_crlf_check(status)}#{EOL}" if status
227238
server = options.delete('server')
228-
buf << "Server: #{server}#{EOL}" if server
239+
buf << "Server: #{_no_crlf_check(server)}#{EOL}" if server
229240
connection = options.delete('connection')
230-
buf << "Connection: #{connection}#{EOL}" if connection
241+
buf << "Connection: #{_no_crlf_check(connection)}#{EOL}" if connection
231242
type = options.delete('type')
232-
buf << "Content-Type: #{type}#{EOL}" #if type
243+
buf << "Content-Type: #{_no_crlf_check(type)}#{EOL}" #if type
233244
length = options.delete('length')
234-
buf << "Content-Length: #{length}#{EOL}" if length
245+
buf << "Content-Length: #{_no_crlf_check(length)}#{EOL}" if length
235246
language = options.delete('language')
236-
buf << "Content-Language: #{language}#{EOL}" if language
247+
buf << "Content-Language: #{_no_crlf_check(language)}#{EOL}" if language
237248
expires = options.delete('expires')
238249
buf << "Expires: #{CGI.rfc1123_date(expires)}#{EOL}" if expires
239250
## cookie
240251
if cookie = options.delete('cookie')
241252
case cookie
242253
when String, Cookie
243-
buf << "Set-Cookie: #{cookie}#{EOL}"
254+
buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}"
244255
when Array
245256
arr = cookie
246-
arr.each {|c| buf << "Set-Cookie: #{c}#{EOL}" }
257+
arr.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
247258
when Hash
248259
hash = cookie
249-
hash.each_value {|c| buf << "Set-Cookie: #{c}#{EOL}" }
260+
hash.each_value {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
250261
end
251262
end
252263
if @output_cookies
253-
@output_cookies.each {|c| buf << "Set-Cookie: #{c}#{EOL}" }
264+
@output_cookies.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
254265
end
255266
## other headers
256267
options.each do |key, value|
257-
buf << "#{key}: #{value}#{EOL}"
268+
buf << "#{_no_crlf_check(key)}: #{_no_crlf_check(value)}#{EOL}"
258269
end
259270
return buf
260271
end # _header_for_hash

spec/ruby/library/cgi/cookie/name_spec.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,18 @@
66
cookie = CGI::Cookie.new("test-cookie")
77
cookie.name.should == "test-cookie"
88

9-
cookie = CGI::Cookie.new("name" => "another cookie")
10-
cookie.name.should == "another cookie"
9+
cookie = CGI::Cookie.new("name" => "another-cookie")
10+
cookie.name.should == "another-cookie"
1111
end
1212
end
1313

1414
describe "CGI::Cookie#name=" do
1515
it "sets self's expiration date" do
1616
cookie = CGI::Cookie.new("test-cookie")
17-
cookie.name = "another name"
18-
cookie.name.should == "another name"
17+
cookie.name = "another-name"
18+
cookie.name.should == "another-name"
1919

20-
cookie.name = "and one more"
21-
cookie.name.should == "and one more"
20+
cookie.name = "and-one-more"
21+
cookie.name.should == "and-one-more"
2222
end
2323
end

spec/ruby/library/cgi/cookie/parse_spec.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@
66
expected = { "test-cookie" => ["one", "two", "three"] }
77
CGI::Cookie.parse("test-cookie=one&two&three").should == expected
88

9-
expected = { "second cookie" => ["three", "four"], "first cookie" => ["one", "two"] }
10-
CGI::Cookie.parse("first cookie=one&two;second cookie=three&four").should == expected
9+
expected = { "second-cookie" => ["three", "four"], "first-cookie" => ["one", "two"] }
10+
CGI::Cookie.parse("first-cookie=one&two;second-cookie=three&four").should == expected
1111
end
1212

1313
it "does not use , for cookie separators" do
1414
expected = {
15-
"first cookie" => ["one", "two"],
16-
"second cookie" => ["three", "four,third_cookie=five", "six"]
15+
"first-cookie" => ["one", "two"],
16+
"second-cookie" => ["three", "four,third_cookie=five", "six"]
1717
}
18-
CGI::Cookie.parse("first cookie=one&two;second cookie=three&four,third_cookie=five&six").should == expected
18+
CGI::Cookie.parse("first-cookie=one&two;second-cookie=three&four,third_cookie=five&six").should == expected
1919
end
2020

2121
it "unescapes the Cookie values" do

test/mri/tests/cgi/test_cgi_cookie.rb

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,24 @@ def test_cgi_cookie_new_complex
6060
end
6161

6262

63+
def test_cgi_cookie_new_with_domain
64+
h = {'name'=>'name1', 'value'=>'value1'}
65+
cookie = CGI::Cookie.new('domain'=>'a.example.com', **h)
66+
assert_equal('a.example.com', cookie.domain)
67+
68+
cookie = CGI::Cookie.new('domain'=>'1.example.com', **h)
69+
assert_equal('1.example.com', cookie.domain, 'enhanced by RFC 1123')
70+
71+
assert_raise(ArgumentError) {
72+
CGI::Cookie.new('domain'=>'-a.example.com', **h)
73+
}
74+
75+
assert_raise(ArgumentError) {
76+
CGI::Cookie.new('domain'=>'a-.example.com', **h)
77+
}
78+
end
79+
80+
6381
def test_cgi_cookie_scriptname
6482
cookie = CGI::Cookie.new('name1', 'value1')
6583
assert_equal('', cookie.path)
@@ -118,6 +136,70 @@ def test_cgi_cookie_arrayinterface
118136
end
119137

120138

139+
def test_cgi_cookie_domain_injection_into_name
140+
name = "a=b; domain=example.com;"
141+
path = "/"
142+
domain = "example.jp"
143+
assert_raise(ArgumentError) do
144+
CGI::Cookie.new('name' => name,
145+
'value' => "value",
146+
'domain' => domain,
147+
'path' => path)
148+
end
149+
end
150+
151+
152+
def test_cgi_cookie_newline_injection_into_name
153+
name = "a=b;\r\nLocation: http://example.com#"
154+
path = "/"
155+
domain = "example.jp"
156+
assert_raise(ArgumentError) do
157+
CGI::Cookie.new('name' => name,
158+
'value' => "value",
159+
'domain' => domain,
160+
'path' => path)
161+
end
162+
end
163+
164+
165+
def test_cgi_cookie_multibyte_injection_into_name
166+
name = "a=b;\u3042"
167+
path = "/"
168+
domain = "example.jp"
169+
assert_raise(ArgumentError) do
170+
CGI::Cookie.new('name' => name,
171+
'value' => "value",
172+
'domain' => domain,
173+
'path' => path)
174+
end
175+
end
176+
177+
178+
def test_cgi_cookie_injection_into_path
179+
name = "name"
180+
path = "/; samesite=none"
181+
domain = "example.jp"
182+
assert_raise(ArgumentError) do
183+
CGI::Cookie.new('name' => name,
184+
'value' => "value",
185+
'domain' => domain,
186+
'path' => path)
187+
end
188+
end
189+
190+
191+
def test_cgi_cookie_injection_into_domain
192+
name = "name"
193+
path = "/"
194+
domain = "example.jp; samesite=none"
195+
assert_raise(ArgumentError) do
196+
CGI::Cookie.new('name' => name,
197+
'value' => "value",
198+
'domain' => domain,
199+
'path' => path)
200+
end
201+
end
202+
121203

122204
instance_methods.each do |method|
123205
private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']

test/mri/tests/cgi/test_cgi_header.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,14 @@ def test_cgi_http_header_nph
176176
end
177177

178178

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+
179187

180188
instance_methods.each do |method|
181189
private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']

0 commit comments

Comments
 (0)