unicorn 5.5.3 → 5.5.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: def232de6614b9d1e5394c6a8ebde552a8c5d063ddc04e946478d7dbc3220526
4
- data.tar.gz: f1dc8153f477d7c81b9be38c0e6b184863d8eb5d6d35779f637f9259e76a1272
3
+ metadata.gz: 67fce6dee5ff186d34900c6d95f974e225f51811102623e1f343cfbff700dde2
4
+ data.tar.gz: ef054e9c76e847ec2d3d9300ea95352da1df2e1e78f5525c02dac920d0d04ddf
5
5
  SHA512:
6
- metadata.gz: 330cd4ce75af35523f2e6bbdee24794f42222d1c14ffabeccc37bfb5d8cd5e4774d940e6528e6fa72b2890c07acc02c4f917458459718794c1709d6cf0aa0493
7
- data.tar.gz: ecbc2df84176fa9a99522b06dc127fd5d19e78d782f8f027f8f74f25dca841295299ef310698fb7c42f2cee7bb9b3531dbd2331c328dd8e1d358666df6b2a706
6
+ metadata.gz: cd4c2ad09a0f315cb43a8ba8c229df23b52c4356d76a69818e2a56503022654a063070a69306a7853b6bedecc8552cc881e1e2c7e916ecc3de80c11125a973f1
7
+ data.tar.gz: c83258e4ed3534bae563c7b9dab30e1f3001982bff24a2d6e2d6c3925e01d0aa6ccfe3538ae9895a1bf49e1f9cba977b3e2a60533571605694315be35eec8d6f
data/GIT-VERSION-GEN CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- DEF_VER = "v5.5.3"
2
+ DEF_VER = "v5.5.4"
3
3
  CONSTANT = "Unicorn::Const::UNICORN_VERSION"
4
4
  RVF = "lib/unicorn/version.rb"
5
5
  GVF = "GIT-VERSION-FILE"
@@ -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 (STR_CSTR_CASE_EQ(v, "chunked")) {
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, cHttpParser;
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
@@ -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.3').dup
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.3
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-01-31 00:00:00.000000000 Z
11
+ date: 2020-03-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack