diff --git a/proxy/hdrs/HTTP.cc b/proxy/hdrs/HTTP.cc index 9effe0a1c8a..7a56ff8934f 100644 --- a/proxy/hdrs/HTTP.cc +++ b/proxy/hdrs/HTTP.cc @@ -1781,6 +1781,30 @@ HTTPHdr::url_printed_length(unsigned normalization_flags) return zret; } +// Look for headers that the proxy will need to be able to process +// Return false if the proxy does not know how to process the header +// Currently just looking at TRANSFER_ENCODING. The proxy only knows how to +// process the chunked action +bool +HTTPHdr::check_hdr_implements() +{ + bool retval = true; + MIMEField *transfer_encode = + mime_hdr_field_find(this->m_http->m_fields_impl, MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING); + if (transfer_encode) { + int len; + const char *val; + do { + val = transfer_encode->value_get(&len); + if (len != 7 || 0 != strncasecmp(val, "chunked", len)) { + retval = false; + } + transfer_encode = transfer_encode->m_next_dup; + } while (retval && transfer_encode); + } + return retval; +} + /*********************************************************************** * * * M A R S H A L I N G * diff --git a/proxy/hdrs/HTTP.h b/proxy/hdrs/HTTP.h index 3196f08166d..c9dd513182d 100644 --- a/proxy/hdrs/HTTP.h +++ b/proxy/hdrs/HTTP.h @@ -665,6 +665,8 @@ class HTTPHdr : public MIMEHdr size_t max_request_line_size = UINT16_MAX, size_t max_hdr_field_size = UINT16_MAX); ParseResult parse_resp(HTTPParser *parser, IOBufferReader *r, int *bytes_used, bool eof); + bool check_hdr_implements(); + public: // Utility routines bool is_cache_control_set(const char *cc_directive_wks); diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc index 3b5deb15062..9b12eb83e8e 100644 --- a/proxy/http/HttpSM.cc +++ b/proxy/http/HttpSM.cc @@ -848,6 +848,12 @@ HttpSM::state_read_client_request_header(int event, void *data) case PARSE_RESULT_DONE: SMDebug("http", "[%" PRId64 "] done parsing client request header", sm_id); + if (!t_state.hdr_info.client_request.check_hdr_implements()) { + t_state.http_return_code = HTTP_STATUS_NOT_IMPLEMENTED; + call_transact_and_set_next_state(HttpTransact::BadRequest); + break; + } + if (_from_early_data) { // Only allow early data for safe methods defined in RFC7231 Section 4.2.1. // https://tools.ietf.org/html/rfc7231#section-4.2.1 @@ -2051,6 +2057,13 @@ HttpSM::state_read_server_response_header(int event, void *data) // fallthrough case PARSE_RESULT_DONE: + + if (!t_state.hdr_info.server_response.check_hdr_implements()) { + t_state.http_return_code = HTTP_STATUS_BAD_GATEWAY; + call_transact_and_set_next_state(HttpTransact::BadRequest); + break; + } + SMDebug("http_seq", "Done parsing server response header"); // Now that we know that we have all of the origin server diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc index 521ac5721e0..b92e8b35892 100644 --- a/proxy/http/HttpTransact.cc +++ b/proxy/http/HttpTransact.cc @@ -814,6 +814,10 @@ HttpTransact::BadRequest(State *s) status = s->http_return_code; reason = "URI Too Long"; break; + case HTTP_STATUS_NOT_IMPLEMENTED: + status = s->http_return_code; + reason = "Field not implemented"; + body_factory_template = "transcoding#unsupported"; default: break; } diff --git a/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py b/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py new file mode 100644 index 00000000000..cdfc0bf9cd2 --- /dev/null +++ b/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py @@ -0,0 +1,71 @@ +''' +''' +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +Test.Summary = ''' +Test unsupported values for chunked_encoding +''' + +Test.ContinueOnFail = True + +# Define default ATS +ts = Test.MakeATSProcess("ts", select_ports=True, enable_tls=False) +server = Test.MakeOriginServer("server") + +testName = "" +request_header = {"headers": "POST /case1 HTTP/1.1\r\nHost: www.example.com\r\nuuid:1\r\n\r\n", + "timestamp": "1469733493.993", + "body": "stuff" + } +response_header = {"headers": "HTTP/1.1 200 OK\r\nServer: uServer\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n", + "timestamp": "1469733493.993", + "body": "more stuff"} + +server.addResponse("sessionlog.json", request_header, response_header) + +ts.Disk.records_config.update({'proxy.config.diags.debug.enabled': 0, + 'proxy.config.diags.debug.tags': 'http'}) + +ts.Disk.remap_config.AddLine( + 'map / http://127.0.0.1:{0}'.format(server.Variables.Port) +) + +# HTTP1.1 POST: www.example.com/case1 with gzip transfer-encoding +tr = Test.AddTestRun() +tr.TimeOut = 5 +tr.Processes.Default.Command = 'curl -H "host: example.com" -H "transfer-encoding: gzip" -d "stuff" http://127.0.0.1:{0}/case1 --verbose'.format( + ts.Variables.port) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.StartBefore(server) +tr.Processes.Default.StartBefore(Test.Processes.ts) +tr.Processes.Default.Streams.All = Testers.ContainsExpression("501 Field not implemented", "Should fail") +tr.Processes.Default.Streams.All = Testers.ExcludesExpression("200 OK", "Should not succeed") +tr.StillRunningAfter = server +tr.StillRunningAfter = ts + +# HTTP1.1 POST: www.example.com/case1 with gzip and chunked transfer-encoding +tr = Test.AddTestRun() +tr.TimeOut = 5 +tr.Processes.Default.Command = 'curl -H "host: example.com" -H "transfer-encoding: gzip" -H "transfer-encoding: chunked" -d "stuff" http://127.0.0.1:{0}/case1 --verbose'.format( + ts.Variables.port) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.All = Testers.ContainsExpression("501 Field not implemented", "Should fail") +tr.Processes.Default.Streams.All = Testers.ExcludesExpression("200 OK", "Should not succeed") +tr.StillRunningAfter = server +tr.StillRunningAfter = ts