kcar 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 = any* >start_value %write_value;
64
+ field_value = content* >start_value %write_value;
36
65
 
37
- value_cont = lws+ any* >start_value %write_cont_value;
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
  }%%
@@ -1,23 +1,27 @@
1
- ENV["VERSION"] or abort "VERSION= must be specified"
2
- manifest = File.readlines('.manifest').map! { |x| x.chomp! }
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["VERSION"].dup
10
- s.homepage = Olddoc.config['rdoc_url']
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 = readme_description
13
- s.email = %q{kcar@bogomips.org}
14
- s.extra_rdoc_files = extra_rdoc_files(manifest)
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 = 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
- s.licenses = %w(GPLv2+ Ruby)
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
@@ -3,6 +3,6 @@ module Kcar
3
3
  autoload :Response, 'kcar/response'
4
4
  end
5
5
 
6
- require 'kcar/version'
7
- require 'kcar/parser'
6
+ require_relative 'kcar/version'
7
+ require_relative 'kcar/parser'
8
8
  require 'kcar_ext'
@@ -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
- Parser = Kcar::Parser
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, @parser = sock, hdr, unchunk, "", Parser.new
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 { |x| yield x } :
67
- each_identity { |x| yield x }
65
+ @parser.chunked? ? each_unchunk { |buf| yield buf } :
66
+ each_identity { |buf| yield buf }
68
67
  else
69
- @parser.chunked? ? each_rechunk { |x| yield x } :
70
- each_identity { |x| yield x }
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.respond_to?(:clear) ? dst.clear : @buf = ""
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 oldweb
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),)
@@ -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