Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(88)

Side by Side Diff: net/http/http_network_transaction.cc

Issue 2753453003: Reject unadvertised encodings (Closed)
Patch Set: Reject unadvertised encodings. Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "net/http/http_network_transaction.h" 5 #include "net/http/http_network_transaction.h"
6 6
7 #include <memory> 7 #include <memory>
8 #include <set> 8 #include <set>
9 #include <utility> 9 #include <utility>
10 #include <vector> 10 #include <vector>
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
74 std::unique_ptr<base::Value> NetLogSSLCipherFallbackCallback( 74 std::unique_ptr<base::Value> NetLogSSLCipherFallbackCallback(
75 const GURL* url, 75 const GURL* url,
76 int net_error, 76 int net_error,
77 NetLogCaptureMode /* capture_mode */) { 77 NetLogCaptureMode /* capture_mode */) {
78 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); 78 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
79 dict->SetString("host_and_port", GetHostAndPort(*url)); 79 dict->SetString("host_and_port", GetHostAndPort(*url));
80 dict->SetInteger("net_error", net_error); 80 dict->SetInteger("net_error", net_error);
81 return std::move(dict); 81 return std::move(dict);
82 } 82 }
83 83
84 enum QualityParsingState {
85 BEFORE_QUALIFIER, // waiting for ";"
86 QUALIFIER, // waiting for "q"
87 EQUALS, // waiting for "="
88 ZERO_OR_ONE, // waiting for "0" or "1"
89 PERIOD, // waiting for "."
90 DECIMALS,
91 DONE
92 };
93
94 // Tests is specified encoding is allowed by the given "Accept-Encoding" header.
95 // It is expected that header value is somewhat well-formatted (according to the
96 // RFC2616), and no "content-coding" is a prefix of another "content-coding".
97 bool IsAcceptableEncoding(const std::string& accept_encoding,
Randy Smith (Not in Mondays) 2017/03/15 16:35:31 Confirming: This is here because there's no existi
eustas 2017/03/16 12:08:16 Yup, hasn't found parsers for "Accept-xxx" header.
Randy Smith (Not in Mondays) 2017/03/16 17:50:44 Stick in a TODO to that effect? (To move it somew
eustas 2017/03/20 13:05:18 Reworked and moved to HttpUtil
98 const std::string& encoding) {
99 size_t cursor = accept_encoding.find(encoding);
100 char c;
101
102 // Encoding is not mentioned.
103 if (cursor == std::string::npos)
104 return false;
105
106 // Check that match position is not in the middle of another entry.
107 if (cursor != 0) {
108 c = accept_encoding[cursor - 1];
109 if ((c != ' ') && (c != ','))
110 return false;
111 }
112 cursor += encoding.size();
113
114 QualityParsingState state = BEFORE_QUALIFIER;
115 int ones = 0;
116 int sum = 0;
117 int digits = 0;
118
119 for (; cursor < accept_encoding.size(); ++cursor) {
120 c = accept_encoding[cursor];
121 switch (state) {
122 case BEFORE_QUALIFIER:
123 if (c == ' ')
124 break;
125 if (c == ',')
126 return true;
127 if (c == ';') {
128 state = QUALIFIER;
129 break;
130 }
131 return false;
132
133 case QUALIFIER:
134 if (c == ' ')
135 break;
136 if (c == 'q' || c == 'Q') {
137 state = EQUALS;
138 break;
139 }
140 return false;
141
142 case EQUALS:
143 if (c == '=') {
144 state = ZERO_OR_ONE;
145 break;
146 }
147 return false;
148
149 case ZERO_OR_ONE:
150 if (c == '0' || c == '1') {
151 ones = c - '0';
152 state = PERIOD;
153 break;
154 }
155 return false;
156
157 case PERIOD:
158 if (c == '.') {
159 state = DECIMALS;
160 break;
161 }
162 return false;
163
164 case DECIMALS:
165 if (c == ',' || c == ' ') {
166 state = DONE;
167 break;
168 }
169 if (c >= '0' && c <= '9') {
170 sum += c - '0';
171 digits++;
172 break;
173 }
174 return false;
175
176 case DONE:
177 // assert(0)
178 break;
179 }
180 if (state == DONE)
181 break;
182 }
183
184 // Before "q".
185 if (state == BEFORE_QUALIFIER)
186 return true;
187
188 // Before value.
189 if (state == QUALIFIER || state == EQUALS || state == ZERO_OR_ONE)
190 return false;
191
192 // assert(state == PERIOD || state == DECIMALS || state == DONE
193 // Some value is read.
194
195 if (digits > 3)
196 return false;
197
198 if (ones == 0)
199 return (sum != 0);
200
201 // Reject values like "1.x", where x != 0
202 return (sum == 0);
203 }
204
84 } // namespace 205 } // namespace
85 206
86 //----------------------------------------------------------------------------- 207 //-----------------------------------------------------------------------------
87 208
88 HttpNetworkTransaction::HttpNetworkTransaction(RequestPriority priority, 209 HttpNetworkTransaction::HttpNetworkTransaction(RequestPriority priority,
89 HttpNetworkSession* session) 210 HttpNetworkSession* session)
90 : pending_auth_target_(HttpAuth::AUTH_NONE), 211 : pending_auth_target_(HttpAuth::AUTH_NONE),
91 io_callback_(base::Bind(&HttpNetworkTransaction::OnIOComplete, 212 io_callback_(base::Bind(&HttpNetworkTransaction::OnIOComplete,
92 base::Unretained(this))), 213 base::Unretained(this))),
93 session_(session), 214 session_(session),
(...skipping 444 matching lines...) Expand 10 before | Expand all | Expand 10 after
538 const HttpResponseInfo& proxy_response, 659 const HttpResponseInfo& proxy_response,
539 const SSLConfig& used_ssl_config, 660 const SSLConfig& used_ssl_config,
540 const ProxyInfo& used_proxy_info, 661 const ProxyInfo& used_proxy_info,
541 HttpAuthController* auth_controller) { 662 HttpAuthController* auth_controller) {
542 DCHECK(stream_request_.get()); 663 DCHECK(stream_request_.get());
543 DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_); 664 DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_);
544 665
545 establishing_tunnel_ = true; 666 establishing_tunnel_ = true;
546 response_.headers = proxy_response.headers; 667 response_.headers = proxy_response.headers;
547 response_.auth_challenge = proxy_response.auth_challenge; 668 response_.auth_challenge = proxy_response.auth_challenge;
669 // TODO(eustas): Should content encoding be checked here?
Randy Smith (Not in Mondays) 2017/03/15 16:35:32 Hmmm. I'm inclined to think yes. I'll ask Matt f
eustas 2017/03/16 12:08:16 Added similar check here.
548 headers_valid_ = true; 670 headers_valid_ = true;
549 server_ssl_config_ = used_ssl_config; 671 server_ssl_config_ = used_ssl_config;
550 proxy_info_ = used_proxy_info; 672 proxy_info_ = used_proxy_info;
551 673
552 auth_controllers_[HttpAuth::AUTH_PROXY] = auth_controller; 674 auth_controllers_[HttpAuth::AUTH_PROXY] = auth_controller;
553 pending_auth_target_ = HttpAuth::AUTH_PROXY; 675 pending_auth_target_ = HttpAuth::AUTH_PROXY;
554 676
555 DoCallback(OK); 677 DoCallback(OK);
556 } 678 }
557 679
558 void HttpNetworkTransaction::OnNeedsClientAuth( 680 void HttpNetworkTransaction::OnNeedsClientAuth(
559 const SSLConfig& used_ssl_config, 681 const SSLConfig& used_ssl_config,
560 SSLCertRequestInfo* cert_info) { 682 SSLCertRequestInfo* cert_info) {
561 DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_); 683 DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_);
562 684
563 server_ssl_config_ = used_ssl_config; 685 server_ssl_config_ = used_ssl_config;
564 response_.cert_request_info = cert_info; 686 response_.cert_request_info = cert_info;
565 OnIOComplete(ERR_SSL_CLIENT_AUTH_CERT_NEEDED); 687 OnIOComplete(ERR_SSL_CLIENT_AUTH_CERT_NEEDED);
566 } 688 }
567 689
568 void HttpNetworkTransaction::OnHttpsProxyTunnelResponse( 690 void HttpNetworkTransaction::OnHttpsProxyTunnelResponse(
569 const HttpResponseInfo& response_info, 691 const HttpResponseInfo& response_info,
570 const SSLConfig& used_ssl_config, 692 const SSLConfig& used_ssl_config,
571 const ProxyInfo& used_proxy_info, 693 const ProxyInfo& used_proxy_info,
572 HttpStream* stream) { 694 HttpStream* stream) {
573 DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_); 695 DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_);
574 696
575 CopyConnectionAttemptsFromStreamRequest(); 697 CopyConnectionAttemptsFromStreamRequest();
576 698
699 // TODO(eustas): Should content encoding be checked here?
577 headers_valid_ = true; 700 headers_valid_ = true;
578 response_ = response_info; 701 response_ = response_info;
579 server_ssl_config_ = used_ssl_config; 702 server_ssl_config_ = used_ssl_config;
580 proxy_info_ = used_proxy_info; 703 proxy_info_ = used_proxy_info;
581 if (stream_) { 704 if (stream_) {
582 total_received_bytes_ += stream_->GetTotalReceivedBytes(); 705 total_received_bytes_ += stream_->GetTotalReceivedBytes();
583 total_sent_bytes_ += stream_->GetTotalSentBytes(); 706 total_sent_bytes_ += stream_->GetTotalSentBytes();
584 } 707 }
585 stream_.reset(stream); 708 stream_.reset(stream);
586 stream_request_.reset(); // we're done with the stream request 709 stream_request_.reset(); // we're done with the stream request
(...skipping 652 matching lines...) Expand 10 before | Expand all | Expand 10 after
1239 // bizarre for SPDY. Assuming this logic is useful at all. 1362 // bizarre for SPDY. Assuming this logic is useful at all.
1240 // TODO(davidben): Bubble the error code up so we do not cache? 1363 // TODO(davidben): Bubble the error code up so we do not cache?
1241 if (result == ERR_CONNECTION_CLOSED && response_.headers.get()) 1364 if (result == ERR_CONNECTION_CLOSED && response_.headers.get())
1242 result = OK; 1365 result = OK;
1243 1366
1244 if (result < 0) 1367 if (result < 0)
1245 return HandleIOError(result); 1368 return HandleIOError(result);
1246 1369
1247 DCHECK(response_.headers.get()); 1370 DCHECK(response_.headers.get());
1248 1371
1372 if (response_.headers.get() && !ContentEncodingsValid()) {
1373 // TODO(eustas): report in UMA?
Randy Smith (Not in Mondays) 2017/03/15 16:35:32 See comment about which error.
eustas 2017/03/16 12:08:16 Done.
1374 // TODO(eustas): ignore, based on Finch flag?
Randy Smith (Not in Mondays) 2017/03/15 16:35:32 I'm inclined to think we can just watch the metric
eustas 2017/03/16 12:08:16 Done.
1375 return ERR_INVALID_CONTENT_ENCODING;
1376 }
1377
1249 // On a 408 response from the server ("Request Timeout") on a stale socket, 1378 // On a 408 response from the server ("Request Timeout") on a stale socket,
1250 // retry the request. 1379 // retry the request.
1251 // Headers can be NULL because of http://crbug.com/384554. 1380 // Headers can be NULL because of http://crbug.com/384554.
1252 if (response_.headers.get() && response_.headers->response_code() == 408 && 1381 if (response_.headers.get() && response_.headers->response_code() == 408 &&
1253 stream_->IsConnectionReused()) { 1382 stream_->IsConnectionReused()) {
1254 net_log_.AddEventWithNetErrorCode( 1383 net_log_.AddEventWithNetErrorCode(
1255 NetLogEventType::HTTP_TRANSACTION_RESTART_AFTER_ERROR, 1384 NetLogEventType::HTTP_TRANSACTION_RESTART_AFTER_ERROR,
1256 response_.headers->response_code()); 1385 response_.headers->response_code());
1257 // This will close the socket - it would be weird to try and reuse it, even 1386 // This will close the socket - it would be weird to try and reuse it, even
1258 // if the server doesn't actually close it. 1387 // if the server doesn't actually close it.
(...skipping 445 matching lines...) Expand 10 before | Expand all | Expand 10 after
1704 void HttpNetworkTransaction::CopyConnectionAttemptsFromStreamRequest() { 1833 void HttpNetworkTransaction::CopyConnectionAttemptsFromStreamRequest() {
1705 DCHECK(stream_request_); 1834 DCHECK(stream_request_);
1706 1835
1707 // Since the transaction can restart with auth credentials, it may create a 1836 // Since the transaction can restart with auth credentials, it may create a
1708 // stream more than once. Accumulate all of the connection attempts across 1837 // stream more than once. Accumulate all of the connection attempts across
1709 // those streams by appending them to the vector: 1838 // those streams by appending them to the vector:
1710 for (const auto& attempt : stream_request_->connection_attempts()) 1839 for (const auto& attempt : stream_request_->connection_attempts())
1711 connection_attempts_.push_back(attempt); 1840 connection_attempts_.push_back(attempt);
1712 } 1841 }
1713 1842
1843 bool HttpNetworkTransaction::ContentEncodingsValid() const {
1844 HttpResponseHeaders* headers = GetResponseHeaders();
1845 DCHECK(headers);
1846
1847 // It is OK, if both "Accept-Encoding" and "Content-Encoding" headers are
1848 // missing. It is a widely used setup in unit-tests.
Randy Smith (Not in Mondays) 2017/03/15 16:35:32 If this is specifically for unit tests, it's not v
eustas 2017/03/16 12:08:16 Pulled GetHeader out of loop.
1849
1850 bool accept_encoding_acquired = false;
1851 std::string accept_encoding;
1852 std::string encoding;
1853 size_t iter = 0;
1854 while (headers->EnumerateHeader(&iter, "Content-Encoding", &encoding)) {
1855 encoding = base::ToLowerASCII(encoding);
1856 // RFC2616 Section 3.5 recommendation: applications SHOULD consider "x-gzip"
1857 // and "x-compress" to be equivalent to "gzip" and "compress" respectively.
1858 if (encoding.compare("x-gzip") == 0)
1859 encoding = "gzip";
1860 if (encoding.compare("x-compress") == 0)
1861 encoding = "compress";
1862
1863 // Reject malformed encodings.
1864 if (encoding.find_first_of("=;, ") != std::string::npos)
1865 return false;
1866
1867 if (!accept_encoding_acquired) {
1868 if (!request_headers_.GetHeader(HttpRequestHeaders::kAcceptEncoding,
1869 &accept_encoding)) {
1870 // URLRequestHttpJob sets the header if no value is provided.
1871 // Other clients shoot themselves in a foot, if they don't.
1872 return false;
1873 }
1874 accept_encoding_acquired = true;
1875 }
1876
1877 if (!IsAcceptableEncoding(accept_encoding, encoding))
1878 return false;
1879 }
1880 return true;
1881 }
1882
1714 } // namespace net 1883 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698