kcar 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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