kcar 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|