kcar 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.document +1 -0
- data/.olddoc.yml +13 -6
- data/GIT-VERSION-GEN +2 -2
- data/GNUmakefile +1 -1
- data/HACKING +36 -0
- data/LICENSE +1 -1
- data/README +18 -9
- data/archive/slrnpull.conf +4 -1
- data/ext/kcar/c_util.h +2 -2
- data/ext/kcar/ext_help.h +0 -24
- data/ext/kcar/extconf.rb +30 -5
- data/ext/kcar/kcar.rl +554 -111
- data/ext/kcar/kcar_http_common.rl +36 -5
- data/kcar.gemspec +17 -13
- data/lib/kcar.rb +2 -2
- data/lib/kcar/response.rb +10 -11
- data/pkg.mk +3 -3
- data/test/test_parser.rb +32 -0
- data/test/test_request_parser.rb +1219 -0
- metadata +11 -26
@@ -21,20 +21,49 @@
|
|
21
21
|
pchar = (uchar | ":" | "@" | "&" | "=" | "+");
|
22
22
|
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
|
23
23
|
lws = (" " | "\t");
|
24
|
+
content = ((any -- CTL) | lws);
|
24
25
|
|
25
26
|
# elements
|
26
27
|
token = (ascii -- (CTL | tspecials));
|
28
|
+
|
29
|
+
# URI schemes and absolute paths
|
30
|
+
scheme = ( "http"i ("s"i)? ) $downcase_char >mark %url_scheme;
|
31
|
+
hostname = ((alnum | "-" | "." | "_")+ | ("[" (":" | xdigit)+ "]"));
|
32
|
+
host_with_port = (hostname (":" digit*)?) >mark %host;
|
33
|
+
userinfo = ((unreserved | escape | ";" | ":" | "&" | "=" | "+")+ "@")*;
|
34
|
+
|
35
|
+
path = ( pchar+ ( "/" pchar* )* ) ;
|
36
|
+
query = ( uchar | reserved )* %query_string ;
|
37
|
+
param = ( pchar | "/" )* ;
|
38
|
+
params = ( param ( ";" param )* ) ;
|
39
|
+
rel_path = (path? (";" params)? %request_path) ("?" %start_query query)?;
|
40
|
+
absolute_path = ( "/"+ rel_path );
|
41
|
+
path_uri = absolute_path > mark %request_uri;
|
42
|
+
Absolute_URI = (scheme "://" userinfo host_with_port path_uri);
|
43
|
+
|
44
|
+
Request_URI = ((absolute_path | "*") >mark %request_uri) | Absolute_URI;
|
45
|
+
|
46
|
+
# lets not waste cycles setting fragment in the request,
|
47
|
+
# valid clients do not send it, but we will just silently ignore it.
|
48
|
+
Fragment = ( uchar | reserved )* >mark %fragment;
|
49
|
+
|
50
|
+
Method = (token){1,20} >mark %request_method;
|
51
|
+
GetOnly = "GET" >mark %request_method;
|
52
|
+
|
53
|
+
http_number = ( digit+ "." digit+ ) ;
|
54
|
+
HTTP_Version = ( "HTTP/" http_number ) >mark %http_version ;
|
55
|
+
Request_Line = ( Method " " Request_URI ("#" Fragment){0,1} " "
|
56
|
+
HTTP_Version CRLF ) ;
|
57
|
+
|
27
58
|
phrase = (any -- CRLF)+;
|
28
59
|
Status_Phrase = (digit+ (" "+ phrase)?) >mark %status_phrase ;
|
29
|
-
http_number = (digit+ "." digit+) ;
|
30
|
-
HTTP_Version = ("HTTP/" http_number) >mark %http_version ;
|
31
60
|
Status_Line = HTTP_Version " "+ Status_Phrase :> CRLF;
|
32
61
|
|
33
62
|
field_name = ( token -- ":" )+ >start_field %write_field;
|
34
63
|
|
35
|
-
field_value =
|
64
|
+
field_value = content* >start_value %write_value;
|
36
65
|
|
37
|
-
value_cont = lws+
|
66
|
+
value_cont = lws+ content* >start_value %write_cont_value;
|
38
67
|
|
39
68
|
message_header = ((field_name ":" lws* field_value)|value_cont) :> CRLF;
|
40
69
|
chunk_ext_val = token*;
|
@@ -50,7 +79,9 @@
|
|
50
79
|
Trailers := (message_header)* CRLF @end_trailers;
|
51
80
|
|
52
81
|
FullResponse = Status_Line (message_header)* CRLF @header_done;
|
82
|
+
FullRequest = Request_Line (message_header)* CRLF @header_done;
|
83
|
+
SimpleRequest = GetOnly " " Request_URI ("#"Fragment){0,1} CRLF @header_done;
|
53
84
|
|
54
|
-
main := FullResponse;
|
85
|
+
main := FullResponse | FullRequest | SimpleRequest;
|
55
86
|
|
56
87
|
}%%
|
data/kcar.gemspec
CHANGED
@@ -1,23 +1,27 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'olddoc'
|
4
|
-
extend Olddoc::Gemspec
|
5
|
-
name, summary, title = readme_metadata
|
1
|
+
manifest = File.exist?('.manifest') ?
|
2
|
+
IO.readlines('.manifest').map!(&:chomp!) : `git ls-files`.split("\n")
|
6
3
|
|
7
4
|
Gem::Specification.new do |s|
|
8
5
|
s.name = %q{kcar}
|
9
|
-
s.version = ENV[
|
10
|
-
s.homepage =
|
6
|
+
s.version = (ENV['VERSION'] || '0.7.0').dup
|
7
|
+
s.homepage = 'https://yhbt.net/kcar/'
|
11
8
|
s.authors = ["kcar hackers"]
|
12
|
-
s.description =
|
13
|
-
s.email = %q{kcar@
|
14
|
-
s.extra_rdoc_files =
|
9
|
+
s.description = File.read('README').split("\n\n")[1]
|
10
|
+
s.email = %q{kcar-public@yhbt.net}
|
11
|
+
s.extra_rdoc_files = IO.readlines('.document').map!(&:chomp!).keep_if do |f|
|
12
|
+
File.exist?(f)
|
13
|
+
end
|
15
14
|
s.files = manifest
|
16
|
-
s.summary =
|
15
|
+
s.summary = 'bytestream to Rack response converter'
|
17
16
|
s.test_files = Dir['test/test_*.rb']
|
18
17
|
s.extensions = %w(ext/kcar/extconf.rb)
|
19
|
-
s.add_development_dependency('olddoc', '~> 1.0')
|
20
18
|
s.add_development_dependency('test-unit', '~> 3.0')
|
21
19
|
|
22
|
-
|
20
|
+
# Note: To avoid ambiguity, we intentionally avoid the SPDX-compatible
|
21
|
+
# 'Ruby' for the Ruby 1.8 license. This is because Ruby 1.9.3 switched
|
22
|
+
# to BSD-2-Clause, but we inherited our license from Mongrel when
|
23
|
+
# Ruby was at 1.8. We cannot automatically switch licenses when Ruby
|
24
|
+
# changes; so we maintain the Ruby 1.8 license (not 1.9.3+) along
|
25
|
+
# with GPL-2.0+
|
26
|
+
s.licenses = %w(GPL-2.0+ Nonstandard)
|
23
27
|
end
|
data/lib/kcar.rb
CHANGED
data/lib/kcar/response.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
|
3
3
|
|
4
|
-
# This may be used to generate a Rack response
|
5
|
-
#
|
6
|
-
class Kcar::Response
|
7
|
-
attr_accessor :sock, :hdr, :unchunk, :buf, :parser
|
4
|
+
# This may be used to generate a Rack response synchronously.
|
8
5
|
|
6
|
+
class Kcar::Response
|
9
7
|
# :stopdoc:
|
10
|
-
|
8
|
+
attr_accessor :sock, :hdr, :unchunk, :buf, :parser
|
11
9
|
# :startdoc:
|
12
10
|
|
13
11
|
# By default we readpartial at most 16K off a socket at once
|
@@ -17,7 +15,8 @@ class Kcar::Response
|
|
17
15
|
# method. +unchunk+ may be set to disable transparent unchunking
|
18
16
|
# +hdr+ may be a Hash, Array, or Rack::Utils::HeaderHash
|
19
17
|
def initialize(sock, hdr = {}, unchunk = true)
|
20
|
-
@sock, @hdr, @unchunk, @buf
|
18
|
+
@sock, @hdr, @unchunk, @buf = sock, hdr, unchunk, ""
|
19
|
+
@parser = Kcar::Parser.new
|
21
20
|
end
|
22
21
|
|
23
22
|
# returns a 3-element array that resembles a Rack response, but is
|
@@ -63,11 +62,11 @@ class Kcar::Response
|
|
63
62
|
def each
|
64
63
|
return if @parser.body_eof?
|
65
64
|
if @unchunk
|
66
|
-
@parser.chunked? ? each_unchunk { |
|
67
|
-
each_identity { |
|
65
|
+
@parser.chunked? ? each_unchunk { |buf| yield buf } :
|
66
|
+
each_identity { |buf| yield buf }
|
68
67
|
else
|
69
|
-
@parser.chunked? ? each_rechunk { |
|
70
|
-
each_identity { |
|
68
|
+
@parser.chunked? ? each_rechunk { |buf| yield buf } :
|
69
|
+
each_identity { |buf| yield buf }
|
71
70
|
end
|
72
71
|
end
|
73
72
|
|
@@ -142,7 +141,7 @@ class Kcar::Response
|
|
142
141
|
len -= @sock.readpartial(len > READ_SIZE ? READ_SIZE : len, dst).size
|
143
142
|
yield dst
|
144
143
|
end while len > 0
|
145
|
-
dst.
|
144
|
+
dst.clear
|
146
145
|
end
|
147
146
|
end
|
148
147
|
@parser.body_bytes_left = 0
|
data/pkg.mk
CHANGED
@@ -60,7 +60,7 @@ doc:: .document .olddoc.yml $(pkg_extra) $(PLACEHOLDERS)
|
|
60
60
|
-find lib -type f -name '*.rbc' -exec rm -f '{}' ';'
|
61
61
|
-find ext -type f -name '*.rbc' -exec rm -f '{}' ';'
|
62
62
|
$(RM) -r doc
|
63
|
-
$(RDOC) -f
|
63
|
+
$(RDOC) -f dark216
|
64
64
|
$(OLDDOC) merge
|
65
65
|
install -m644 COPYING doc/COPYING
|
66
66
|
install -m644 NEWS doc/NEWS
|
@@ -86,7 +86,7 @@ fix-perms:
|
|
86
86
|
gem: $(pkggem)
|
87
87
|
|
88
88
|
install-gem: $(pkggem)
|
89
|
-
gem install $(CURDIR)/$<
|
89
|
+
gem install --local $(CURDIR)/$<
|
90
90
|
|
91
91
|
$(pkggem): manifest fix-perms
|
92
92
|
gem build $(rfpackage).gemspec
|
@@ -119,7 +119,7 @@ test: check
|
|
119
119
|
check: test-unit
|
120
120
|
test-unit: $(test_units)
|
121
121
|
$(test_units): build
|
122
|
-
$(RUBY) -I $(lib) $@ $(RUBY_TEST_OPTS)
|
122
|
+
$(VALGRIND) $(RUBY) -w -I $(lib) $@ $(RUBY_TEST_OPTS)
|
123
123
|
|
124
124
|
# this requires GNU coreutils variants
|
125
125
|
ifneq ($(RSYNC_DEST),)
|
data/test/test_parser.rb
CHANGED
@@ -254,6 +254,12 @@ class TestParser < Test::Unit::TestCase
|
|
254
254
|
end
|
255
255
|
end
|
256
256
|
|
257
|
+
def test_bad_cr
|
258
|
+
assert_raises(Kcar::ParserError) do
|
259
|
+
@hp.headers([], "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nA:\rb")
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
257
263
|
def test_leading_tab
|
258
264
|
resp = "HTTP/1.1 200 OK\r\nHost:\texample.com\r\n\r\n"
|
259
265
|
assert @hp.headers(env = {}, resp)
|
@@ -289,4 +295,30 @@ class TestParser < Test::Unit::TestCase
|
|
289
295
|
assert @hp.headers(env = {}, resp)
|
290
296
|
assert_equal '', env['Host']
|
291
297
|
end
|
298
|
+
|
299
|
+
def test_memsize
|
300
|
+
require 'objspace'
|
301
|
+
n = ObjectSpace.memsize_of(@hp)
|
302
|
+
assert_kind_of Integer, n
|
303
|
+
warn "memsize: #{n}\n" if $DEBUG
|
304
|
+
rescue LoadError
|
305
|
+
warn 'ObjectSpace not available'
|
306
|
+
end
|
307
|
+
|
308
|
+
def test_uminus_dd
|
309
|
+
# oddly, opt_str_freeze is not always effective:
|
310
|
+
# https://bugs.ruby-lang.org/issues/13282
|
311
|
+
a = -(%w(H o s t).join)
|
312
|
+
b = -(%w(H o s t).join)
|
313
|
+
if a.object_id == b.object_id
|
314
|
+
resp = "HTTP/1.1 200 OK\r\nHost: example.com\r\n\r\n"
|
315
|
+
assert @hp.headers(e = {}, resp.dup)
|
316
|
+
@hp.reset
|
317
|
+
assert @hp.headers(f = {}, resp.dup)
|
318
|
+
assert_same e.keys[0], f.keys[0]
|
319
|
+
assert_same a, e.keys[0]
|
320
|
+
else
|
321
|
+
warn "String#-@ does not dedupe with #{RUBY_ENGINE}-#{RUBY_VERSION}"
|
322
|
+
end
|
323
|
+
end
|
292
324
|
end
|
@@ -0,0 +1,1219 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
## Copyright (c) 2005 Zed A. Shaw
|
3
|
+
# You can redistribute it and/or modify it under the same terms as Ruby 1.8
|
4
|
+
# or GPL-2.0+ <https://www.gnu.org/licenses/gpl-2.0.txt>
|
5
|
+
#
|
6
|
+
# Additional work donated by contributors. See git history of
|
7
|
+
# unicorn for more information: git clone https://yhbt.net/unicorn.git
|
8
|
+
|
9
|
+
require 'test/unit'
|
10
|
+
require 'digest'
|
11
|
+
require 'kcar'
|
12
|
+
require 'uri'
|
13
|
+
|
14
|
+
class TestRequestParser < Test::Unit::TestCase
|
15
|
+
def setup
|
16
|
+
@hp = Kcar::Parser.new
|
17
|
+
@env = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_parse_oneshot_simple
|
21
|
+
buf = "GET / HTTP/1.1\r\n\r\n"
|
22
|
+
http = 'http'.freeze
|
23
|
+
@env['rack.url_scheme'] = http
|
24
|
+
env = @hp.request(@env, buf.dup)
|
25
|
+
assert_same env, @env
|
26
|
+
exp = {
|
27
|
+
'SERVER_PROTOCOL' => 'HTTP/1.1',
|
28
|
+
'HTTP_VERSION' => 'HTTP/1.1',
|
29
|
+
'REQUEST_PATH' => '/',
|
30
|
+
'PATH_INFO' => '/',
|
31
|
+
'REQUEST_URI' => '/',
|
32
|
+
'REQUEST_METHOD' => 'GET',
|
33
|
+
'QUERY_STRING' => '',
|
34
|
+
'rack.url_scheme' => 'http',
|
35
|
+
}
|
36
|
+
assert_equal exp, env
|
37
|
+
assert_same env['HTTP_VERSION'], env['SERVER_PROTOCOL']
|
38
|
+
assert_predicate env['HTTP_VERSION'], :frozen?
|
39
|
+
assert_predicate env['REQUEST_METHOD'], :frozen?
|
40
|
+
assert_same http, env['rack.url_scheme']
|
41
|
+
assert_predicate @hp, :keepalive?
|
42
|
+
|
43
|
+
@hp.reset
|
44
|
+
|
45
|
+
buf = "G"
|
46
|
+
assert_nil @hp.request(@env, buf)
|
47
|
+
# try parsing again to ensure we were reset correctly
|
48
|
+
buf << "ET /hello-world HTTP/1.1\r\n\r\n"
|
49
|
+
assert_same env, @hp.request(env, buf)
|
50
|
+
|
51
|
+
assert_equal 'HTTP/1.1', env['SERVER_PROTOCOL']
|
52
|
+
assert_equal '/hello-world', env['REQUEST_PATH']
|
53
|
+
assert_equal 'HTTP/1.1', env['HTTP_VERSION']
|
54
|
+
assert_equal '/hello-world', env['REQUEST_URI']
|
55
|
+
assert_equal 'GET', env['REQUEST_METHOD']
|
56
|
+
assert_equal '', env['QUERY_STRING']
|
57
|
+
assert @hp.keepalive?
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_tab_lws
|
61
|
+
@hp.request(@env, "GET / HTTP/1.1\r\nHost:\tfoo.bar\r\n\r\n")
|
62
|
+
assert_equal "foo.bar", @env['HTTP_HOST']
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_connection_close_no_ka
|
66
|
+
@hp.request(@env = {}, "GET / HTTP/1.1\r\nConnection: close\r\n\r\n")
|
67
|
+
assert_equal 'GET', @env['REQUEST_METHOD']
|
68
|
+
assert ! @hp.keepalive?
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_connection_keep_alive_ka
|
72
|
+
@hp.request(@env, "HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n")
|
73
|
+
assert @hp.keepalive?
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_connection_keep_alive_no_body
|
77
|
+
r = @hp.request(@env, "POST / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n")
|
78
|
+
assert_same @env, r
|
79
|
+
assert @hp.keepalive?
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_connection_keep_alive_no_body_empty
|
83
|
+
buf = "POST / HTTP/1.1\r\n" \
|
84
|
+
"Content-Length: 0\r\n" \
|
85
|
+
"Connection: keep-alive\r\n\r\n"
|
86
|
+
assert_same @env, @hp.request(@env, buf)
|
87
|
+
assert @hp.keepalive?
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_connection_keep_alive_ka_bad_version
|
91
|
+
@hp.request(@env, "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n")
|
92
|
+
assert @hp.keepalive?
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_parse_server_host_default_port
|
96
|
+
buf = "GET / HTTP/1.1\r\nHost: foo\r\n\r\n"
|
97
|
+
assert_same @env, @hp.request(@env, buf)
|
98
|
+
assert_equal 'foo', @env['SERVER_NAME']
|
99
|
+
assert_nil @env['SERVER_PORT']
|
100
|
+
assert_equal '', buf
|
101
|
+
assert @hp.keepalive?
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_parse_server_host_alt_port
|
105
|
+
buf = "GET / HTTP/1.1\r\nHost: foo:999\r\n\r\n"
|
106
|
+
@hp.request(@env, buf)
|
107
|
+
assert_equal 'foo', @env['SERVER_NAME']
|
108
|
+
assert_equal '999', @env['SERVER_PORT']
|
109
|
+
assert_equal '', buf
|
110
|
+
assert @hp.keepalive?
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_parse_server_host_empty_port
|
114
|
+
@hp.request(@env, "GET / HTTP/1.1\r\nHost: foo:\r\n\r\n")
|
115
|
+
assert_equal 'foo', @env['SERVER_NAME']
|
116
|
+
assert_nil @env['SERVER_PORT']
|
117
|
+
assert @hp.keepalive?
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_parse_host_cont
|
121
|
+
@hp.request(@env, "GET / HTTP/1.1\r\nHost:\r\n foo\r\n\r\n")
|
122
|
+
assert_equal 'foo', @env['SERVER_NAME']
|
123
|
+
assert_nil @env['SERVER_PORT']
|
124
|
+
assert @hp.keepalive?
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_preserve_existing_server_vars
|
128
|
+
@env = {
|
129
|
+
'SERVER_NAME' => 'example.com',
|
130
|
+
'SERVER_PORT' => '1234',
|
131
|
+
'rack.url_scheme' => 'https'
|
132
|
+
}
|
133
|
+
@hp.request(@env, "GET / HTTP/1.0\r\n\r\n")
|
134
|
+
assert_equal 'example.com', @env['SERVER_NAME']
|
135
|
+
assert_equal '1234', @env['SERVER_PORT']
|
136
|
+
assert_equal 'https', @env['rack.url_scheme']
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_parse_strange_headers
|
140
|
+
should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
|
141
|
+
req = @hp.request(@env, should_be_good)
|
142
|
+
assert_same req, @env
|
143
|
+
assert_equal '', should_be_good
|
144
|
+
assert_predicate @hp, :keepalive?
|
145
|
+
assert_equal '++++++++++', @env['HTTP_AAAAAAAAAAAAA']
|
146
|
+
end
|
147
|
+
|
148
|
+
# legacy test case from Mongrel
|
149
|
+
# I still consider Pound irrelevant, unfortunately stupid clients that
|
150
|
+
# send extremely big headers do exist and they've managed to find us...
|
151
|
+
def test_nasty_pound_header
|
152
|
+
nasty_pound_header = "GET / HTTP/1.1\r\n" \
|
153
|
+
"X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n" \
|
154
|
+
"\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n" \
|
155
|
+
"\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n" \
|
156
|
+
"\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n" \
|
157
|
+
"\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n" \
|
158
|
+
"\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n" \
|
159
|
+
"\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n" \
|
160
|
+
"\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n" \
|
161
|
+
"\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n" \
|
162
|
+
"\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n" \
|
163
|
+
"\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n" \
|
164
|
+
"\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n" \
|
165
|
+
"\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n" \
|
166
|
+
"\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n" \
|
167
|
+
"\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n" \
|
168
|
+
"\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n" \
|
169
|
+
"\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n" \
|
170
|
+
"\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n" \
|
171
|
+
"\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n" \
|
172
|
+
"\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n" \
|
173
|
+
"\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n" \
|
174
|
+
"\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n" \
|
175
|
+
"\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n" \
|
176
|
+
"\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n" \
|
177
|
+
"\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n" \
|
178
|
+
"\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n" \
|
179
|
+
"\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n" \
|
180
|
+
"\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n" \
|
181
|
+
"\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n" \
|
182
|
+
"\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n" \
|
183
|
+
"\tRA==\r\n" \
|
184
|
+
"\t-----END CERTIFICATE-----\r\n" \
|
185
|
+
"\r\n"
|
186
|
+
ok = (/(-----BEGIN .*--END CERTIFICATE-----)/m =~ nasty_pound_header)
|
187
|
+
expect = $1.dup
|
188
|
+
assert ok, 'end certificate matched'
|
189
|
+
expect.gsub!(/\r\n\t/, ' ')
|
190
|
+
req = @hp.request(@env, nasty_pound_header.dup)
|
191
|
+
assert_equal expect, req['HTTP_X_SSL_BULLSHIT']
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_multiline_header_0d0a
|
195
|
+
req = @hp.request(@env, "GET / HTTP/1.0\r\n" \
|
196
|
+
"X-Multiline-Header: foo bar\r\n\tcha cha\r\n\tzha zha\r\n\r\n")
|
197
|
+
assert_same req, @env
|
198
|
+
assert_equal 'foo bar cha cha zha zha', req['HTTP_X_MULTILINE_HEADER']
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_multiline_header_0a
|
202
|
+
req = @hp.request(@env, "GET / HTTP/1.0\n" \
|
203
|
+
"X-Multiline-Header: foo bar\n\tcha cha\n\tzha zha\n\n")
|
204
|
+
assert_same req, @env
|
205
|
+
assert_equal 'foo bar cha cha zha zha', req['HTTP_X_MULTILINE_HEADER']
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_continuation_eats_leading_spaces
|
209
|
+
header = "GET / HTTP/1.1\r\n" \
|
210
|
+
"X-ASDF: \r\n" \
|
211
|
+
"\t\r\n" \
|
212
|
+
" \r\n" \
|
213
|
+
" ASDF\r\n\r\n"
|
214
|
+
req = @hp.request(@env, header)
|
215
|
+
assert_same req, @env
|
216
|
+
assert_equal '', header
|
217
|
+
assert_equal 'ASDF', req['HTTP_X_ASDF']
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_continuation_eats_scattered_leading_spaces
|
221
|
+
header = "GET / HTTP/1.1\r\n" \
|
222
|
+
"X-ASDF: hi\r\n" \
|
223
|
+
" y\r\n" \
|
224
|
+
"\t\r\n" \
|
225
|
+
" x\r\n" \
|
226
|
+
" ASDF\r\n\r\n"
|
227
|
+
req = @hp.request(@env, header)
|
228
|
+
assert_same req, @env
|
229
|
+
assert_equal '', header
|
230
|
+
assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_continuation_eats_trailing_spaces
|
234
|
+
header = "GET / HTTP/1.1\r\n" \
|
235
|
+
"X-ASDF: \r\n" \
|
236
|
+
"\t\r\n" \
|
237
|
+
" b \r\n" \
|
238
|
+
" ASDF\r\n\r\nZ"
|
239
|
+
req = @hp.request(@env, header)
|
240
|
+
assert_same req, @env
|
241
|
+
assert_equal 'Z', header
|
242
|
+
assert_equal 'b ASDF', req['HTTP_X_ASDF']
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_continuation_with_absolute_uri_and_ignored_host_header
|
246
|
+
header = "GET http://example.com/ HTTP/1.1\r\n" \
|
247
|
+
"Host: \r\n" \
|
248
|
+
" example.org\r\n" \
|
249
|
+
"\r\n"
|
250
|
+
req = @hp.request(@env, header)
|
251
|
+
assert_same req, @env
|
252
|
+
assert_equal 'example.com', req['HTTP_HOST']
|
253
|
+
assert_equal 'http', req['rack.url_scheme']
|
254
|
+
assert_equal '/', req['PATH_INFO']
|
255
|
+
assert_equal 'example.com', req['SERVER_NAME']
|
256
|
+
assert_equal '80', req['SERVER_PORT']
|
257
|
+
end
|
258
|
+
|
259
|
+
# this may seem to be testing more of an implementation detail, but
|
260
|
+
# it also helps ensure we're safe in the presence of multiple parsers
|
261
|
+
# in case we ever go multithreaded/evented...
|
262
|
+
def test_resumable_continuations
|
263
|
+
nr = 1000
|
264
|
+
header = "GET / HTTP/1.1\r\n" \
|
265
|
+
"X-ASDF: \r\n" \
|
266
|
+
" hello\r\n"
|
267
|
+
tmp = []
|
268
|
+
nr.times { |i|
|
269
|
+
hp = Kcar::Parser.new
|
270
|
+
env = {}
|
271
|
+
assert_nil hp.request(env, buf = "#{header} #{i}\r\n")
|
272
|
+
asdf = env['HTTP_X_ASDF']
|
273
|
+
assert_equal "hello #{i}", asdf
|
274
|
+
tmp << [ hp, asdf, env, buf ]
|
275
|
+
}
|
276
|
+
tmp.each_with_index { |(hp, asdf, env, buf), i|
|
277
|
+
buf << " .\r\n\r\n"
|
278
|
+
assert_same env, hp.request(env, buf)
|
279
|
+
assert_equal "hello #{i} .", asdf
|
280
|
+
}
|
281
|
+
end
|
282
|
+
|
283
|
+
def test_invalid_continuation
|
284
|
+
header = "GET / HTTP/1.1\r\n" \
|
285
|
+
" y\r\n" \
|
286
|
+
"Host: hello\r\n" \
|
287
|
+
"\r\n"
|
288
|
+
buf = header.dup
|
289
|
+
assert_raises(Kcar::ParserError) do
|
290
|
+
@hp.request(@env, buf)
|
291
|
+
end
|
292
|
+
assert_equal header, buf, 'no modification on invalid'
|
293
|
+
end
|
294
|
+
|
295
|
+
def test_parse_ie6_urls
|
296
|
+
%w(/some/random/path"
|
297
|
+
/some/random/path>
|
298
|
+
/some/random/path<
|
299
|
+
/we/love/you/ie6?q=<"">
|
300
|
+
/url?<="&>="
|
301
|
+
/mal"formed"?
|
302
|
+
).each do |path|
|
303
|
+
sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
|
304
|
+
assert_same @env, @hp.request(@env, sorta_safe)
|
305
|
+
assert_equal path, @env['REQUEST_URI']
|
306
|
+
assert_equal '', sorta_safe
|
307
|
+
assert @hp.keepalive?
|
308
|
+
@hp.reset
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def test_parse_error
|
313
|
+
bad_http = "GET / SsUTF/1.1"
|
314
|
+
assert_raises(Kcar::ParserError) { @hp.request(@env, bad_http) }
|
315
|
+
|
316
|
+
# make sure we can recover
|
317
|
+
@env.clear
|
318
|
+
@hp.reset
|
319
|
+
assert_equal @env, @hp.request(@env, "GET / HTTP/1.0\r\n\r\n")
|
320
|
+
assert ! @hp.keepalive?
|
321
|
+
end
|
322
|
+
|
323
|
+
def test_piecemeal
|
324
|
+
http = "GET"
|
325
|
+
req = @env
|
326
|
+
assert_nil @hp.request(@env, http)
|
327
|
+
assert_nil @hp.request(@env, http)
|
328
|
+
assert_nil @hp.request(@env, http << " / HTTP/1.0")
|
329
|
+
assert_equal '/', req['REQUEST_PATH']
|
330
|
+
assert_equal '/', req['REQUEST_URI']
|
331
|
+
assert_equal 'GET', req['REQUEST_METHOD']
|
332
|
+
assert_nil @hp.request(req, http << "\r\n")
|
333
|
+
assert_equal 'HTTP/1.0', req['HTTP_VERSION']
|
334
|
+
assert_nil @hp.request(req, http << "\r")
|
335
|
+
assert_same req, @hp.request(req, http << "\n")
|
336
|
+
assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
|
337
|
+
assert_nil req['FRAGMENT']
|
338
|
+
assert_equal '', req['QUERY_STRING']
|
339
|
+
assert_equal "", http
|
340
|
+
assert ! @hp.keepalive?
|
341
|
+
end
|
342
|
+
|
343
|
+
# not common, but underscores do appear in practice
|
344
|
+
def test_absolute_uri_underscores
|
345
|
+
http = "GET https://under_score.example.com/foo?q=bar HTTP/1.0\r\n\r\n"
|
346
|
+
req = @hp.request(@env, http)
|
347
|
+
assert_same req, @env
|
348
|
+
assert_equal 'https', req['rack.url_scheme']
|
349
|
+
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
350
|
+
assert_equal '/foo', req['REQUEST_PATH']
|
351
|
+
assert_equal 'q=bar', req['QUERY_STRING']
|
352
|
+
assert_equal 'under_score.example.com', req['HTTP_HOST']
|
353
|
+
assert_equal 'under_score.example.com', req['SERVER_NAME']
|
354
|
+
assert_equal '443', req['SERVER_PORT']
|
355
|
+
assert_equal "", http
|
356
|
+
assert ! @hp.keepalive?
|
357
|
+
end
|
358
|
+
|
359
|
+
# some dumb clients add users because they're stupid
|
360
|
+
def test_absolute_uri_w_user
|
361
|
+
http = "GET http://user%20space@example.com/foo?q=bar HTTP/1.0\r\n\r\n"
|
362
|
+
req = @hp.request(@env, http)
|
363
|
+
assert_same req, @env
|
364
|
+
assert_equal 'http', req['rack.url_scheme']
|
365
|
+
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
366
|
+
assert_equal '/foo', req['REQUEST_PATH']
|
367
|
+
assert_equal 'q=bar', req['QUERY_STRING']
|
368
|
+
assert_equal 'example.com', req['HTTP_HOST']
|
369
|
+
assert_equal 'example.com', req['SERVER_NAME']
|
370
|
+
assert_equal '80', req['SERVER_PORT']
|
371
|
+
assert_equal "", http
|
372
|
+
assert ! @hp.keepalive?
|
373
|
+
end
|
374
|
+
|
375
|
+
# since Mongrel supported anything URI.parse supported, we're stuck
|
376
|
+
# supporting everything URI.parse supports
|
377
|
+
def test_absolute_uri_uri_parse
|
378
|
+
require 'uri'
|
379
|
+
"#{URI::REGEXP::PATTERN::UNRESERVED};:&=+$,".split(//).each do |char|
|
380
|
+
http = "GET http://#{char}@example.com/ HTTP/1.0\r\n\r\n"
|
381
|
+
req = @hp.request(@env, http)
|
382
|
+
assert_equal 'http', req['rack.url_scheme']
|
383
|
+
assert_equal '/', req['REQUEST_URI']
|
384
|
+
assert_equal '/', req['REQUEST_PATH']
|
385
|
+
assert_equal '', req['QUERY_STRING']
|
386
|
+
|
387
|
+
assert_equal 'example.com', req['HTTP_HOST']
|
388
|
+
assert_equal 'example.com', req['SERVER_NAME']
|
389
|
+
assert_equal '80', req['SERVER_PORT']
|
390
|
+
assert_equal "", http
|
391
|
+
assert ! @hp.keepalive?
|
392
|
+
@hp.reset
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def test_absolute_uri
|
397
|
+
req = @hp.request(@env, "GET http://example.com/foo?q=bar HTTP/1.0\r\n\r\n")
|
398
|
+
assert_equal 'http', req['rack.url_scheme']
|
399
|
+
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
400
|
+
assert_equal '/foo', req['REQUEST_PATH']
|
401
|
+
assert_equal 'q=bar', req['QUERY_STRING']
|
402
|
+
assert_equal '80', req['SERVER_PORT']
|
403
|
+
assert_equal 'example.com', req['HTTP_HOST']
|
404
|
+
assert_equal 'example.com', req['SERVER_NAME']
|
405
|
+
assert ! @hp.keepalive?
|
406
|
+
end
|
407
|
+
|
408
|
+
def test_absolute_uri_https
|
409
|
+
http = "GET https://example.com/foo?q=bar HTTP/1.1\r\n" \
|
410
|
+
"X-Foo: bar\n\r\n"
|
411
|
+
req = @hp.request(@env, http)
|
412
|
+
assert_equal 'https', req['rack.url_scheme']
|
413
|
+
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
414
|
+
assert_equal '/foo', req['REQUEST_PATH']
|
415
|
+
assert_equal 'q=bar', req['QUERY_STRING']
|
416
|
+
assert_equal 'example.com', req['HTTP_HOST']
|
417
|
+
assert_equal 'example.com', req['SERVER_NAME']
|
418
|
+
assert_equal '443', req['SERVER_PORT']
|
419
|
+
assert_equal "", http
|
420
|
+
assert @hp.keepalive?
|
421
|
+
end
|
422
|
+
|
423
|
+
# Host: header should be ignored for absolute URIs
|
424
|
+
def test_absolute_uri_with_port
|
425
|
+
req = @hp.request(@env,"GET http://example.com:8080/foo?q=bar HTTP/1.1\r\n" \
|
426
|
+
"Host: bad.example.com\r\n\r\n")
|
427
|
+
assert_equal 'http', req['rack.url_scheme']
|
428
|
+
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
429
|
+
assert_equal '/foo', req['REQUEST_PATH']
|
430
|
+
assert_equal 'q=bar', req['QUERY_STRING']
|
431
|
+
assert_equal 'example.com:8080', req['HTTP_HOST']
|
432
|
+
assert_equal 'example.com', req['SERVER_NAME']
|
433
|
+
assert_equal '8080', req['SERVER_PORT']
|
434
|
+
assert @hp.keepalive?
|
435
|
+
end
|
436
|
+
|
437
|
+
def test_absolute_uri_with_empty_port
|
438
|
+
req = @hp.request(@env, "GET https://example.com:/foo?q=bar HTTP/1.1\r\n" \
|
439
|
+
"Host: bad.example.com\r\n\r\n")
|
440
|
+
assert_same req, @env
|
441
|
+
assert_equal 'https', req['rack.url_scheme']
|
442
|
+
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
443
|
+
assert_equal '/foo', req['REQUEST_PATH']
|
444
|
+
assert_equal 'q=bar', req['QUERY_STRING']
|
445
|
+
assert_equal 'example.com:', req['HTTP_HOST']
|
446
|
+
assert_equal 'example.com', req['SERVER_NAME']
|
447
|
+
assert_equal '443', req['SERVER_PORT']
|
448
|
+
assert @hp.keepalive?
|
449
|
+
end
|
450
|
+
|
451
|
+
def test_absolute_ipv6_uri
|
452
|
+
url = "http://[::1]/foo?q=bar"
|
453
|
+
http = "GET #{url} HTTP/1.1\r\n" \
|
454
|
+
"Host: bad.example.com\r\n\r\n"
|
455
|
+
req = @hp.request(@env, http)
|
456
|
+
assert_same req, @env
|
457
|
+
assert_equal 'http', req['rack.url_scheme']
|
458
|
+
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
459
|
+
assert_equal '/foo', req['REQUEST_PATH']
|
460
|
+
assert_equal 'q=bar', req['QUERY_STRING']
|
461
|
+
|
462
|
+
uri = URI.parse(url)
|
463
|
+
assert_equal "[::1]", uri.host,
|
464
|
+
"URI.parse changed upstream for #{url}? host=#{uri.host}"
|
465
|
+
assert_equal "[::1]", req['HTTP_HOST']
|
466
|
+
assert_equal "[::1]", req['SERVER_NAME']
|
467
|
+
assert_equal '80', req['SERVER_PORT']
|
468
|
+
assert_equal "", http
|
469
|
+
end
|
470
|
+
|
471
|
+
def test_absolute_ipv6_uri_alpha
|
472
|
+
url = "http://[::a]/"
|
473
|
+
http = "GET #{url} HTTP/1.1\r\n" \
|
474
|
+
"Host: bad.example.com\r\n\r\n"
|
475
|
+
req = @hp.request(@env, http)
|
476
|
+
assert_equal 'http', req['rack.url_scheme']
|
477
|
+
uri = URI.parse(url)
|
478
|
+
assert_equal "[::a]", uri.host,
|
479
|
+
"URI.parse changed upstream for #{url}? host=#{uri.host}"
|
480
|
+
assert_equal "[::a]", req['HTTP_HOST']
|
481
|
+
assert_equal "[::a]", req['SERVER_NAME']
|
482
|
+
assert_equal '80', req['SERVER_PORT']
|
483
|
+
end
|
484
|
+
|
485
|
+
def test_absolute_ipv6_uri_alpha_2
|
486
|
+
url = "http://[::B]/"
|
487
|
+
http = "GET #{url} HTTP/1.1\r\n" \
|
488
|
+
"Host: bad.example.com\r\n\r\n"
|
489
|
+
req = @hp.request(@env, http)
|
490
|
+
assert_equal 'http', req['rack.url_scheme']
|
491
|
+
|
492
|
+
uri = URI.parse(url)
|
493
|
+
assert_equal "[::B]", uri.host,
|
494
|
+
"URI.parse changed upstream for #{url}? host=#{uri.host}"
|
495
|
+
assert_equal "[::B]", req['HTTP_HOST']
|
496
|
+
assert_equal "[::B]", req['SERVER_NAME']
|
497
|
+
assert_equal '80', req['SERVER_PORT']
|
498
|
+
end
|
499
|
+
|
500
|
+
def test_absolute_ipv6_uri_with_empty_port
|
501
|
+
url = "https://[::1]:/foo?q=bar"
|
502
|
+
http = "GET #{url} HTTP/1.1\r\n" \
|
503
|
+
"Host: bad.example.com\r\n\r\n"
|
504
|
+
req = @hp.request(@env, http)
|
505
|
+
assert_equal 'https', req['rack.url_scheme']
|
506
|
+
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
507
|
+
assert_equal '/foo', req['REQUEST_PATH']
|
508
|
+
assert_equal 'q=bar', req['QUERY_STRING']
|
509
|
+
|
510
|
+
uri = URI.parse(url)
|
511
|
+
assert_equal "[::1]", uri.host,
|
512
|
+
"URI.parse changed upstream for #{url}? host=#{uri.host}"
|
513
|
+
assert_equal "[::1]:", req['HTTP_HOST']
|
514
|
+
assert_equal "[::1]", req['SERVER_NAME']
|
515
|
+
assert_equal '443', req['SERVER_PORT']
|
516
|
+
assert_equal "", http
|
517
|
+
end
|
518
|
+
|
519
|
+
def test_absolute_ipv6_uri_with_port
|
520
|
+
url = "https://[::1]:666/foo?q=bar"
|
521
|
+
http = "GET #{url} HTTP/1.1\r\n" \
|
522
|
+
"Host: bad.example.com\r\n\r\n"
|
523
|
+
req = @hp.request(@env, http)
|
524
|
+
assert_equal 'https', req['rack.url_scheme']
|
525
|
+
assert_equal '/foo?q=bar', req['REQUEST_URI']
|
526
|
+
assert_equal '/foo', req['REQUEST_PATH']
|
527
|
+
assert_equal 'q=bar', req['QUERY_STRING']
|
528
|
+
|
529
|
+
uri = URI.parse(url)
|
530
|
+
assert_equal "[::1]", uri.host,
|
531
|
+
"URI.parse changed upstream for #{url}? host=#{uri.host}"
|
532
|
+
assert_equal "[::1]:666", req['HTTP_HOST']
|
533
|
+
assert_equal "[::1]", req['SERVER_NAME']
|
534
|
+
assert_equal '666', req['SERVER_PORT']
|
535
|
+
assert_equal "", http
|
536
|
+
end
|
537
|
+
|
538
|
+
def test_ipv6_host_header
|
539
|
+
buf = "GET / HTTP/1.1\r\n" \
|
540
|
+
"Host: [::1]\r\n\r\n"
|
541
|
+
req = @hp.request(@env, buf)
|
542
|
+
assert_equal "[::1]", req['HTTP_HOST']
|
543
|
+
assert_equal "[::1]", req['SERVER_NAME']
|
544
|
+
assert_nil req['SERVER_PORT']
|
545
|
+
end
|
546
|
+
|
547
|
+
def test_ipv6_host_header_with_port
|
548
|
+
req = @hp.request(@env, "GET / HTTP/1.1\r\n" \
|
549
|
+
"Host: [::1]:666\r\n\r\n")
|
550
|
+
assert_equal "[::1]", req['SERVER_NAME']
|
551
|
+
assert_equal '666', req['SERVER_PORT']
|
552
|
+
assert_equal "[::1]:666", req['HTTP_HOST']
|
553
|
+
end
|
554
|
+
|
555
|
+
def test_ipv6_host_header_with_empty_port
|
556
|
+
req = @hp.request(@env, "GET / HTTP/1.1\r\nHost: [::1]:\r\n\r\n")
|
557
|
+
assert_equal "[::1]", req['SERVER_NAME']
|
558
|
+
assert_nil req['SERVER_PORT']
|
559
|
+
assert_equal "[::1]:", req['HTTP_HOST']
|
560
|
+
end
|
561
|
+
|
562
|
+
# XXX Highly unlikely..., just make sure we don't segfault or assert on it
|
563
|
+
def test_broken_ipv6_host_header
|
564
|
+
req = @hp.request(@env, "GET / HTTP/1.1\r\nHost: [::1:\r\n\r\n")
|
565
|
+
assert_equal "[", req['SERVER_NAME']
|
566
|
+
assert_equal ':1:', req['SERVER_PORT']
|
567
|
+
assert_equal "[::1:", req['HTTP_HOST']
|
568
|
+
end
|
569
|
+
|
570
|
+
def test_put_body_oneshot
|
571
|
+
buf = "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde"
|
572
|
+
req = @hp.request(@env, buf)
|
573
|
+
assert_equal '/', req['REQUEST_PATH']
|
574
|
+
assert_equal '/', req['REQUEST_URI']
|
575
|
+
assert_equal 'PUT', req['REQUEST_METHOD']
|
576
|
+
assert_equal 'HTTP/1.0', req['HTTP_VERSION']
|
577
|
+
assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
|
578
|
+
assert_equal "abcde", buf
|
579
|
+
end
|
580
|
+
|
581
|
+
def test_put_body_later
|
582
|
+
buf = "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
|
583
|
+
req = @hp.request(@env, buf)
|
584
|
+
assert_equal '/l', req['REQUEST_PATH']
|
585
|
+
assert_equal '/l', req['REQUEST_URI']
|
586
|
+
assert_equal 'PUT', req['REQUEST_METHOD']
|
587
|
+
assert_equal 'HTTP/1.0', req['HTTP_VERSION']
|
588
|
+
assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
|
589
|
+
assert_equal "", buf
|
590
|
+
end
|
591
|
+
|
592
|
+
def test_unknown_methods
|
593
|
+
%w(GETT HEADR XGET XHEAD).each { |m|
|
594
|
+
s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
|
595
|
+
req = @hp.request(@env, s)
|
596
|
+
assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
|
597
|
+
assert_equal 'posts-17408', req['FRAGMENT']
|
598
|
+
assert_equal 'page=1', req['QUERY_STRING']
|
599
|
+
assert_equal "", s
|
600
|
+
assert_equal m, req['REQUEST_METHOD']
|
601
|
+
@hp.reset
|
602
|
+
}
|
603
|
+
end
|
604
|
+
|
605
|
+
def test_fragment_in_uri
|
606
|
+
get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
|
607
|
+
req = @hp.request(@env, get)
|
608
|
+
assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
|
609
|
+
assert_equal 'posts-17408', req['FRAGMENT']
|
610
|
+
assert_equal 'page=1', req['QUERY_STRING']
|
611
|
+
assert_equal '', get
|
612
|
+
end
|
613
|
+
|
614
|
+
# lame random garbage maker
|
615
|
+
def rand_data(min, max, readable=true)
|
616
|
+
count = min + ((rand(max)+1) *10).to_i
|
617
|
+
res = count.to_s + "/"
|
618
|
+
|
619
|
+
if readable
|
620
|
+
res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
|
621
|
+
else
|
622
|
+
res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
|
623
|
+
end
|
624
|
+
|
625
|
+
return res
|
626
|
+
end
|
627
|
+
|
628
|
+
def test_horrible_queries
|
629
|
+
# then that large header names are caught
|
630
|
+
10.times do |c|
|
631
|
+
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
|
632
|
+
assert_raises(Kcar::ParserError, Kcar::RequestURITooLongError) do
|
633
|
+
@hp.request(@env, get)
|
634
|
+
@hp.clear
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
# then that large mangled field values are caught
|
639
|
+
10.times do |c|
|
640
|
+
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
641
|
+
assert_raises(Kcar::ParserError,Kcar::RequestURITooLongError) do
|
642
|
+
@hp.request(@env, get)
|
643
|
+
@hp.clear
|
644
|
+
end
|
645
|
+
end
|
646
|
+
|
647
|
+
# then large headers are rejected too FIXME not supported, yet
|
648
|
+
if false
|
649
|
+
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
|
650
|
+
get << "X-Test: test\r\n" * (80 * 1024)
|
651
|
+
@hp.reset
|
652
|
+
assert_raises(Kcar::ParserError,Kcar::RequestURITooLongError) do
|
653
|
+
@hp.request(@env, get)
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
# finally just that random garbage gets blocked all the time
|
658
|
+
10.times do |c|
|
659
|
+
get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
660
|
+
@hp.reset
|
661
|
+
assert_raises(Kcar::ParserError,Kcar::RequestURITooLongError) do
|
662
|
+
@hp.request(@env, get)
|
663
|
+
end
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
def test_leading_tab
|
668
|
+
get = "GET / HTTP/1.1\r\nHost:\texample.com\r\n\r\n"
|
669
|
+
req = @hp.request(@env, get)
|
670
|
+
assert_equal 'example.com', req['HTTP_HOST']
|
671
|
+
end
|
672
|
+
|
673
|
+
def test_trailing_whitespace
|
674
|
+
get = "GET / HTTP/1.1\r\nHost: example.com \r\n\r\n"
|
675
|
+
req = @hp.request(@env, get)
|
676
|
+
assert_equal 'example.com', req['HTTP_HOST']
|
677
|
+
end
|
678
|
+
|
679
|
+
def test_trailing_tab
|
680
|
+
get = "GET / HTTP/1.1\r\nHost: example.com\t\r\n\r\n"
|
681
|
+
req = @hp.request(@env, get)
|
682
|
+
assert_equal 'example.com', req['HTTP_HOST']
|
683
|
+
end
|
684
|
+
|
685
|
+
def test_trailing_multiple_linear_whitespace
|
686
|
+
get = "GET / HTTP/1.1\r\nHost: example.com\t \t \t\r\n\r\n"
|
687
|
+
req = @hp.request(@env, get)
|
688
|
+
assert_equal 'example.com', req['HTTP_HOST']
|
689
|
+
end
|
690
|
+
|
691
|
+
def test_embedded_linear_whitespace_ok
|
692
|
+
get = "GET / HTTP/1.1\r\nX-Space: hello\t world\t \r\n\r\n"
|
693
|
+
req = @hp.request(@env, get)
|
694
|
+
assert_equal "hello\t world", req["HTTP_X_SPACE"]
|
695
|
+
end
|
696
|
+
|
697
|
+
def test_null_byte_header
|
698
|
+
get = "GET / HTTP/1.1\r\nHost: \0\r\n\r\n"
|
699
|
+
assert_raises(Kcar::ParserError) { @hp.request(@env, get) }
|
700
|
+
end
|
701
|
+
|
702
|
+
def test_null_byte_in_middle
|
703
|
+
get = "GET / HTTP/1.1\r\nHost: hello\0world\r\n\r\n"
|
704
|
+
assert_raises(Kcar::ParserError) { @hp.request(@env, get) }
|
705
|
+
end
|
706
|
+
|
707
|
+
def test_null_byte_at_end
|
708
|
+
get = "GET / HTTP/1.1\r\nHost: hello\0\r\n\r\n"
|
709
|
+
assert_raises(Kcar::ParserError) { @hp.request(@env, get) }
|
710
|
+
end
|
711
|
+
|
712
|
+
def test_empty_header
|
713
|
+
get = "GET / HTTP/1.1\r\nHost: \r\n\r\n"
|
714
|
+
req = @hp.request(@env, get)
|
715
|
+
assert_equal '', req['HTTP_HOST']
|
716
|
+
end
|
717
|
+
|
718
|
+
def test_connection_TE
|
719
|
+
req = @hp.request(@env, "GET / HTTP/1.1\r\nHost: example.com\r\n" \
|
720
|
+
"Connection: TE\r\n" \
|
721
|
+
"TE: trailers\r\n\r\n")
|
722
|
+
assert_predicate @hp, :keepalive?
|
723
|
+
assert_equal 'TE', req['HTTP_CONNECTION']
|
724
|
+
end
|
725
|
+
|
726
|
+
def test_repeat_headers
|
727
|
+
str = "PUT / HTTP/1.1\r\n" \
|
728
|
+
"Trailer: Content-MD5\r\n" \
|
729
|
+
"Trailer: Content-SHA1\r\n" \
|
730
|
+
"transfer-Encoding: chunked\r\n\r\n" \
|
731
|
+
"1\r\na\r\n2\r\n..\r\n0\r\n"
|
732
|
+
req = @hp.request(@env, str)
|
733
|
+
assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
|
734
|
+
assert_equal "1\r\na\r\n2\r\n..\r\n0\r\n", str
|
735
|
+
assert_not_predicate @hp, :keepalive?
|
736
|
+
end
|
737
|
+
|
738
|
+
def test_http_09
|
739
|
+
buf = "GET /read-rfc1945-if-you-dont-believe-me\r\n"
|
740
|
+
req = @hp.request(@env, buf)
|
741
|
+
assert_equal '', buf
|
742
|
+
expect = {
|
743
|
+
"REQUEST_PATH" => "/read-rfc1945-if-you-dont-believe-me",
|
744
|
+
"PATH_INFO" => "/read-rfc1945-if-you-dont-believe-me",
|
745
|
+
"REQUEST_URI" => "/read-rfc1945-if-you-dont-believe-me",
|
746
|
+
"REQUEST_METHOD" => "GET",
|
747
|
+
"QUERY_STRING" => ""
|
748
|
+
}
|
749
|
+
assert_equal expect, req
|
750
|
+
end
|
751
|
+
|
752
|
+
def test_path_info_semicolon
|
753
|
+
qs = "QUERY_STRING"
|
754
|
+
pi = "PATH_INFO"
|
755
|
+
req = {}
|
756
|
+
str = "GET %s HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
757
|
+
{
|
758
|
+
"/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
|
759
|
+
"/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
|
760
|
+
"/1;a=b" => { qs => "", pi => "/1;a=b" },
|
761
|
+
"/1;a=b?" => { qs => "", pi => "/1;a=b" },
|
762
|
+
"/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
|
763
|
+
"*" => { qs => "", pi => "" },
|
764
|
+
}.each do |uri,expect|
|
765
|
+
@env.clear
|
766
|
+
@hp.reset
|
767
|
+
buf = str % [ uri ]
|
768
|
+
req = @hp.request(@env, buf)
|
769
|
+
assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
|
770
|
+
assert_equal expect[qs], req[qs], "#{qs} mismatch"
|
771
|
+
assert_equal expect[pi], req[pi], "#{pi} mismatch"
|
772
|
+
next if uri == "*"
|
773
|
+
uri = URI.parse("http://example.com#{uri}")
|
774
|
+
assert_equal uri.query.to_s, req[qs], "#{qs} mismatch URI.parse disagrees"
|
775
|
+
assert_equal uri.path, req[pi], "#{pi} mismatch URI.parse disagrees"
|
776
|
+
end
|
777
|
+
end
|
778
|
+
|
779
|
+
def test_path_info_semicolon_absolute
|
780
|
+
qs = "QUERY_STRING"
|
781
|
+
pi = "PATH_INFO"
|
782
|
+
str = "GET http://example.com%s HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
|
783
|
+
{
|
784
|
+
"/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
|
785
|
+
"/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
|
786
|
+
"/1;a=b" => { qs => "", pi => "/1;a=b" },
|
787
|
+
"/1;a=b?" => { qs => "", pi => "/1;a=b" },
|
788
|
+
"/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
|
789
|
+
}.each do |uri,expect|
|
790
|
+
@hp.reset
|
791
|
+
@env.clear
|
792
|
+
buf = str % [ uri ]
|
793
|
+
req = @hp.request(@env, buf)
|
794
|
+
assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
|
795
|
+
assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
|
796
|
+
assert_equal expect[qs], req[qs], "#{qs} mismatch"
|
797
|
+
assert_equal expect[pi], req[pi], "#{pi} mismatch"
|
798
|
+
end
|
799
|
+
end
|
800
|
+
|
801
|
+
def test_negative_content_length
|
802
|
+
str = "PUT / HTTP/1.1\r\n" \
|
803
|
+
"Content-Length: -1\r\n" \
|
804
|
+
"\r\n"
|
805
|
+
assert_raises(Kcar::ParserError) do
|
806
|
+
@hp.request(@env, str)
|
807
|
+
end
|
808
|
+
end
|
809
|
+
|
810
|
+
def test_invalid_content_length
|
811
|
+
str = "PUT / HTTP/1.1\r\n" \
|
812
|
+
"Content-Length: zzzzz\r\n" \
|
813
|
+
"\r\n"
|
814
|
+
assert_raises(Kcar::ParserError) do
|
815
|
+
@hp.request(@env, str)
|
816
|
+
end
|
817
|
+
end
|
818
|
+
|
819
|
+
def test_ignore_version_header
|
820
|
+
req = @hp.request(@env, "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n")
|
821
|
+
expect = {
|
822
|
+
'REQUEST_PATH' => '/',
|
823
|
+
'SERVER_PROTOCOL' => 'HTTP/1.1',
|
824
|
+
'PATH_INFO' => '/',
|
825
|
+
'HTTP_VERSION' => 'HTTP/1.1',
|
826
|
+
'REQUEST_URI' => '/',
|
827
|
+
'REQUEST_METHOD' => 'GET',
|
828
|
+
'QUERY_STRING' => ''
|
829
|
+
}
|
830
|
+
assert_equal expect, req
|
831
|
+
end
|
832
|
+
|
833
|
+
def test_pipelined_requests
|
834
|
+
expect = {
|
835
|
+
'HTTP_HOST' => 'example.com',
|
836
|
+
'SERVER_NAME' => 'example.com',
|
837
|
+
'REQUEST_PATH' => '/',
|
838
|
+
'SERVER_PROTOCOL' => 'HTTP/1.1',
|
839
|
+
'PATH_INFO' => '/',
|
840
|
+
'HTTP_VERSION' => 'HTTP/1.1',
|
841
|
+
'REQUEST_URI' => '/',
|
842
|
+
'REQUEST_METHOD' => 'GET',
|
843
|
+
'QUERY_STRING' => ''
|
844
|
+
}
|
845
|
+
req1 = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
846
|
+
req2 = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
|
847
|
+
buf = req1 + req2
|
848
|
+
env1 = @hp.request(@env, buf)
|
849
|
+
assert_equal expect, env1
|
850
|
+
assert_equal req2, buf
|
851
|
+
assert_predicate @hp, :keepalive?
|
852
|
+
@env.clear
|
853
|
+
@hp.reset
|
854
|
+
env2 = @hp.request(@env, buf)
|
855
|
+
expect['HTTP_HOST'] = expect['SERVER_NAME'] = 'www.example.com'
|
856
|
+
assert_equal expect, env2
|
857
|
+
assert_equal '', buf
|
858
|
+
end
|
859
|
+
|
860
|
+
def test_identity_byte_headers
|
861
|
+
str = "PUT / HTTP/1.1\r\n"
|
862
|
+
str << "Content-Length: 123\r\n"
|
863
|
+
str << "\r"
|
864
|
+
buf = ''
|
865
|
+
str.each_byte { |byte|
|
866
|
+
buf << byte.chr
|
867
|
+
assert_nil @hp.request(@env, str)
|
868
|
+
}
|
869
|
+
buf << "\n"
|
870
|
+
req = @hp.request(@env, buf)
|
871
|
+
assert_equal '123', req['CONTENT_LENGTH']
|
872
|
+
assert_predicate buf, :empty?
|
873
|
+
assert ! @hp.keepalive?
|
874
|
+
assert_equal 123, @hp.body_bytes_left
|
875
|
+
dst = ""
|
876
|
+
buf = '.' * 123
|
877
|
+
@hp.filter_body(dst, buf)
|
878
|
+
assert_equal '.' * 123, dst
|
879
|
+
assert_equal "", buf
|
880
|
+
assert_predicate @hp, :keepalive?
|
881
|
+
end
|
882
|
+
|
883
|
+
def test_identity_step_headers
|
884
|
+
str = "PUT / HTTP/1.1\r\n"
|
885
|
+
assert_nil @hp.request(@env, str)
|
886
|
+
str << "Content-Length: 123\r\n"
|
887
|
+
assert_nil @hp.request(@env, str)
|
888
|
+
str << "\r\n"
|
889
|
+
req = @hp.request(@env, str)
|
890
|
+
assert_equal '123', req['CONTENT_LENGTH']
|
891
|
+
assert_equal 0, str.size
|
892
|
+
assert_not_predicate @hp, :keepalive?
|
893
|
+
dst = ""
|
894
|
+
buf = '.' * 123
|
895
|
+
@hp.filter_body(dst, buf)
|
896
|
+
assert_equal '.' * 123, dst
|
897
|
+
assert_equal "", buf
|
898
|
+
assert_predicate @hp, :keepalive?
|
899
|
+
end
|
900
|
+
|
901
|
+
def test_identity_oneshot_header
|
902
|
+
str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
|
903
|
+
req = @hp.request(@env, str)
|
904
|
+
assert_equal '123', req['CONTENT_LENGTH']
|
905
|
+
assert_equal 0, str.size
|
906
|
+
assert_not_predicate @hp, :keepalive?
|
907
|
+
dst = ""
|
908
|
+
buf = '.' * 123
|
909
|
+
@hp.filter_body(dst, buf)
|
910
|
+
assert_equal '.' * 123, dst
|
911
|
+
assert_equal "", buf
|
912
|
+
assert_predicate @hp, :keepalive?
|
913
|
+
end
|
914
|
+
|
915
|
+
def test_identity_oneshot_header_with_body
|
916
|
+
body = ('a' * 123).freeze
|
917
|
+
str = "PUT / HTTP/1.1\r\n" \
|
918
|
+
"Content-Length: #{body.length}\r\n" \
|
919
|
+
"\r\n#{body}"
|
920
|
+
req = @hp.request(@env, str)
|
921
|
+
assert_equal '123', req['CONTENT_LENGTH']
|
922
|
+
assert_equal 123, str.size
|
923
|
+
assert_equal body, str
|
924
|
+
tmp = ''
|
925
|
+
assert_same tmp, @hp.filter_body(tmp, str)
|
926
|
+
assert_equal 0, str.size
|
927
|
+
assert_equal tmp, body
|
928
|
+
assert_equal body, @hp.filter_body(tmp, str)
|
929
|
+
assert_predicate @hp, :keepalive?
|
930
|
+
end
|
931
|
+
|
932
|
+
def test_identity_oneshot_header_with_body_partial
|
933
|
+
str = "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
|
934
|
+
req = @hp.request(@env, str)
|
935
|
+
assert_equal 1, str.size
|
936
|
+
assert_equal 'a', str
|
937
|
+
tmp = ''
|
938
|
+
assert_same tmp, @hp.filter_body(tmp, str)
|
939
|
+
assert_equal "", str
|
940
|
+
assert_equal "a", tmp
|
941
|
+
str << ' ' * 122
|
942
|
+
rv = @hp.filter_body(tmp, str)
|
943
|
+
assert_equal 122, tmp.size
|
944
|
+
assert_same tmp, rv
|
945
|
+
assert_equal "", str
|
946
|
+
assert_same tmp, @hp.filter_body(tmp, str)
|
947
|
+
assert_predicate @hp, :keepalive?
|
948
|
+
assert_equal '123', req['CONTENT_LENGTH']
|
949
|
+
end
|
950
|
+
|
951
|
+
def test_identity_oneshot_header_with_body_slop
|
952
|
+
str = "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
|
953
|
+
req = @hp.request(@env, str)
|
954
|
+
assert_equal 2, str.size
|
955
|
+
assert_equal 'aG', str
|
956
|
+
tmp = ''
|
957
|
+
assert_same tmp, @hp.filter_body(tmp, str)
|
958
|
+
assert_equal "G", str
|
959
|
+
assert_equal "a", @hp.filter_body(tmp, str)
|
960
|
+
assert_equal 1, tmp.size
|
961
|
+
assert_equal "a", tmp
|
962
|
+
assert_predicate @hp, :keepalive?
|
963
|
+
assert_equal '1', req['CONTENT_LENGTH']
|
964
|
+
end
|
965
|
+
|
966
|
+
def test_chunked
|
967
|
+
str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
|
968
|
+
req = @hp.request(@env, str)
|
969
|
+
assert_equal 0, str.size
|
970
|
+
tmp = ""
|
971
|
+
assert_predicate @hp, :chunked?
|
972
|
+
assert_nil @hp.filter_body(tmp, str << "6")
|
973
|
+
assert_equal 0, tmp.size
|
974
|
+
assert_nil @hp.filter_body(tmp, str << "\r\n")
|
975
|
+
assert_equal 0, str.size
|
976
|
+
assert_equal 0, tmp.size
|
977
|
+
tmp = ""
|
978
|
+
assert_nil @hp.filter_body(tmp, str << "..")
|
979
|
+
assert_equal "..", tmp
|
980
|
+
assert_nil @hp.filter_body(tmp, str << "abcd\r\n0\r\n")
|
981
|
+
assert_equal "abcd", tmp
|
982
|
+
assert_same tmp, @hp.filter_body(tmp, str << "PUT")
|
983
|
+
assert_equal "PUT", str
|
984
|
+
assert_not_predicate @hp, :keepalive?
|
985
|
+
str << "TY: FOO\r\n\r\n"
|
986
|
+
req = @hp.request(@env, str)
|
987
|
+
assert_equal "FOO", req["HTTP_PUTTY"]
|
988
|
+
assert_predicate @hp, :keepalive?
|
989
|
+
end
|
990
|
+
|
991
|
+
def test_chunked_empty
|
992
|
+
str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
|
993
|
+
req = @hp.request(@env, str)
|
994
|
+
assert_equal 0, str.size
|
995
|
+
tmp = ""
|
996
|
+
assert_same tmp, @hp.filter_body(tmp, str << "0\r\n\r\n")
|
997
|
+
assert_predicate @hp, :body_eof?
|
998
|
+
assert_equal "", tmp
|
999
|
+
assert_same req, @hp.request(@env, str)
|
1000
|
+
assert_equal "", str
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
def test_two_chunks
|
1004
|
+
str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
|
1005
|
+
req = @hp.request(@env, str)
|
1006
|
+
assert_same req, @env
|
1007
|
+
assert_equal 0, str.size
|
1008
|
+
tmp = ""
|
1009
|
+
assert_nil @hp.filter_body(tmp, str << "6")
|
1010
|
+
assert_equal 0, tmp.size
|
1011
|
+
assert_nil @hp.filter_body(tmp, str << "\r\n")
|
1012
|
+
assert_equal "", str
|
1013
|
+
assert_equal 0, tmp.size
|
1014
|
+
tmp = ""
|
1015
|
+
assert_nil @hp.filter_body(tmp, str << "..")
|
1016
|
+
assert_equal 2, tmp.size
|
1017
|
+
assert_equal "..", tmp
|
1018
|
+
assert_nil @hp.filter_body(tmp, str << "abcd\r\n1")
|
1019
|
+
assert_equal "abcd", tmp
|
1020
|
+
assert_nil @hp.filter_body(tmp, str << "\r")
|
1021
|
+
assert_equal "", tmp
|
1022
|
+
assert_nil @hp.filter_body(tmp, str << "\n")
|
1023
|
+
assert_equal "", tmp
|
1024
|
+
assert_nil @hp.filter_body(tmp, str << "z")
|
1025
|
+
assert_equal "z", tmp
|
1026
|
+
assert_nil @hp.filter_body(tmp, str << "\r\n")
|
1027
|
+
assert_nil @hp.filter_body(tmp, str << "0")
|
1028
|
+
assert_nil @hp.filter_body(tmp, str << "\r")
|
1029
|
+
rv = @hp.filter_body(tmp, str << "\nGET")
|
1030
|
+
assert_equal "GET", str
|
1031
|
+
assert_same tmp, rv
|
1032
|
+
assert_equal '', tmp
|
1033
|
+
assert_not_predicate @hp, :keepalive?
|
1034
|
+
end
|
1035
|
+
|
1036
|
+
def test_big_chunk
|
1037
|
+
str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
|
1038
|
+
"4000\r\nabcd"
|
1039
|
+
req = @hp.request(@env, str)
|
1040
|
+
tmp = ''
|
1041
|
+
assert_nil @hp.filter_body(tmp, str)
|
1042
|
+
assert_equal '', str
|
1043
|
+
str << ' ' * 16300
|
1044
|
+
assert_nil @hp.filter_body(tmp, str)
|
1045
|
+
assert_equal '', str
|
1046
|
+
str << ' ' * 80
|
1047
|
+
assert_nil @hp.filter_body(tmp, str)
|
1048
|
+
assert_equal '', str
|
1049
|
+
assert_not_predicate @hp, :body_eof?
|
1050
|
+
assert_same tmp, @hp.filter_body(tmp, str << "\r\n0\r\n")
|
1051
|
+
assert_equal "", tmp
|
1052
|
+
assert_predicate @hp, :body_eof?
|
1053
|
+
str << "\r\n"
|
1054
|
+
assert_same req, @hp.request(@env, str)
|
1055
|
+
assert_equal "", str
|
1056
|
+
assert_predicate @hp, :body_eof?
|
1057
|
+
assert_predicate @hp, :keepalive?
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
def test_two_chunks_oneshot
|
1061
|
+
str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
|
1062
|
+
"1\r\na\r\n2\r\n..\r\n0\r\n"
|
1063
|
+
req = @hp.request(@env, str)
|
1064
|
+
assert_same req, @env
|
1065
|
+
tmp = ''
|
1066
|
+
assert_nil @hp.filter_body(tmp, str)
|
1067
|
+
assert_equal 'a..', tmp
|
1068
|
+
assert_same tmp, @hp.filter_body(tmp, str)
|
1069
|
+
assert_not_predicate @hp, :keepalive?
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
def test_chunks_bytewise
|
1073
|
+
chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n"
|
1074
|
+
str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
|
1075
|
+
buf = str.dup
|
1076
|
+
req = @hp.request(@env, buf)
|
1077
|
+
assert_same req, @env
|
1078
|
+
assert_equal "", buf
|
1079
|
+
tmp = ''
|
1080
|
+
body = ''
|
1081
|
+
str = chunked[0..-2]
|
1082
|
+
str.each_byte { |byte|
|
1083
|
+
assert_nil @hp.filter_body(tmp, buf << byte.chr)
|
1084
|
+
body << tmp
|
1085
|
+
}
|
1086
|
+
assert_equal 'abcdefghijklmnop0123456789abcdefg', body
|
1087
|
+
assert_same tmp, @hp.filter_body(tmp, buf << "\n")
|
1088
|
+
assert_not_predicate @hp, :keepalive?
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
def test_trailers
|
1092
|
+
str = "PUT / HTTP/1.1\r\n" \
|
1093
|
+
"Trailer: Content-MD5\r\n" \
|
1094
|
+
"transfer-Encoding: chunked\r\n\r\n" \
|
1095
|
+
"1\r\na\r\n2\r\n..\r\n0\r\n"
|
1096
|
+
req = @hp.request(@env, str)
|
1097
|
+
assert_same req, @env
|
1098
|
+
assert_equal 'Content-MD5', req['HTTP_TRAILER']
|
1099
|
+
assert_nil req['HTTP_CONTENT_MD5']
|
1100
|
+
tmp = ''
|
1101
|
+
assert_nil @hp.filter_body(tmp, str)
|
1102
|
+
assert_equal 'a..', tmp
|
1103
|
+
md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
|
1104
|
+
assert_same tmp, @hp.filter_body(tmp, str)
|
1105
|
+
assert_equal '', str
|
1106
|
+
md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
|
1107
|
+
str << md5_hdr
|
1108
|
+
assert_nil @hp.request(@env, str)
|
1109
|
+
assert_equal md5_b64, req['HTTP_CONTENT_MD5']
|
1110
|
+
assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
|
1111
|
+
str << "\r"
|
1112
|
+
assert_nil @hp.request(@env, str)
|
1113
|
+
str << "\nGET / "
|
1114
|
+
req = @hp.request(@env, str)
|
1115
|
+
assert_equal "GET / ", str
|
1116
|
+
assert_predicate @hp, :keepalive?
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
def test_trailers_slowly
|
1120
|
+
str = "PUT / HTTP/1.1\r\n" \
|
1121
|
+
"Trailer: Content-MD5\r\n" \
|
1122
|
+
"transfer-Encoding: chunked\r\n\r\n" \
|
1123
|
+
"1\r\na\r\n2\r\n..\r\n0\r\n"
|
1124
|
+
req = @hp.request(@env, str)
|
1125
|
+
assert_equal 'Content-MD5', req['HTTP_TRAILER']
|
1126
|
+
assert_nil req['HTTP_CONTENT_MD5']
|
1127
|
+
tmp = ''
|
1128
|
+
assert_nil @hp.filter_body(tmp, str)
|
1129
|
+
assert_equal 'a..', tmp
|
1130
|
+
md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
|
1131
|
+
assert_same tmp, @hp.filter_body(tmp, str)
|
1132
|
+
assert_equal '', str
|
1133
|
+
assert_nil @hp.request(req, str)
|
1134
|
+
md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
|
1135
|
+
md5_hdr.each_byte { |byte|
|
1136
|
+
str << byte.chr
|
1137
|
+
assert_nil @hp.request(req, str)
|
1138
|
+
}
|
1139
|
+
assert_equal md5_b64, req['HTTP_CONTENT_MD5']
|
1140
|
+
assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
|
1141
|
+
str << "\r"
|
1142
|
+
assert_nil @hp.request(@env, str)
|
1143
|
+
str << "\n"
|
1144
|
+
assert_same req, @hp.request(@env, str)
|
1145
|
+
end
|
1146
|
+
|
1147
|
+
def test_max_chunk
|
1148
|
+
assert_operator Kcar::Parser::CHUNK_MAX, :>=, 0xffff
|
1149
|
+
str = "PUT / HTTP/1.1\r\n" \
|
1150
|
+
"transfer-Encoding: chunked\r\n\r\n" \
|
1151
|
+
"#{Kcar::Parser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
|
1152
|
+
req = @hp.request(@env, str)
|
1153
|
+
assert_same req, @env
|
1154
|
+
assert_nil @hp.body_bytes_left
|
1155
|
+
assert_nil @hp.filter_body('', str)
|
1156
|
+
assert_not_predicate @hp, :keepalive?
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
def test_max_body
|
1160
|
+
n = Kcar::Parser::LENGTH_MAX
|
1161
|
+
buf = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
|
1162
|
+
req = @hp.request(@env, buf)
|
1163
|
+
assert_equal n, req['CONTENT_LENGTH'].to_i
|
1164
|
+
# connection can only be persistent when body is drained:
|
1165
|
+
assert_not_predicate @hp, :keepalive?
|
1166
|
+
@hp.body_bytes_left = 1
|
1167
|
+
assert_not_predicate @hp, :keepalive?
|
1168
|
+
@hp.body_bytes_left = 0
|
1169
|
+
assert_predicate @hp, :keepalive?
|
1170
|
+
end
|
1171
|
+
|
1172
|
+
def test_overflow_chunk
|
1173
|
+
n = Kcar::Parser::CHUNK_MAX + 1
|
1174
|
+
str = "PUT / HTTP/1.1\r\n" \
|
1175
|
+
"transfer-Encoding: chunked\r\n\r\n" \
|
1176
|
+
"#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
|
1177
|
+
req = @hp.request(@env, str)
|
1178
|
+
assert_nil @hp.body_bytes_left
|
1179
|
+
assert_equal 'chunked', req['HTTP_TRANSFER_ENCODING']
|
1180
|
+
assert_raise(Kcar::ParserError) { @hp.filter_body('', str) }
|
1181
|
+
end
|
1182
|
+
|
1183
|
+
def test_overflow_content_length
|
1184
|
+
n = Kcar::Parser::LENGTH_MAX + 1
|
1185
|
+
buf ="PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
|
1186
|
+
assert_raise(Kcar::ParserError) do
|
1187
|
+
@hp.request(@env, buf)
|
1188
|
+
end
|
1189
|
+
end
|
1190
|
+
|
1191
|
+
def test_bad_chunk
|
1192
|
+
buf = "PUT / HTTP/1.1\r\n" \
|
1193
|
+
"transfer-Encoding: chunked\r\n\r\n" \
|
1194
|
+
"#zzz\r\na\r\n2\r\n..\r\n0\r\n"
|
1195
|
+
@hp.request(@env, buf)
|
1196
|
+
assert_nil @hp.body_bytes_left
|
1197
|
+
assert_raise(Kcar::ParserError) { @hp.filter_body("", buf) }
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
def test_bad_content_length
|
1201
|
+
buf = "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
|
1202
|
+
assert_raise(Kcar::ParserError) { @hp.request(@env, buf) }
|
1203
|
+
end
|
1204
|
+
|
1205
|
+
def test_bad_trailers
|
1206
|
+
str = "PUT / HTTP/1.1\r\n" \
|
1207
|
+
"Trailer: Transfer-Encoding\r\n" \
|
1208
|
+
"transfer-Encoding: chunked\r\n\r\n" \
|
1209
|
+
"1\r\na\r\n2\r\n..\r\n0\r\n"
|
1210
|
+
req = @hp.request(@env, str)
|
1211
|
+
assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
|
1212
|
+
tmp = ''
|
1213
|
+
assert_nil @hp.filter_body(tmp, str)
|
1214
|
+
assert_equal 'a..', tmp
|
1215
|
+
assert_equal '', str
|
1216
|
+
str << "Transfer-Encoding: identity\r\n\r\n"
|
1217
|
+
assert_raise(Kcar::ParserError) { @hp.request(@env, str) }
|
1218
|
+
end
|
1219
|
+
end
|