You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by bc...@apache.org on 2018/03/06 00:27:59 UTC

[trafficserver] 01/01: Respond with 400 code when Content-Length headers mismatch, remove duplicate copies of the Content-Length header with exactly same values, and remove Content-Length headers if Transfer-Encoding header exists.

This is an automated email from the ASF dual-hosted git repository.

bcall pushed a commit to branch content_length
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit c54b47b4ec5dfad855a5cb342d84796e35ec6be6
Author: Bryan Call <bc...@apache.org>
AuthorDate: Mon Mar 5 16:21:18 2018 -0800

    Respond with 400 code when Content-Length headers mismatch, remove
    duplicate copies of the Content-Length header with exactly same values,
    and remove Content-Length headers if Transfer-Encoding header exists.
---
 proxy/hdrs/HTTP.cc                      | 60 +++++++++++++++++++++++++++++++--
 proxy/hdrs/HTTP.h                       |  1 +
 tests/gold_tests/headers/syntax.test.py | 31 ++++++++++++++---
 3 files changed, 85 insertions(+), 7 deletions(-)

diff --git a/proxy/hdrs/HTTP.cc b/proxy/hdrs/HTTP.cc
index 77d0a6e..32b5aba 100644
--- a/proxy/hdrs/HTTP.cc
+++ b/proxy/hdrs/HTTP.cc
@@ -960,8 +960,12 @@ http_parser_parse_req(HTTPParser *parser, HdrHeap *heap, HTTPHdrImpl *hh, const
       }
 
       ParseResult ret = mime_parser_parse(&parser->m_mime_parser, heap, hh->m_fields_impl, start, end, must_copy_strings, eof);
+      // If we're done with the main parse do some validation
       if (ret == PARSE_RESULT_DONE) {
-        ret = validate_hdr_host(hh); // if we're done with the main parse, check HOST.
+        ret = validate_hdr_host(hh); // check HOST header
+      }
+      if (ret == PARSE_RESULT_DONE) {
+        ret = validate_hdr_content_length(heap, hh);
       }
       return ret;
     }
@@ -1111,8 +1115,12 @@ http_parser_parse_req(HTTPParser *parser, HdrHeap *heap, HTTPHdrImpl *hh, const
     parser->m_parsing_http = false;
 
     ParseResult ret = mime_parser_parse(&parser->m_mime_parser, heap, hh->m_fields_impl, start, end, must_copy_strings, eof);
+    // If we're done with the main parse do some validation
     if (ret == PARSE_RESULT_DONE) {
-      ret = validate_hdr_host(hh); // if we're done with the main parse, check HOST.
+      ret = validate_hdr_host(hh); // check HOST header
+    }
+    if (ret == PARSE_RESULT_DONE) {
+      ret = validate_hdr_content_length(heap, hh);
     }
     return ret;
   }
@@ -1156,6 +1164,54 @@ validate_hdr_host(HTTPHdrImpl *hh)
   return ret;
 }
 
+ParseResult
+validate_hdr_content_length(HdrHeap *heap, HTTPHdrImpl *hh)
+{
+  MIMEField *content_length_field = mime_hdr_field_find(hh->m_fields_impl, MIME_FIELD_CONTENT_LENGTH, MIME_LEN_CONTENT_LENGTH);
+
+  if (content_length_field) {
+    // RFC 7230 section 3.3.3:
+    // If a message is received with both a Transfer-Encoding and a
+    // Content-Length header field, the Transfer-Encoding overrides
+    // the Content-Length
+    if (mime_hdr_field_find(hh->m_fields_impl, MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING) != nullptr) {
+      // Delete all Content-Length headers
+      Debug("http", "Transfer-Encoding header and Content-Length headers the request, removing all Content-Length headers");
+      mime_hdr_field_delete(heap, hh->m_fields_impl, content_length_field);
+      return PARSE_RESULT_DONE;
+    }
+
+    // RFC 7230 section 3.3.3:
+    // If a message is received without Transfer-Encoding and with
+    // either multiple Content-Length header fields having differing
+    // field-values or a single Content-Length header field having an
+    // invalid value, then the message framing is invalid and the
+    // recipient MUST treat it as an unrecoverable error.  If this is a
+    // request message, the server MUST respond with a 400 (Bad Request)
+    // status code and then close the connection
+    int content_length_len         = 0;
+    const char *content_length_val = content_length_field->value_get(&content_length_len);
+
+    while (content_length_field->has_dups()) {
+      int content_length_len_2         = 0;
+      const char *content_length_val_2 = content_length_field->m_next_dup->value_get(&content_length_len_2);
+
+      if ((content_length_len != content_length_len_2) ||
+          (memcmp(content_length_val, content_length_val_2, content_length_len) != 0)) {
+        // Values are different, parse error
+        Debug("http", "Content-Length headers don't match, returning parse error");
+        return PARSE_RESULT_ERROR;
+      } else {
+        // Delete the duplicate since it has the same value
+        Debug("http", "Deleting duplicate Content-Length header");
+        mime_hdr_field_delete(heap, hh->m_fields_impl, content_length_field->m_next_dup, false);
+      }
+    }
+  }
+
+  return PARSE_RESULT_DONE;
+}
+
 /*-------------------------------------------------------------------------
   -------------------------------------------------------------------------*/
 
diff --git a/proxy/hdrs/HTTP.h b/proxy/hdrs/HTTP.h
index eb99817..0f59213 100644
--- a/proxy/hdrs/HTTP.h
+++ b/proxy/hdrs/HTTP.h
@@ -442,6 +442,7 @@ void http_parser_clear(HTTPParser *parser);
 ParseResult http_parser_parse_req(HTTPParser *parser, HdrHeap *heap, HTTPHdrImpl *hh, const char **start, const char *end,
                                   bool must_copy_strings, bool eof, bool strict_uri_parsing);
 ParseResult validate_hdr_host(HTTPHdrImpl *hh);
+ParseResult validate_hdr_content_length(HdrHeap *heap, HTTPHdrImpl *hh);
 ParseResult http_parser_parse_resp(HTTPParser *parser, HdrHeap *heap, HTTPHdrImpl *hh, const char **start, const char *end,
                                    bool must_copy_strings, bool eof);
 
diff --git a/tests/gold_tests/headers/syntax.test.py b/tests/gold_tests/headers/syntax.test.py
index e38b97e..2938304 100644
--- a/tests/gold_tests/headers/syntax.test.py
+++ b/tests/gold_tests/headers/syntax.test.py
@@ -64,21 +64,42 @@ tr = Test.AddTestRun()
 tr.Processes.Default.StartBefore(server)
 tr.Processes.Default.StartBefore(Test.Processes.ts)
 tr.Processes.Default.StartBefore(Test.Processes.ts, ready=When.PortOpen(ts.Variables.ssl_port))
-tr.Processes.Default.Command = 'curl -s -D - -v --ipv4 --http1.1 -H " foo: bar"  -H "Host: www.example.com" http://localhost:8080/'
+tr.Processes.Default.Command = 'curl -s -D - -v --ipv4 --http1.1 -H " foo: bar" -H "Host: www.example.com" http://localhost:8080/'
 tr.Processes.Default.ReturnCode = 0
 tr.Processes.Default.Streams.stdout = "syntax.200.gold"
 tr.StillRunningAfter = ts
 
-# Test 2 - 400 Response
+# Test 2 - 400 Response - Single space after field name
 tr = Test.AddTestRun()
-tr.Processes.Default.Command = 'curl -s -D - -v --ipv4 --http1.1 -H "foo : bar"  -H "Host: www.example.com" http://localhost:8080/'
+tr.Processes.Default.Command = 'curl -s -D - -v --ipv4 --http1.1 -H "foo : bar" -H "Host: www.example.com" http://localhost:8080/'
 tr.Processes.Default.ReturnCode = 0
 tr.Processes.Default.Streams.stdout = "syntax.400.gold"
 tr.StillRunningAfter = ts
 
-# Test 3 - 400 Response
+# Test 3 - 400 Response - Double space after field name
 tr = Test.AddTestRun()
-tr.Processes.Default.Command = 'curl -s -D - -v --ipv4 --http1.1 -H "foo  : bar"  -H "Host: www.example.com" http://localhost:8080/'
+tr.Processes.Default.Command = 'curl -s -D - -v --ipv4 --http1.1 -H "foo  : bar" -H "Host: www.example.com" http://localhost:8080/'
 tr.Processes.Default.ReturnCode = 0
 tr.Processes.Default.Streams.stdout = "syntax.400.gold"
 tr.StillRunningAfter = ts
+
+# Test 4 - 400 Response - Three different Content-Length headers
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = 'curl -s -D - -v --ipv4 --http1.1 -d "hello world" -H "Content-Length: 11" -H "Content-Length: 10" -H "Content-Length: 9" -H "Host: www.example.com" http://localhost:8080/'
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Streams.stdout = "syntax.400.gold"
+tr.StillRunningAfter = ts
+
+# Test 4 - 200 Response - Three same Content-Length headers
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = 'curl -s -D - -v --ipv4 --http1.1 -d "hello world" -H "Content-Length: 11" -H "Content-Length: 11" -H "Content-Length: 11" -H "Host: www.example.com" http://localhost:8080/'
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Streams.stdout = "syntax.200.gold"
+tr.StillRunningAfter = ts
+
+# Test 4 - 200 Response - Three different Content-Length headers with a Transfer ecoding header
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = 'curl -s -D - -v --ipv4 --http1.1 -d "hello world" -H "Transfer-Encoding: chunked" -H "Content-Length: 11" -H "Content-Length: 10" -H "Content-Length: 9" -H "Host: www.example.com" http://localhost:8080/'
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Streams.stdout = "syntax.200.gold"
+tr.StillRunningAfter = ts

-- 
To stop receiving notification emails like this one, please contact
bcall@apache.org.