unicorn 5.5.3 → 5.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/GIT-VERSION-GEN +1 -1
- data/ext/unicorn_http/unicorn_http.rl +42 -4
- data/lib/unicorn/http_request.rb +11 -0
- data/test/unit/test_http_parser_ng.rb +81 -0
- data/unicorn.gemspec +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67fce6dee5ff186d34900c6d95f974e225f51811102623e1f343cfbff700dde2
|
4
|
+
data.tar.gz: ef054e9c76e847ec2d3d9300ea95352da1df2e1e78f5525c02dac920d0d04ddf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd4c2ad09a0f315cb43a8ba8c229df23b52c4356d76a69818e2a56503022654a063070a69306a7853b6bedecc8552cc881e1e2c7e916ecc3de80c11125a973f1
|
7
|
+
data.tar.gz: c83258e4ed3534bae563c7b9dab30e1f3001982bff24a2d6e2d6c3925e01d0aa6ccfe3538ae9895a1bf49e1f9cba977b3e2a60533571605694315be35eec8d6f
|
data/GIT-VERSION-GEN
CHANGED
@@ -62,7 +62,8 @@ struct http_parser {
|
|
62
62
|
} len;
|
63
63
|
};
|
64
64
|
|
65
|
-
static ID id_set_backtrace;
|
65
|
+
static ID id_set_backtrace, id_is_chunked_p;
|
66
|
+
static VALUE cHttpParser;
|
66
67
|
|
67
68
|
#ifdef HAVE_RB_HASH_CLEAR /* Ruby >= 2.0 */
|
68
69
|
# define my_hash_clear(h) (void)rb_hash_clear(h)
|
@@ -220,6 +221,19 @@ static void write_cont_value(struct http_parser *hp,
|
|
220
221
|
rb_str_buf_cat(hp->cont, vptr, end + 1);
|
221
222
|
}
|
222
223
|
|
224
|
+
static int is_chunked(VALUE v)
|
225
|
+
{
|
226
|
+
/* common case first */
|
227
|
+
if (STR_CSTR_CASE_EQ(v, "chunked"))
|
228
|
+
return 1;
|
229
|
+
|
230
|
+
/*
|
231
|
+
* call Ruby function in unicorn/http_request.rb to deal with unlikely
|
232
|
+
* comma-delimited case
|
233
|
+
*/
|
234
|
+
return rb_funcall(cHttpParser, id_is_chunked_p, 1, v) != Qfalse;
|
235
|
+
}
|
236
|
+
|
223
237
|
static void write_value(struct http_parser *hp,
|
224
238
|
const char *buffer, const char *p)
|
225
239
|
{
|
@@ -246,7 +260,9 @@ static void write_value(struct http_parser *hp,
|
|
246
260
|
f = uncommon_field(field, flen);
|
247
261
|
} else if (f == g_http_connection) {
|
248
262
|
hp_keepalive_connection(hp, v);
|
249
|
-
} else if (f == g_content_length) {
|
263
|
+
} else if (f == g_content_length && !HP_FL_TEST(hp, CHUNKED)) {
|
264
|
+
if (hp->len.content)
|
265
|
+
parser_raise(eHttpParserError, "Content-Length already set");
|
250
266
|
hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v));
|
251
267
|
if (hp->len.content < 0)
|
252
268
|
parser_raise(eHttpParserError, "invalid Content-Length");
|
@@ -254,9 +270,30 @@ static void write_value(struct http_parser *hp,
|
|
254
270
|
HP_FL_SET(hp, HASBODY);
|
255
271
|
hp_invalid_if_trailer(hp);
|
256
272
|
} else if (f == g_http_transfer_encoding) {
|
257
|
-
if (
|
273
|
+
if (is_chunked(v)) {
|
274
|
+
if (HP_FL_TEST(hp, CHUNKED))
|
275
|
+
/*
|
276
|
+
* RFC 7230 3.3.1:
|
277
|
+
* A sender MUST NOT apply chunked more than once to a message body
|
278
|
+
* (i.e., chunking an already chunked message is not allowed).
|
279
|
+
*/
|
280
|
+
parser_raise(eHttpParserError, "Transfer-Encoding double chunked");
|
281
|
+
|
258
282
|
HP_FL_SET(hp, CHUNKED);
|
259
283
|
HP_FL_SET(hp, HASBODY);
|
284
|
+
|
285
|
+
/* RFC 7230 3.3.3, 3: favor chunked if Content-Length exists */
|
286
|
+
hp->len.content = 0;
|
287
|
+
} else if (HP_FL_TEST(hp, CHUNKED)) {
|
288
|
+
/*
|
289
|
+
* RFC 7230 3.3.3, point 3 states:
|
290
|
+
* If a Transfer-Encoding header field is present in a request and
|
291
|
+
* the chunked transfer coding is not the final encoding, the
|
292
|
+
* message body length cannot be determined reliably; the server
|
293
|
+
* MUST respond with the 400 (Bad Request) status code and then
|
294
|
+
* close the connection.
|
295
|
+
*/
|
296
|
+
parser_raise(eHttpParserError, "invalid Transfer-Encoding");
|
260
297
|
}
|
261
298
|
hp_invalid_if_trailer(hp);
|
262
299
|
} else if (f == g_http_trailer) {
|
@@ -931,7 +968,7 @@ static VALUE HttpParser_rssget(VALUE self)
|
|
931
968
|
|
932
969
|
void Init_unicorn_http(void)
|
933
970
|
{
|
934
|
-
VALUE mUnicorn
|
971
|
+
VALUE mUnicorn;
|
935
972
|
|
936
973
|
mUnicorn = rb_define_module("Unicorn");
|
937
974
|
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
@@ -991,5 +1028,6 @@ void Init_unicorn_http(void)
|
|
991
1028
|
#ifndef HAVE_RB_HASH_CLEAR
|
992
1029
|
id_clear = rb_intern("clear");
|
993
1030
|
#endif
|
1031
|
+
id_is_chunked_p = rb_intern("is_chunked?");
|
994
1032
|
}
|
995
1033
|
#undef SET_GLOBAL
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -188,4 +188,15 @@ def write_http_header(socket) # :nodoc:
|
|
188
188
|
HTTP_RESPONSE_START.each { |c| socket.write(c) }
|
189
189
|
end
|
190
190
|
end
|
191
|
+
|
192
|
+
# called by ext/unicorn_http/unicorn_http.rl via rb_funcall
|
193
|
+
def self.is_chunked?(v) # :nodoc:
|
194
|
+
vals = v.split(/[ \t]*,[ \t]*/).map!(&:downcase)
|
195
|
+
if vals.pop == 'chunked'.freeze
|
196
|
+
return true unless vals.include?('chunked'.freeze)
|
197
|
+
raise Unicorn::HttpParserError, 'double chunked', []
|
198
|
+
end
|
199
|
+
return false unless vals.include?('chunked'.freeze)
|
200
|
+
raise Unicorn::HttpParserError, 'chunked not last', []
|
201
|
+
end
|
191
202
|
end
|
@@ -11,6 +11,20 @@ def setup
|
|
11
11
|
@parser = HttpParser.new
|
12
12
|
end
|
13
13
|
|
14
|
+
# RFC 7230 allows gzip/deflate/compress Transfer-Encoding,
|
15
|
+
# but "chunked" must be last if used
|
16
|
+
def test_is_chunked
|
17
|
+
[ 'chunked,chunked', 'chunked,gzip', 'chunked,gzip,chunked' ].each do |x|
|
18
|
+
assert_raise(HttpParserError) { HttpParser.is_chunked?(x) }
|
19
|
+
end
|
20
|
+
[ 'gzip, chunked', 'gzip,chunked', 'gzip ,chunked' ].each do |x|
|
21
|
+
assert HttpParser.is_chunked?(x)
|
22
|
+
end
|
23
|
+
[ 'gzip', 'xhunked', 'xchunked' ].each do |x|
|
24
|
+
assert !HttpParser.is_chunked?(x)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
14
28
|
def test_parser_max_len
|
15
29
|
assert_raises(RangeError) do
|
16
30
|
HttpParser.max_header_len = 0xffffffff + 1
|
@@ -566,6 +580,73 @@ def test_invalid_content_length
|
|
566
580
|
end
|
567
581
|
end
|
568
582
|
|
583
|
+
def test_duplicate_content_length
|
584
|
+
str = "PUT / HTTP/1.1\r\n" \
|
585
|
+
"Content-Length: 1\r\n" \
|
586
|
+
"Content-Length: 9\r\n" \
|
587
|
+
"\r\n"
|
588
|
+
assert_raises(HttpParserError) { @parser.headers({}, str) }
|
589
|
+
end
|
590
|
+
|
591
|
+
def test_chunked_overrides_content_length
|
592
|
+
order = [ 'Transfer-Encoding: chunked', 'Content-Length: 666' ]
|
593
|
+
%w(a b).each do |x|
|
594
|
+
str = "PUT /#{x} HTTP/1.1\r\n" \
|
595
|
+
"#{order.join("\r\n")}" \
|
596
|
+
"\r\n\r\na\r\nhelloworld\r\n0\r\n\r\n"
|
597
|
+
order.reverse!
|
598
|
+
env = @parser.headers({}, str)
|
599
|
+
assert_nil @parser.content_length
|
600
|
+
assert_equal 'chunked', env['HTTP_TRANSFER_ENCODING']
|
601
|
+
assert_equal '666', env['CONTENT_LENGTH'],
|
602
|
+
'Content-Length logged so the app can log a possible client bug/attack'
|
603
|
+
@parser.filter_body(dst = '', str)
|
604
|
+
assert_equal 'helloworld', dst
|
605
|
+
@parser.parse # handle the non-existent trailer
|
606
|
+
assert @parser.next?
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
def test_chunked_order_good
|
611
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
612
|
+
"Transfer-Encoding: gzip\r\n" \
|
613
|
+
"Transfer-Encoding: chunked\r\n" \
|
614
|
+
"\r\n"
|
615
|
+
env = @parser.headers({}, str)
|
616
|
+
assert_equal 'gzip,chunked', env['HTTP_TRANSFER_ENCODING']
|
617
|
+
assert_nil @parser.content_length
|
618
|
+
|
619
|
+
@parser.clear
|
620
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
621
|
+
"Transfer-Encoding: gzip, chunked\r\n" \
|
622
|
+
"\r\n"
|
623
|
+
env = @parser.headers({}, str)
|
624
|
+
assert_equal 'gzip, chunked', env['HTTP_TRANSFER_ENCODING']
|
625
|
+
assert_nil @parser.content_length
|
626
|
+
end
|
627
|
+
|
628
|
+
def test_chunked_order_bad
|
629
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
630
|
+
"Transfer-Encoding: chunked\r\n" \
|
631
|
+
"Transfer-Encoding: gzip\r\n" \
|
632
|
+
"\r\n"
|
633
|
+
assert_raise(HttpParserError) { @parser.headers({}, str) }
|
634
|
+
end
|
635
|
+
|
636
|
+
def test_double_chunked
|
637
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
638
|
+
"Transfer-Encoding: chunked\r\n" \
|
639
|
+
"Transfer-Encoding: chunked\r\n" \
|
640
|
+
"\r\n"
|
641
|
+
assert_raise(HttpParserError) { @parser.headers({}, str) }
|
642
|
+
|
643
|
+
@parser.clear
|
644
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
645
|
+
"Transfer-Encoding: chunked,chunked\r\n" \
|
646
|
+
"\r\n"
|
647
|
+
assert_raise(HttpParserError) { @parser.headers({}, str) }
|
648
|
+
end
|
649
|
+
|
569
650
|
def test_backtrace_is_empty
|
570
651
|
begin
|
571
652
|
@parser.headers({}, "AAADFSFDSFD\r\n\r\n")
|
data/unicorn.gemspec
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
|
12
12
|
Gem::Specification.new do |s|
|
13
13
|
s.name = %q{unicorn}
|
14
|
-
s.version = (ENV['VERSION'] || '5.5.
|
14
|
+
s.version = (ENV['VERSION'] || '5.5.4').dup
|
15
15
|
s.authors = ['unicorn hackers']
|
16
16
|
s.summary = 'Rack HTTP server for fast clients and Unix'
|
17
17
|
s.description = File.read('README').split("\n\n")[1]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unicorn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.5.
|
4
|
+
version: 5.5.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- unicorn hackers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-03-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|