unicorn-rupcio 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.CHANGELOG.old +25 -0
- data/.document +28 -0
- data/.gitattributes +5 -0
- data/.gitignore +25 -0
- data/.mailmap +26 -0
- data/.manifest +144 -0
- data/.olddoc.yml +25 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +39 -0
- data/COPYING +674 -0
- data/DESIGN +99 -0
- data/Documentation/.gitignore +3 -0
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +70 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +39 -0
- data/GNUmakefile +318 -0
- data/HACKING +117 -0
- data/ISSUES +102 -0
- data/KNOWN_ISSUES +79 -0
- data/LICENSE +67 -0
- data/Links +58 -0
- data/PHILOSOPHY +139 -0
- data/README +165 -0
- data/Rakefile +17 -0
- data/SIGNALS +123 -0
- data/Sandbox +104 -0
- data/TODO +1 -0
- data/TUNING +119 -0
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/bin/unicorn +129 -0
- data/bin/unicorn_rails +210 -0
- data/examples/big_app_gc.rb +3 -0
- data/examples/echo.ru +27 -0
- data/examples/init.sh +102 -0
- data/examples/logger_mp_safe.rb +26 -0
- data/examples/logrotate.conf +44 -0
- data/examples/nginx.conf +156 -0
- data/examples/unicorn.conf.minimal.rb +14 -0
- data/examples/unicorn.conf.rb +111 -0
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +40 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +115 -0
- data/ext/unicorn_http/common_field_optimization.h +128 -0
- data/ext/unicorn_http/epollexclusive.h +128 -0
- data/ext/unicorn_http/ext_help.h +38 -0
- data/ext/unicorn_http/extconf.rb +40 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +91 -0
- data/ext/unicorn_http/unicorn_http.c +4348 -0
- data/ext/unicorn_http/unicorn_http.rl +1054 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn/app/old_rails/static.rb +60 -0
- data/lib/unicorn/app/old_rails.rb +36 -0
- data/lib/unicorn/cgi_wrapper.rb +148 -0
- data/lib/unicorn/configurator.rb +749 -0
- data/lib/unicorn/const.rb +22 -0
- data/lib/unicorn/http_request.rb +180 -0
- data/lib/unicorn/http_response.rb +95 -0
- data/lib/unicorn/http_server.rb +860 -0
- data/lib/unicorn/launcher.rb +63 -0
- data/lib/unicorn/oob_gc.rb +82 -0
- data/lib/unicorn/preread_input.rb +34 -0
- data/lib/unicorn/select_waiter.rb +7 -0
- data/lib/unicorn/socket_helper.rb +186 -0
- data/lib/unicorn/stream_input.rb +152 -0
- data/lib/unicorn/tee_input.rb +132 -0
- data/lib/unicorn/tmpio.rb +34 -0
- data/lib/unicorn/util.rb +91 -0
- data/lib/unicorn/version.rb +1 -0
- data/lib/unicorn/worker.rb +166 -0
- data/lib/unicorn.rb +137 -0
- data/man/man1/unicorn.1 +222 -0
- data/man/man1/unicorn_rails.1 +207 -0
- data/setup.rb +1587 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +5 -0
- data/t/README +49 -0
- data/t/active-unix-socket.t +110 -0
- data/t/back-out-of-upgrade.t +44 -0
- data/t/bin/unused_listen +40 -0
- data/t/client_body_buffer_size.ru +15 -0
- data/t/client_body_buffer_size.t +79 -0
- data/t/detach.ru +12 -0
- data/t/env.ru +4 -0
- data/t/fails-rack-lint.ru +6 -0
- data/t/heartbeat-timeout.ru +13 -0
- data/t/heartbeat-timeout.t +60 -0
- data/t/integration.ru +129 -0
- data/t/integration.t +509 -0
- data/t/lib.perl +309 -0
- data/t/listener_names.ru +5 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +18 -0
- data/t/oob_gc_path.ru +18 -0
- data/t/pid.ru +4 -0
- data/t/preread_input.ru +23 -0
- data/t/reload-bad-config.t +49 -0
- data/t/reopen-logs.ru +14 -0
- data/t/reopen-logs.t +36 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0012-reload-empty-config.sh +86 -0
- data/t/t0013-rewindable-input-false.sh +24 -0
- data/t/t0013.ru +13 -0
- data/t/t0014-rewindable-input-true.sh +24 -0
- data/t/t0014.ru +13 -0
- data/t/t0015-configurator-internals.sh +25 -0
- data/t/t0020-at_exit-handler.sh +49 -0
- data/t/t0021-process_detach.sh +29 -0
- data/t/t0022-listener_names-preload_app.sh +32 -0
- data/t/t0300-no-default-middleware.sh +20 -0
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +14 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +125 -0
- data/t/winch_ttin.t +64 -0
- data/t/working_directory.t +86 -0
- data/test/aggregate.rb +16 -0
- data/test/benchmark/README +60 -0
- data/test/benchmark/dd.ru +19 -0
- data/test/benchmark/ddstream.ru +51 -0
- data/test/benchmark/readinput.ru +41 -0
- data/test/benchmark/stack.ru +9 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1030 -0
- data/test/test_helper.rb +307 -0
- data/test/unit/test_configurator.rb +176 -0
- data/test/unit/test_droplet.rb +29 -0
- data/test/unit/test_http_parser.rb +885 -0
- data/test/unit/test_http_parser_ng.rb +715 -0
- data/test/unit/test_server.rb +245 -0
- data/test/unit/test_signals.rb +189 -0
- data/test/unit/test_socket_helper.rb +160 -0
- data/test/unit/test_stream_input.rb +211 -0
- data/test/unit/test_tee_input.rb +304 -0
- data/test/unit/test_util.rb +132 -0
- data/test/unit/test_waiter.rb +35 -0
- data/unicorn.gemspec +49 -0
- metadata +266 -0
@@ -0,0 +1,715 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: false
|
3
|
+
|
4
|
+
require './test/test_helper'
|
5
|
+
require 'digest/md5'
|
6
|
+
|
7
|
+
include Unicorn
|
8
|
+
|
9
|
+
class HttpParserNgTest < Test::Unit::TestCase
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@parser = HttpParser.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# RFC 7230 allows gzip/deflate/compress Transfer-Encoding,
|
16
|
+
# but "chunked" must be last if used
|
17
|
+
def test_is_chunked
|
18
|
+
[ 'chunked,chunked', 'chunked,gzip', 'chunked,gzip,chunked' ].each do |x|
|
19
|
+
assert_raise(HttpParserError) { HttpParser.is_chunked?(x) }
|
20
|
+
end
|
21
|
+
[ 'gzip, chunked', 'gzip,chunked', 'gzip ,chunked' ].each do |x|
|
22
|
+
assert HttpParser.is_chunked?(x)
|
23
|
+
end
|
24
|
+
[ 'gzip', 'xhunked', 'xchunked' ].each do |x|
|
25
|
+
assert !HttpParser.is_chunked?(x)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_parser_max_len
|
30
|
+
assert_raises(RangeError) do
|
31
|
+
HttpParser.max_header_len = 0xffffffff + 1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_next_clear
|
36
|
+
r = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
37
|
+
@parser.buf << r
|
38
|
+
@parser.parse
|
39
|
+
@parser.response_start_sent = true
|
40
|
+
assert @parser.keepalive?
|
41
|
+
assert @parser.next?
|
42
|
+
assert @parser.response_start_sent
|
43
|
+
|
44
|
+
# persistent client makes another request:
|
45
|
+
@parser.buf << r
|
46
|
+
@parser.parse
|
47
|
+
assert @parser.keepalive?
|
48
|
+
assert @parser.next?
|
49
|
+
assert_equal false, @parser.response_start_sent
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_response_start_sent
|
53
|
+
assert_equal false, @parser.response_start_sent, "default is false"
|
54
|
+
@parser.response_start_sent = true
|
55
|
+
assert_equal true, @parser.response_start_sent
|
56
|
+
@parser.response_start_sent = false
|
57
|
+
assert_equal false, @parser.response_start_sent
|
58
|
+
@parser.response_start_sent = true
|
59
|
+
@parser.clear
|
60
|
+
assert_equal false, @parser.response_start_sent
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_connection_TE
|
64
|
+
@parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: TE\r\n"
|
65
|
+
@parser.buf << "TE: trailers\r\n\r\n"
|
66
|
+
@parser.parse
|
67
|
+
assert @parser.keepalive?
|
68
|
+
assert @parser.next?
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_keepalive_requests_with_next?
|
72
|
+
req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
|
73
|
+
expect = {
|
74
|
+
"SERVER_NAME" => "example.com",
|
75
|
+
"HTTP_HOST" => "example.com",
|
76
|
+
"rack.url_scheme" => "http",
|
77
|
+
"REQUEST_PATH" => "/",
|
78
|
+
"SERVER_PROTOCOL" => "HTTP/1.1",
|
79
|
+
"PATH_INFO" => "/",
|
80
|
+
"HTTP_VERSION" => "HTTP/1.1",
|
81
|
+
"REQUEST_URI" => "/",
|
82
|
+
"SERVER_PORT" => "80",
|
83
|
+
"REQUEST_METHOD" => "GET",
|
84
|
+
"QUERY_STRING" => ""
|
85
|
+
}.freeze
|
86
|
+
100.times do |nr|
|
87
|
+
@parser.buf << req
|
88
|
+
assert_equal expect, @parser.parse
|
89
|
+
assert @parser.next?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_default_keepalive_is_off
|
94
|
+
assert ! @parser.keepalive?
|
95
|
+
assert ! @parser.next?
|
96
|
+
@parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
97
|
+
@parser.parse
|
98
|
+
assert @parser.keepalive?
|
99
|
+
@parser.clear
|
100
|
+
assert ! @parser.keepalive?
|
101
|
+
assert ! @parser.next?
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_identity_byte_headers
|
105
|
+
req = @parser.env
|
106
|
+
str = "PUT / HTTP/1.1\r\n"
|
107
|
+
str << "Content-Length: 123\r\n"
|
108
|
+
str << "\r"
|
109
|
+
hdr = @parser.buf
|
110
|
+
str.each_byte { |byte|
|
111
|
+
hdr << byte.chr
|
112
|
+
assert_nil @parser.parse
|
113
|
+
}
|
114
|
+
hdr << "\n"
|
115
|
+
assert_equal req.object_id, @parser.parse.object_id
|
116
|
+
assert_equal '123', req['CONTENT_LENGTH']
|
117
|
+
assert_equal 0, hdr.size
|
118
|
+
assert ! @parser.keepalive?
|
119
|
+
assert @parser.headers?
|
120
|
+
assert_equal 123, @parser.content_length
|
121
|
+
dst = ""
|
122
|
+
buf = '.' * 123
|
123
|
+
@parser.filter_body(dst, buf)
|
124
|
+
assert_equal '.' * 123, dst
|
125
|
+
assert_equal "", buf
|
126
|
+
assert @parser.keepalive?
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_identity_step_headers
|
130
|
+
req = @parser.env
|
131
|
+
str = @parser.buf
|
132
|
+
str << "PUT / HTTP/1.1\r\n"
|
133
|
+
assert ! @parser.parse
|
134
|
+
str << "Content-Length: 123\r\n"
|
135
|
+
assert ! @parser.parse
|
136
|
+
str << "\r\n"
|
137
|
+
assert_equal req.object_id, @parser.parse.object_id
|
138
|
+
assert_equal '123', req['CONTENT_LENGTH']
|
139
|
+
assert_equal 0, str.size
|
140
|
+
assert ! @parser.keepalive?
|
141
|
+
assert @parser.headers?
|
142
|
+
dst = ""
|
143
|
+
buf = '.' * 123
|
144
|
+
@parser.filter_body(dst, buf)
|
145
|
+
assert_equal '.' * 123, dst
|
146
|
+
assert_equal "", buf
|
147
|
+
assert @parser.keepalive?
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_identity_oneshot_header
|
151
|
+
req = @parser.env
|
152
|
+
str = @parser.buf
|
153
|
+
str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\n"
|
154
|
+
assert_equal req.object_id, @parser.parse.object_id
|
155
|
+
assert_equal '123', req['CONTENT_LENGTH']
|
156
|
+
assert_equal 0, str.size
|
157
|
+
assert ! @parser.keepalive?
|
158
|
+
assert @parser.headers?
|
159
|
+
dst = ""
|
160
|
+
buf = '.' * 123
|
161
|
+
@parser.filter_body(dst, buf)
|
162
|
+
assert_equal '.' * 123, dst
|
163
|
+
assert_equal "", buf
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_identity_oneshot_header_with_body
|
167
|
+
body = ('a' * 123).freeze
|
168
|
+
req = @parser.env
|
169
|
+
str = @parser.buf
|
170
|
+
str << "PUT / HTTP/1.1\r\n" \
|
171
|
+
"Content-Length: #{body.length}\r\n" \
|
172
|
+
"\r\n#{body}"
|
173
|
+
assert_equal req.object_id, @parser.parse.object_id
|
174
|
+
assert_equal '123', req['CONTENT_LENGTH']
|
175
|
+
assert_equal 123, str.size
|
176
|
+
assert_equal body, str
|
177
|
+
tmp = ''
|
178
|
+
assert_nil @parser.filter_body(tmp, str)
|
179
|
+
assert_equal 0, str.size
|
180
|
+
assert_equal tmp, body
|
181
|
+
assert_equal "", @parser.filter_body(tmp, str)
|
182
|
+
assert @parser.keepalive?
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_identity_oneshot_header_with_body_partial
|
186
|
+
str = @parser.buf
|
187
|
+
str << "PUT / HTTP/1.1\r\nContent-Length: 123\r\n\r\na"
|
188
|
+
assert_equal Hash, @parser.parse.class
|
189
|
+
assert_equal 1, str.size
|
190
|
+
assert_equal 'a', str
|
191
|
+
tmp = ''
|
192
|
+
assert_nil @parser.filter_body(tmp, str)
|
193
|
+
assert_equal "", str
|
194
|
+
assert_equal "a", tmp
|
195
|
+
str << ' ' * 122
|
196
|
+
rv = @parser.filter_body(tmp, str)
|
197
|
+
assert_equal 122, tmp.size
|
198
|
+
assert_nil rv
|
199
|
+
assert_equal "", str
|
200
|
+
assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
|
201
|
+
assert @parser.keepalive?
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_identity_oneshot_header_with_body_slop
|
205
|
+
str = @parser.buf
|
206
|
+
str << "PUT / HTTP/1.1\r\nContent-Length: 1\r\n\r\naG"
|
207
|
+
assert_equal Hash, @parser.parse.class
|
208
|
+
assert_equal 2, str.size
|
209
|
+
assert_equal 'aG', str
|
210
|
+
tmp = ''
|
211
|
+
assert_nil @parser.filter_body(tmp, str)
|
212
|
+
assert_equal "G", str
|
213
|
+
assert_equal "G", @parser.filter_body(tmp, str)
|
214
|
+
assert_equal 1, tmp.size
|
215
|
+
assert_equal "a", tmp
|
216
|
+
assert @parser.keepalive?
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_chunked
|
220
|
+
str = @parser.buf
|
221
|
+
req = @parser.env
|
222
|
+
str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
|
223
|
+
assert_equal req, @parser.parse, "msg=#{str}"
|
224
|
+
assert_equal 0, str.size
|
225
|
+
tmp = ""
|
226
|
+
assert_nil @parser.filter_body(tmp, str << "6")
|
227
|
+
assert_equal 0, tmp.size
|
228
|
+
assert_nil @parser.filter_body(tmp, str << "\r\n")
|
229
|
+
assert_equal 0, str.size
|
230
|
+
assert_equal 0, tmp.size
|
231
|
+
tmp = ""
|
232
|
+
assert_nil @parser.filter_body(tmp, str << "..")
|
233
|
+
assert_equal "..", tmp
|
234
|
+
assert_nil @parser.filter_body(tmp, str << "abcd\r\n0\r\n")
|
235
|
+
assert_equal "abcd", tmp
|
236
|
+
assert_equal str.object_id, @parser.filter_body(tmp, str << "PUT").object_id
|
237
|
+
assert_equal "PUT", str
|
238
|
+
assert ! @parser.keepalive?
|
239
|
+
str << "TY: FOO\r\n\r\n"
|
240
|
+
assert_equal req, @parser.parse
|
241
|
+
assert_equal "FOO", req["HTTP_PUTTY"]
|
242
|
+
assert @parser.keepalive?
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_chunked_empty
|
246
|
+
str = @parser.buf
|
247
|
+
req = @parser.env
|
248
|
+
str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
|
249
|
+
assert_equal req, @parser.parse, "msg=#{str}"
|
250
|
+
assert_equal 0, str.size
|
251
|
+
tmp = ""
|
252
|
+
assert_equal str, @parser.filter_body(tmp, str << "0\r\n\r\n")
|
253
|
+
assert_equal "", tmp
|
254
|
+
end
|
255
|
+
|
256
|
+
def test_two_chunks
|
257
|
+
str = @parser.buf
|
258
|
+
str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
|
259
|
+
req = @parser.env
|
260
|
+
assert_equal req, @parser.parse
|
261
|
+
assert_equal 0, str.size
|
262
|
+
tmp = ""
|
263
|
+
assert_nil @parser.filter_body(tmp, str << "6")
|
264
|
+
assert_equal 0, tmp.size
|
265
|
+
assert_nil @parser.filter_body(tmp, str << "\r\n")
|
266
|
+
assert_equal "", str
|
267
|
+
assert_equal 0, tmp.size
|
268
|
+
tmp = ""
|
269
|
+
assert_nil @parser.filter_body(tmp, str << "..")
|
270
|
+
assert_equal 2, tmp.size
|
271
|
+
assert_equal "..", tmp
|
272
|
+
assert_nil @parser.filter_body(tmp, str << "abcd\r\n1")
|
273
|
+
assert_equal "abcd", tmp
|
274
|
+
assert_nil @parser.filter_body(tmp, str << "\r")
|
275
|
+
assert_equal "", tmp
|
276
|
+
assert_nil @parser.filter_body(tmp, str << "\n")
|
277
|
+
assert_equal "", tmp
|
278
|
+
assert_nil @parser.filter_body(tmp, str << "z")
|
279
|
+
assert_equal "z", tmp
|
280
|
+
assert_nil @parser.filter_body(tmp, str << "\r\n")
|
281
|
+
assert_nil @parser.filter_body(tmp, str << "0")
|
282
|
+
assert_nil @parser.filter_body(tmp, str << "\r")
|
283
|
+
rv = @parser.filter_body(tmp, str << "\nGET")
|
284
|
+
assert_equal "GET", rv
|
285
|
+
assert_equal str.object_id, rv.object_id
|
286
|
+
assert ! @parser.keepalive?
|
287
|
+
end
|
288
|
+
|
289
|
+
def test_big_chunk
|
290
|
+
str = @parser.buf
|
291
|
+
str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
|
292
|
+
"4000\r\nabcd"
|
293
|
+
req = @parser.env
|
294
|
+
assert_equal req, @parser.parse
|
295
|
+
tmp = ''
|
296
|
+
assert_nil @parser.filter_body(tmp, str)
|
297
|
+
assert_equal '', str
|
298
|
+
str << ' ' * 16300
|
299
|
+
assert_nil @parser.filter_body(tmp, str)
|
300
|
+
assert_equal '', str
|
301
|
+
str << ' ' * 80
|
302
|
+
assert_nil @parser.filter_body(tmp, str)
|
303
|
+
assert_equal '', str
|
304
|
+
assert ! @parser.body_eof?
|
305
|
+
assert_equal "", @parser.filter_body(tmp, str << "\r\n0\r\n")
|
306
|
+
assert_equal "", tmp
|
307
|
+
assert @parser.body_eof?
|
308
|
+
str << "\r\n"
|
309
|
+
assert_equal req, @parser.parse
|
310
|
+
assert_equal "", str
|
311
|
+
assert @parser.body_eof?
|
312
|
+
assert @parser.keepalive?
|
313
|
+
end
|
314
|
+
|
315
|
+
def test_two_chunks_oneshot
|
316
|
+
str = @parser.buf
|
317
|
+
req = @parser.env
|
318
|
+
str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n" \
|
319
|
+
"1\r\na\r\n2\r\n..\r\n0\r\n"
|
320
|
+
assert_equal req, @parser.parse
|
321
|
+
tmp = ''
|
322
|
+
assert_nil @parser.filter_body(tmp, str)
|
323
|
+
assert_equal 'a..', tmp
|
324
|
+
rv = @parser.filter_body(tmp, str)
|
325
|
+
assert_equal rv.object_id, str.object_id
|
326
|
+
assert ! @parser.keepalive?
|
327
|
+
end
|
328
|
+
|
329
|
+
def test_chunks_bytewise
|
330
|
+
chunked = "10\r\nabcdefghijklmnop\r\n11\r\n0123456789abcdefg\r\n0\r\n"
|
331
|
+
str = "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
|
332
|
+
buf = @parser.buf
|
333
|
+
buf << str
|
334
|
+
req = @parser.env
|
335
|
+
assert_equal req, @parser.parse
|
336
|
+
assert_equal "", buf
|
337
|
+
tmp = ''
|
338
|
+
body = ''
|
339
|
+
str = chunked[0..-2]
|
340
|
+
str.each_byte { |byte|
|
341
|
+
assert_nil @parser.filter_body(tmp, buf << byte.chr)
|
342
|
+
body << tmp
|
343
|
+
}
|
344
|
+
assert_equal 'abcdefghijklmnop0123456789abcdefg', body
|
345
|
+
rv = @parser.filter_body(tmp, buf<< "\n")
|
346
|
+
assert_equal rv.object_id, buf.object_id
|
347
|
+
assert ! @parser.keepalive?
|
348
|
+
end
|
349
|
+
|
350
|
+
def test_trailers
|
351
|
+
req = @parser.env
|
352
|
+
str = @parser.buf
|
353
|
+
str << "PUT / HTTP/1.1\r\n" \
|
354
|
+
"Trailer: Content-MD5\r\n" \
|
355
|
+
"transfer-Encoding: chunked\r\n\r\n" \
|
356
|
+
"1\r\na\r\n2\r\n..\r\n0\r\n"
|
357
|
+
assert_equal req, @parser.parse
|
358
|
+
assert_equal 'Content-MD5', req['HTTP_TRAILER']
|
359
|
+
assert_nil req['HTTP_CONTENT_MD5']
|
360
|
+
tmp = ''
|
361
|
+
assert_nil @parser.filter_body(tmp, str)
|
362
|
+
assert_equal 'a..', tmp
|
363
|
+
md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
|
364
|
+
rv = @parser.filter_body(tmp, str)
|
365
|
+
assert_equal rv.object_id, str.object_id
|
366
|
+
assert_equal '', str
|
367
|
+
md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
|
368
|
+
str << md5_hdr
|
369
|
+
assert_nil @parser.trailers(req, str)
|
370
|
+
assert_equal md5_b64, req['HTTP_CONTENT_MD5']
|
371
|
+
assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
|
372
|
+
str << "\r"
|
373
|
+
assert_nil @parser.parse
|
374
|
+
str << "\nGET / "
|
375
|
+
assert_equal req, @parser.parse
|
376
|
+
assert_equal "GET / ", str
|
377
|
+
assert @parser.keepalive?
|
378
|
+
end
|
379
|
+
|
380
|
+
def test_trailers_slowly
|
381
|
+
str = @parser.buf
|
382
|
+
str << "PUT / HTTP/1.1\r\n" \
|
383
|
+
"Trailer: Content-MD5\r\n" \
|
384
|
+
"transfer-Encoding: chunked\r\n\r\n" \
|
385
|
+
"1\r\na\r\n2\r\n..\r\n0\r\n"
|
386
|
+
req = @parser.env
|
387
|
+
assert_equal req, @parser.parse
|
388
|
+
assert_equal 'Content-MD5', req['HTTP_TRAILER']
|
389
|
+
assert_nil req['HTTP_CONTENT_MD5']
|
390
|
+
tmp = ''
|
391
|
+
assert_nil @parser.filter_body(tmp, str)
|
392
|
+
assert_equal 'a..', tmp
|
393
|
+
md5_b64 = [ Digest::MD5.digest(tmp) ].pack('m').strip.freeze
|
394
|
+
rv = @parser.filter_body(tmp, str)
|
395
|
+
assert_equal rv.object_id, str.object_id
|
396
|
+
assert_equal '', str
|
397
|
+
assert_nil @parser.trailers(req, str)
|
398
|
+
md5_hdr = "Content-MD5: #{md5_b64}\r\n".freeze
|
399
|
+
md5_hdr.each_byte { |byte|
|
400
|
+
str << byte.chr
|
401
|
+
assert_nil @parser.trailers(req, str)
|
402
|
+
}
|
403
|
+
assert_equal md5_b64, req['HTTP_CONTENT_MD5']
|
404
|
+
assert_equal "CONTENT_MD5: #{md5_b64}\r\n", str
|
405
|
+
str << "\r"
|
406
|
+
assert_nil @parser.parse
|
407
|
+
str << "\n"
|
408
|
+
assert_equal req, @parser.parse
|
409
|
+
end
|
410
|
+
|
411
|
+
def test_max_chunk
|
412
|
+
str = @parser.buf
|
413
|
+
str << "PUT / HTTP/1.1\r\n" \
|
414
|
+
"transfer-Encoding: chunked\r\n\r\n" \
|
415
|
+
"#{HttpParser::CHUNK_MAX.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
|
416
|
+
req = @parser.env
|
417
|
+
assert_equal req, @parser.parse
|
418
|
+
assert_nil @parser.content_length
|
419
|
+
@parser.filter_body('', str)
|
420
|
+
assert ! @parser.keepalive?
|
421
|
+
end
|
422
|
+
|
423
|
+
def test_max_body
|
424
|
+
n = HttpParser::LENGTH_MAX
|
425
|
+
@parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
|
426
|
+
req = @parser.env
|
427
|
+
@parser.headers(req, @parser.buf)
|
428
|
+
assert_equal n, req['CONTENT_LENGTH'].to_i
|
429
|
+
assert ! @parser.keepalive?
|
430
|
+
end
|
431
|
+
|
432
|
+
def test_overflow_chunk
|
433
|
+
n = HttpParser::CHUNK_MAX + 1
|
434
|
+
str = @parser.buf
|
435
|
+
req = @parser.env
|
436
|
+
str << "PUT / HTTP/1.1\r\n" \
|
437
|
+
"transfer-Encoding: chunked\r\n\r\n" \
|
438
|
+
"#{n.to_s(16)}\r\na\r\n2\r\n..\r\n0\r\n"
|
439
|
+
assert_equal req, @parser.parse
|
440
|
+
assert_nil @parser.content_length
|
441
|
+
assert_raise(HttpParserError) { @parser.filter_body('', str) }
|
442
|
+
end
|
443
|
+
|
444
|
+
def test_overflow_content_length
|
445
|
+
n = HttpParser::LENGTH_MAX + 1
|
446
|
+
@parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
|
447
|
+
assert_raise(HttpParserError) { @parser.parse }
|
448
|
+
end
|
449
|
+
|
450
|
+
def test_bad_chunk
|
451
|
+
@parser.buf << "PUT / HTTP/1.1\r\n" \
|
452
|
+
"transfer-Encoding: chunked\r\n\r\n" \
|
453
|
+
"#zzz\r\na\r\n2\r\n..\r\n0\r\n"
|
454
|
+
req = @parser.env
|
455
|
+
assert_equal req, @parser.parse
|
456
|
+
assert_nil @parser.content_length
|
457
|
+
assert_raise(HttpParserError) { @parser.filter_body("", @parser.buf) }
|
458
|
+
end
|
459
|
+
|
460
|
+
def test_bad_content_length
|
461
|
+
@parser.buf << "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
|
462
|
+
assert_raise(HttpParserError) { @parser.parse }
|
463
|
+
end
|
464
|
+
|
465
|
+
def test_bad_trailers
|
466
|
+
str = @parser.buf
|
467
|
+
req = @parser.env
|
468
|
+
str << "PUT / HTTP/1.1\r\n" \
|
469
|
+
"Trailer: Transfer-Encoding\r\n" \
|
470
|
+
"transfer-Encoding: chunked\r\n\r\n" \
|
471
|
+
"1\r\na\r\n2\r\n..\r\n0\r\n"
|
472
|
+
assert_equal req, @parser.parse
|
473
|
+
assert_equal 'Transfer-Encoding', req['HTTP_TRAILER']
|
474
|
+
tmp = ''
|
475
|
+
assert_nil @parser.filter_body(tmp, str)
|
476
|
+
assert_equal 'a..', tmp
|
477
|
+
assert_equal '', str
|
478
|
+
str << "Transfer-Encoding: identity\r\n\r\n"
|
479
|
+
assert_raise(HttpParserError) { @parser.parse }
|
480
|
+
end
|
481
|
+
|
482
|
+
def test_repeat_headers
|
483
|
+
str = "PUT / HTTP/1.1\r\n" \
|
484
|
+
"Trailer: Content-MD5\r\n" \
|
485
|
+
"Trailer: Content-SHA1\r\n" \
|
486
|
+
"transfer-Encoding: chunked\r\n\r\n" \
|
487
|
+
"1\r\na\r\n2\r\n..\r\n0\r\n"
|
488
|
+
req = @parser.env
|
489
|
+
@parser.buf << str
|
490
|
+
assert_equal req, @parser.parse
|
491
|
+
assert_equal 'Content-MD5,Content-SHA1', req['HTTP_TRAILER']
|
492
|
+
assert ! @parser.keepalive?
|
493
|
+
end
|
494
|
+
|
495
|
+
def test_parse_simple_request
|
496
|
+
parser = HttpParser.new
|
497
|
+
req = parser.env
|
498
|
+
parser.buf << "GET /read-rfc1945-if-you-dont-believe-me\r\n"
|
499
|
+
assert_equal req, parser.parse
|
500
|
+
assert_equal '', parser.buf
|
501
|
+
expect = {
|
502
|
+
"SERVER_NAME"=>"localhost",
|
503
|
+
"rack.url_scheme"=>"http",
|
504
|
+
"REQUEST_PATH"=>"/read-rfc1945-if-you-dont-believe-me",
|
505
|
+
"PATH_INFO"=>"/read-rfc1945-if-you-dont-believe-me",
|
506
|
+
"REQUEST_URI"=>"/read-rfc1945-if-you-dont-believe-me",
|
507
|
+
"SERVER_PORT"=>"80",
|
508
|
+
"SERVER_PROTOCOL"=>"HTTP/0.9",
|
509
|
+
"REQUEST_METHOD"=>"GET",
|
510
|
+
"QUERY_STRING"=>""
|
511
|
+
}
|
512
|
+
assert_equal expect, req
|
513
|
+
assert ! parser.headers?
|
514
|
+
end
|
515
|
+
|
516
|
+
def test_path_info_semicolon
|
517
|
+
qs = "QUERY_STRING"
|
518
|
+
pi = "PATH_INFO"
|
519
|
+
req = {}
|
520
|
+
str = "GET %s HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
521
|
+
{
|
522
|
+
"/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
|
523
|
+
"/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
|
524
|
+
"/1;a=b" => { qs => "", pi => "/1;a=b" },
|
525
|
+
"/1;a=b?" => { qs => "", pi => "/1;a=b" },
|
526
|
+
"/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
|
527
|
+
"*" => { qs => "", pi => "" },
|
528
|
+
}.each do |uri,expect|
|
529
|
+
assert_equal req, @parser.headers(req.clear, str % [ uri ])
|
530
|
+
req = req.dup
|
531
|
+
@parser.clear
|
532
|
+
assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
|
533
|
+
assert_equal expect[qs], req[qs], "#{qs} mismatch"
|
534
|
+
assert_equal expect[pi], req[pi], "#{pi} mismatch"
|
535
|
+
next if uri == "*"
|
536
|
+
uri = URI.parse("http://example.com#{uri}")
|
537
|
+
assert_equal uri.query.to_s, req[qs], "#{qs} mismatch URI.parse disagrees"
|
538
|
+
assert_equal uri.path, req[pi], "#{pi} mismatch URI.parse disagrees"
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
def test_path_info_semicolon_absolute
|
543
|
+
qs = "QUERY_STRING"
|
544
|
+
pi = "PATH_INFO"
|
545
|
+
req = {}
|
546
|
+
str = "GET http://example.com%s HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
|
547
|
+
{
|
548
|
+
"/1;a=b?c=d&e=f" => { qs => "c=d&e=f", pi => "/1;a=b" },
|
549
|
+
"/1?c=d&e=f" => { qs => "c=d&e=f", pi => "/1" },
|
550
|
+
"/1;a=b" => { qs => "", pi => "/1;a=b" },
|
551
|
+
"/1;a=b?" => { qs => "", pi => "/1;a=b" },
|
552
|
+
"/1?a=b;c=d&e=f" => { qs => "a=b;c=d&e=f", pi => "/1" },
|
553
|
+
}.each do |uri,expect|
|
554
|
+
assert_equal req, @parser.headers(req.clear, str % [ uri ])
|
555
|
+
req = req.dup
|
556
|
+
@parser.clear
|
557
|
+
assert_equal uri, req["REQUEST_URI"], "REQUEST_URI mismatch"
|
558
|
+
assert_equal "example.com", req["HTTP_HOST"], "Host: mismatch"
|
559
|
+
assert_equal expect[qs], req[qs], "#{qs} mismatch"
|
560
|
+
assert_equal expect[pi], req[pi], "#{pi} mismatch"
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
def test_negative_content_length
|
565
|
+
req = {}
|
566
|
+
str = "PUT / HTTP/1.1\r\n" \
|
567
|
+
"Content-Length: -1\r\n" \
|
568
|
+
"\r\n"
|
569
|
+
assert_raises(HttpParserError) do
|
570
|
+
@parser.headers(req, str)
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
def test_invalid_content_length
|
575
|
+
req = {}
|
576
|
+
str = "PUT / HTTP/1.1\r\n" \
|
577
|
+
"Content-Length: zzzzz\r\n" \
|
578
|
+
"\r\n"
|
579
|
+
assert_raises(HttpParserError) do
|
580
|
+
@parser.headers(req, str)
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
def test_duplicate_content_length
|
585
|
+
str = "PUT / HTTP/1.1\r\n" \
|
586
|
+
"Content-Length: 1\r\n" \
|
587
|
+
"Content-Length: 9\r\n" \
|
588
|
+
"\r\n"
|
589
|
+
assert_raises(HttpParserError) { @parser.headers({}, str) }
|
590
|
+
end
|
591
|
+
|
592
|
+
def test_chunked_overrides_content_length
|
593
|
+
order = [ 'Transfer-Encoding: chunked', 'Content-Length: 666' ]
|
594
|
+
%w(a b).each do |x|
|
595
|
+
str = "PUT /#{x} HTTP/1.1\r\n" \
|
596
|
+
"#{order.join("\r\n")}" \
|
597
|
+
"\r\n\r\na\r\nhelloworld\r\n0\r\n\r\n"
|
598
|
+
order.reverse!
|
599
|
+
env = @parser.headers({}, str)
|
600
|
+
assert_nil @parser.content_length
|
601
|
+
assert_equal 'chunked', env['HTTP_TRANSFER_ENCODING']
|
602
|
+
assert_equal '666', env['CONTENT_LENGTH'],
|
603
|
+
'Content-Length logged so the app can log a possible client bug/attack'
|
604
|
+
@parser.filter_body(dst = '', str)
|
605
|
+
assert_equal 'helloworld', dst
|
606
|
+
@parser.parse # handle the non-existent trailer
|
607
|
+
assert @parser.next?
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
def test_chunked_order_good
|
612
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
613
|
+
"Transfer-Encoding: gzip\r\n" \
|
614
|
+
"Transfer-Encoding: chunked\r\n" \
|
615
|
+
"\r\n"
|
616
|
+
env = @parser.headers({}, str)
|
617
|
+
assert_equal 'gzip,chunked', env['HTTP_TRANSFER_ENCODING']
|
618
|
+
assert_nil @parser.content_length
|
619
|
+
|
620
|
+
@parser.clear
|
621
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
622
|
+
"Transfer-Encoding: gzip, chunked\r\n" \
|
623
|
+
"\r\n"
|
624
|
+
env = @parser.headers({}, str)
|
625
|
+
assert_equal 'gzip, chunked', env['HTTP_TRANSFER_ENCODING']
|
626
|
+
assert_nil @parser.content_length
|
627
|
+
end
|
628
|
+
|
629
|
+
def test_chunked_order_bad
|
630
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
631
|
+
"Transfer-Encoding: chunked\r\n" \
|
632
|
+
"Transfer-Encoding: gzip\r\n" \
|
633
|
+
"\r\n"
|
634
|
+
assert_raise(HttpParserError) { @parser.headers({}, str) }
|
635
|
+
end
|
636
|
+
|
637
|
+
def test_double_chunked
|
638
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
639
|
+
"Transfer-Encoding: chunked\r\n" \
|
640
|
+
"Transfer-Encoding: chunked\r\n" \
|
641
|
+
"\r\n"
|
642
|
+
assert_raise(HttpParserError) { @parser.headers({}, str) }
|
643
|
+
|
644
|
+
@parser.clear
|
645
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
646
|
+
"Transfer-Encoding: chunked,chunked\r\n" \
|
647
|
+
"\r\n"
|
648
|
+
assert_raise(HttpParserError) { @parser.headers({}, str) }
|
649
|
+
end
|
650
|
+
|
651
|
+
def test_backtrace_is_empty
|
652
|
+
begin
|
653
|
+
@parser.headers({}, "AAADFSFDSFD\r\n\r\n")
|
654
|
+
assert false, "should never get here line:#{__LINE__}"
|
655
|
+
rescue HttpParserError => e
|
656
|
+
assert_equal [], e.backtrace
|
657
|
+
return
|
658
|
+
end
|
659
|
+
assert false, "should never get here line:#{__LINE__}"
|
660
|
+
end
|
661
|
+
|
662
|
+
def test_ignore_version_header
|
663
|
+
@parser.buf << "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n"
|
664
|
+
req = @parser.env
|
665
|
+
assert_equal req, @parser.parse
|
666
|
+
assert_equal '', @parser.buf
|
667
|
+
expect = {
|
668
|
+
"SERVER_NAME" => "localhost",
|
669
|
+
"rack.url_scheme" => "http",
|
670
|
+
"REQUEST_PATH" => "/",
|
671
|
+
"SERVER_PROTOCOL" => "HTTP/1.1",
|
672
|
+
"PATH_INFO" => "/",
|
673
|
+
"HTTP_VERSION" => "HTTP/1.1",
|
674
|
+
"REQUEST_URI" => "/",
|
675
|
+
"SERVER_PORT" => "80",
|
676
|
+
"REQUEST_METHOD" => "GET",
|
677
|
+
"QUERY_STRING" => ""
|
678
|
+
}
|
679
|
+
assert_equal expect, req
|
680
|
+
end
|
681
|
+
|
682
|
+
def test_pipelined_requests
|
683
|
+
host = "example.com"
|
684
|
+
expect = {
|
685
|
+
"HTTP_HOST" => host,
|
686
|
+
"SERVER_NAME" => host,
|
687
|
+
"REQUEST_PATH" => "/",
|
688
|
+
"rack.url_scheme" => "http",
|
689
|
+
"SERVER_PROTOCOL" => "HTTP/1.1",
|
690
|
+
"PATH_INFO" => "/",
|
691
|
+
"HTTP_VERSION" => "HTTP/1.1",
|
692
|
+
"REQUEST_URI" => "/",
|
693
|
+
"SERVER_PORT" => "80",
|
694
|
+
"REQUEST_METHOD" => "GET",
|
695
|
+
"QUERY_STRING" => ""
|
696
|
+
}
|
697
|
+
req1 = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
698
|
+
req2 = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
|
699
|
+
@parser.buf << (req1 + req2)
|
700
|
+
env1 = @parser.parse.dup
|
701
|
+
assert_equal expect, env1
|
702
|
+
assert_equal req2, @parser.buf
|
703
|
+
assert ! @parser.env.empty?
|
704
|
+
assert @parser.next?
|
705
|
+
assert @parser.keepalive?
|
706
|
+
assert @parser.headers?
|
707
|
+
assert_equal expect, @parser.env
|
708
|
+
env2 = @parser.parse.dup
|
709
|
+
host.replace "www.example.com"
|
710
|
+
assert_equal "www.example.com", expect["HTTP_HOST"]
|
711
|
+
assert_equal "www.example.com", expect["SERVER_NAME"]
|
712
|
+
assert_equal expect, env2
|
713
|
+
assert_equal "", @parser.buf
|
714
|
+
end
|
715
|
+
end
|