diff options
author | ericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-09-13 00:45:27 +0000 |
---|---|---|
committer | ericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-09-13 00:45:27 +0000 |
commit | 231d5a36e476d013a91ca742bb8a0a2973cfee54 (patch) | |
tree | dc5c60f8fc054503f4971b770196ed89fbfc18c1 | |
parent | f4f2df8024d7adf583a61b742a32eaa17522f154 (diff) | |
download | chromium_src-231d5a36e476d013a91ca742bb8a0a2973cfee54.zip chromium_src-231d5a36e476d013a91ca742bb8a0a2973cfee54.tar.gz chromium_src-231d5a36e476d013a91ca742bb8a0a2973cfee54.tar.bz2 |
misc. http response status-line changes:
1. check for http 0.9 responses (no status line)
2. allow up to 4 bytes of junk to precede the http version.
3. distinguish between the parsed vs normalized http version (a TODO needed this).
Review URL: http://codereview.chromium.org/1934
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@2153 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | net/http/http_network_transaction.cc | 72 | ||||
-rw-r--r-- | net/http/http_network_transaction.h | 9 | ||||
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 122 | ||||
-rw-r--r-- | net/http/http_response_headers.cc | 87 | ||||
-rw-r--r-- | net/http/http_response_headers.h | 30 | ||||
-rw-r--r-- | net/http/http_response_headers_unittest.cc | 66 | ||||
-rw-r--r-- | net/http/http_util.cc | 28 | ||||
-rw-r--r-- | net/http/http_util.h | 8 | ||||
-rw-r--r-- | net/http/http_version.h | 59 | ||||
-rw-r--r-- | webkit/glue/resource_handle_win.cc | 10 |
10 files changed, 400 insertions, 91 deletions
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc index 22fff4e..58af549 100644 --- a/net/http/http_network_transaction.cc +++ b/net/http/http_network_transaction.cc @@ -19,9 +19,6 @@ // - authentication // - proxies (need to call ReconsiderProxyAfterError and handle SSL tunnel) // - ssl -// - http/0.9 -// - header line continuations (i.e., lines that start with LWS) -// - tolerate some junk (up to 4 bytes) in front of the HTTP/1.x status line namespace net { @@ -46,6 +43,7 @@ HttpNetworkTransaction::HttpNetworkTransaction(HttpNetworkSession* session, header_buf_capacity_(0), header_buf_len_(0), header_buf_body_offset_(-1), + header_buf_http_offset_(-1), content_length_(-1), // -1 means unspecified. content_read_(0), read_buf_(NULL), @@ -554,26 +552,48 @@ int HttpNetworkTransaction::DoReadHeadersComplete(int result) { if (header_buf_len_ == 0) response_.response_time = Time::Now(); + // The socket was closed before we found end-of-headers. if (result == 0) { if (establishing_tunnel_) { // The socket was closed before the tunnel could be established. return ERR_TUNNEL_CONNECTION_FAILED; } - // The socket was closed before we found end-of-headers. Assume that EOF - // is end-of-headers. - header_buf_body_offset_ = header_buf_len_; + if (has_found_status_line_start()) { + // Assume EOF is end-of-headers. + header_buf_body_offset_ = header_buf_len_; + } else { + // No status line was matched yet, assume HTTP/0.9 + // (this will also match a HTTP/1.x that got closed early). + header_buf_body_offset_ = 0; + } } else { header_buf_len_ += result; DCHECK(header_buf_len_ <= header_buf_capacity_); - // TODO(darin): Check for a HTTP/0.9 response. + // Look for the start of the status line, if it hasn't been found yet. + if (!has_found_status_line_start()) { + header_buf_http_offset_ = HttpUtil::LocateStartOfStatusLine( + header_buf_.get(), header_buf_len_); + } - int eoh = HttpUtil::LocateEndOfHeaders(header_buf_.get(), header_buf_len_); - if (eoh == -1) { - next_state_ = STATE_READ_HEADERS; // Read more. + if (has_found_status_line_start()) { + int eoh = HttpUtil::LocateEndOfHeaders( + header_buf_.get(), header_buf_len_, header_buf_http_offset_); + if (eoh == -1) { + // Haven't found the end of headers yet, keep reading. + next_state_ = STATE_READ_HEADERS; + return OK; + } + header_buf_body_offset_ = eoh; + } else if (header_buf_len_ < 8) { + // Not enough data to decide whether this is HTTP/0.9 yet. + // 8 bytes = (4 bytes of junk) + "http".length() + next_state_ = STATE_READ_HEADERS; return OK; + } else { + // Enough data was read -- there is no status line. + header_buf_body_offset_ = 0; } - header_buf_body_offset_ = eoh; } // And, we are done with the Start or the SSL tunnel CONNECT sequence. @@ -655,10 +675,29 @@ int HttpNetworkTransaction::DoReadBodyComplete(int result) { } int HttpNetworkTransaction::DidReadResponseHeaders() { - // TODO(wtc): Require the "HTTP/1.x" status line. The HttpResponseHeaders - // constructor makes up an "HTTP/1.0 200 OK" status line if it is missing. - scoped_refptr<HttpResponseHeaders> headers = new HttpResponseHeaders( - HttpUtil::AssembleRawHeaders(header_buf_.get(), header_buf_body_offset_)); + scoped_refptr<HttpResponseHeaders> headers; + if (has_found_status_line_start()) { + headers = new HttpResponseHeaders( + HttpUtil::AssembleRawHeaders( + header_buf_.get(), header_buf_body_offset_)); + } else { + // Fabricate a status line to to preserve the HTTP/0.9 version. + // (otherwise HttpResponseHeaders will default it to HTTP/1.0). + headers = new HttpResponseHeaders(std::string("HTTP/0.9 200 OK")); + } + + if (establishing_tunnel_ && + headers->GetParsedHttpVersion() < HttpVersion(1, 0)) { + // Require the "HTTP/1.x" status line. + return ERR_TUNNEL_CONNECTION_FAILED; + } + + // HTTP/0.9 doesn't support the PUT method, so lack of response headers + // indicates a buggy server. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=193921 + if (headers->GetHttpVersion() == HttpVersion(0, 9) && + request_->method == "PUT") + return ERR_ABORTED; // Check for an intermediate 100 Continue response. An origin server is // allowed to send this response even if we didn't ask for it, so we just @@ -709,8 +748,7 @@ int HttpNetworkTransaction::DidReadResponseHeaders() { if (content_length_ == -1) { // Ignore spurious chunked responses from HTTP/1.0 servers and proxies. // Otherwise "Transfer-Encoding: chunked" trumps "Content-Length: N" - const std::string& status_line = response_.headers->GetStatusLine(); - if (!StartsWithASCII(status_line, "HTTP/1.0 ", true) && + if (response_.headers->GetHttpVersion() != HttpVersion(1, 0) && response_.headers->HasHeaderValue("Transfer-Encoding", "chunked")) { chunked_decoder_.reset(new HttpChunkedDecoder()); } else { diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h index 0333222..07e2da6 100644 --- a/net/http/http_network_transaction.h +++ b/net/http/http_network_transaction.h @@ -82,6 +82,13 @@ class HttpNetworkTransaction : public HttpTransaction { // is returned. int HandleIOError(int error); + // Return true if based on the bytes read so far, the start of the + // status line is known. This is used to distingish between HTTP/0.9 + // responses (which have no status line). + bool has_found_status_line_start() const { + return header_buf_http_offset_ != -1; + } + CompletionCallbackImpl<HttpNetworkTransaction> io_callback_; CompletionCallback* user_callback_; @@ -124,6 +131,8 @@ class HttpNetworkTransaction : public HttpTransaction { int header_buf_len_; int header_buf_body_offset_; enum { kHeaderBufInitialSize = 4096 }; + // The position where status line starts; -1 if not found yet. + int header_buf_http_offset_; // Indicates the content length remaining to read. If this value is less // than zero (and chunked_decoder_ is null), then we read until the server diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index 1f9c0e1..bba719b 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -170,17 +170,14 @@ class HttpNetworkTransactionTest : public testing::Test { } }; -} // namespace +struct SimpleGetHelperResult { + std::string status_line; + std::string response_data; +}; -//----------------------------------------------------------------------------- +SimpleGetHelperResult SimpleGetHelper(MockRead data_reads[]) { + SimpleGetHelperResult out; -TEST_F(HttpNetworkTransactionTest, Basic) { - net::HttpTransaction* trans = new net::HttpNetworkTransaction( - CreateSession(), &mock_socket_factory); - trans->Destroy(); -} - -TEST_F(HttpNetworkTransactionTest, SimpleGET) { net::HttpTransaction* trans = new net::HttpNetworkTransaction( CreateSession(), &mock_socket_factory); @@ -189,11 +186,6 @@ TEST_F(HttpNetworkTransactionTest, SimpleGET) { request.url = GURL("http://www.google.com/"); request.load_flags = 0; - MockRead data_reads[] = { - { true, 0, "HTTP/1.0 200 OK\r\n\r\n", -1 }, - { true, 0, "hello world", -1 }, - { false, net::OK, NULL, 0 }, - }; MockSocket data; data.connect.async = true; data.connect.result = net::OK; @@ -213,17 +205,110 @@ TEST_F(HttpNetworkTransactionTest, SimpleGET) { EXPECT_TRUE(response != NULL); EXPECT_TRUE(response->headers != NULL); - EXPECT_TRUE(response->headers->GetStatusLine() == "HTTP/1.0 200 OK"); + out.status_line = response->headers->GetStatusLine(); - std::string response_data; - rv = ReadTransaction(trans, &response_data); + rv = ReadTransaction(trans, &out.response_data); EXPECT_EQ(net::OK, rv); - EXPECT_TRUE(response_data == "hello world"); trans->Destroy(); // Empty the current queue. MessageLoop::current()->RunAllPending(); + + return out; +} + +} // namespace + +//----------------------------------------------------------------------------- + +TEST_F(HttpNetworkTransactionTest, Basic) { + net::HttpTransaction* trans = new net::HttpNetworkTransaction( + CreateSession(), &mock_socket_factory); + trans->Destroy(); +} + +TEST_F(HttpNetworkTransactionTest, SimpleGET) { + MockRead data_reads[] = { + { true, 0, "HTTP/1.0 200 OK\r\n\r\n", -1 }, + { true, 0, "hello world", -1 }, + { false, net::OK, NULL, 0 }, + }; + + SimpleGetHelperResult out = SimpleGetHelper(data_reads); + EXPECT_EQ("HTTP/1.0 200 OK", out.status_line); + EXPECT_EQ("hello world", out.response_data); +} + +// Response with no status line. +TEST_F(HttpNetworkTransactionTest, SimpleGETNoHeaders) { + MockRead data_reads[] = { + { true, 0, "hello world", -1 }, + { false, net::OK, NULL, 0 }, + }; + + SimpleGetHelperResult out = SimpleGetHelper(data_reads); + EXPECT_EQ("HTTP/0.9 200 OK", out.status_line); + EXPECT_EQ("hello world", out.response_data); +} + +// Allow up to 4 bytes of junk to precede status line. +TEST_F(HttpNetworkTransactionTest, StatusLineJunk2Bytes) { + MockRead data_reads[] = { + { true, 0, "xxxHTTP/1.0 404 Not Found\nServer: blah\n\nDATA", -1 }, + { false, net::OK, NULL, 0 }, + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads); + EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line); + EXPECT_EQ("DATA", out.response_data); +} + +// Allow up to 4 bytes of junk to precede status line. +TEST_F(HttpNetworkTransactionTest, StatusLineJunk4Bytes) { + MockRead data_reads[] = { + { true, 0, "\n\nQJHTTP/1.0 404 Not Found\nServer: blah\n\nDATA", -1 }, + { false, net::OK, NULL, 0 }, + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads); + EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line); + EXPECT_EQ("DATA", out.response_data); +} + +// Beyond 4 bytes of slop and it should fail to find a status line. +TEST_F(HttpNetworkTransactionTest, StatusLineJunk5Bytes) { + MockRead data_reads[] = { + { true, 0, "xxxxxHTTP/1.1 404 Not Found\nServer: blah", -1 }, + { false, net::OK, NULL, 0 }, + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads); + EXPECT_TRUE(out.status_line == "HTTP/0.9 200 OK"); + EXPECT_TRUE(out.response_data == "xxxxxHTTP/1.1 404 Not Found\nServer: blah"); +} + +// Same as StatusLineJunk4Bytes, except the read chunks are smaller. +TEST_F(HttpNetworkTransactionTest, StatusLineJunk4Bytes_Slow) { + MockRead data_reads[] = { + { true, 0, "\n", -1 }, + { true, 0, "\n", -1 }, + { true, 0, "Q", -1 }, + { true, 0, "J", -1 }, + { true, 0, "HTTP/1.0 404 Not Found\nServer: blah\n\nDATA", -1 }, + { false, net::OK, NULL, 0 }, + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads); + EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line); + EXPECT_EQ("DATA", out.response_data); +} + +// Close the connection before enough bytes to have a status line. +TEST_F(HttpNetworkTransactionTest, StatusLinePartial) { + MockRead data_reads[] = { + { true, 0, "HTT", -1 }, + { false, net::OK, NULL, 0 }, + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads); + EXPECT_EQ("HTTP/0.9 200 OK", out.status_line); + EXPECT_EQ("HTT", out.response_data); } TEST_F(HttpNetworkTransactionTest, ReuseConnection) { @@ -394,4 +479,3 @@ TEST_F(HttpNetworkTransactionTest, KeepAliveConnectionReset) { MessageLoop::current()->RunAllPending(); } } - diff --git a/net/http/http_response_headers.cc b/net/http/http_response_headers.cc index c06224c..ba96476 100644 --- a/net/http/http_response_headers.cc +++ b/net/http/http_response_headers.cc @@ -196,7 +196,11 @@ void HttpResponseHeaders::Parse(const string& raw_input) { // ParseStatusLine adds a normalized status line to raw_headers_ string::const_iterator line_begin = raw_input.begin(); string::const_iterator line_end = find(line_begin, raw_input.end(), '\0'); - ParseStatusLine(line_begin, line_end); + // has_headers = true, if there is any data following the status line. + // Used by ParseStatusLine() to decide if a HTTP/0.9 is really a HTTP/1.0. + bool has_headers = line_end != raw_input.end() && + (line_end + 1) != raw_input.end() && *(line_end + 1) != '\0'; + ParseStatusLine(line_begin, line_end, has_headers); if (line_end == raw_input.end()) { raw_headers_.push_back('\0'); @@ -311,6 +315,19 @@ string HttpResponseHeaders::GetStatusLine() const { return string(raw_headers_.c_str()); } +std::string HttpResponseHeaders::GetStatusText() const { + // TODO(eroman): this needs a unit test. + + // GetStatusLine() is already normalized, so it has the format: + // <http_version> SP <response_code> SP <status_text> + std::string status_text = GetStatusLine(); + std::string::const_iterator begin = status_text.begin(); + std::string::const_iterator end = status_text.end(); + for (int i = 0; i < 2; ++i) + begin = find(begin, end, ' ') + 1; + return std::string(begin, end); +} + bool HttpResponseHeaders::EnumerateHeaderLines(void** iter, string* name, string* value) const { @@ -375,62 +392,74 @@ bool HttpResponseHeaders::HasHeaderValue(const std::string& name, // Note: this implementation implicitly assumes that line_end points at a valid // sentinel character (such as '\0'). -void HttpResponseHeaders::ParseVersion(string::const_iterator line_begin, - string::const_iterator line_end) { +// static +HttpVersion HttpResponseHeaders::ParseVersion( + string::const_iterator line_begin, + string::const_iterator line_end) { string::const_iterator p = line_begin; // RFC2616 sec 3.1: HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT - // (1*DIGIT apparently means one or more digits, but we only handle 1). + // TODO: (1*DIGIT apparently means one or more digits, but we only handle 1). + // TODO: handle leading zeros, which is allowed by the rfc1616 sec 3.1. if ((line_end - p < 4) || !LowerCaseEqualsASCII(p, p + 4, "http")) { - DLOG(INFO) << "missing status line; assuming HTTP/0.9"; - // Morph this into HTTP/1.0 since HTTP/0.9 has no status line. - raw_headers_ = "HTTP/1.0"; - return; + DLOG(INFO) << "missing status line"; + return HttpVersion(); } p += 4; if (p >= line_end || *p != '/') { - DLOG(INFO) << "missing version; assuming HTTP/1.0"; - raw_headers_ = "HTTP/1.0"; - return; + DLOG(INFO) << "missing version"; + return HttpVersion(); } string::const_iterator dot = find(p, line_end, '.'); if (dot == line_end) { - DLOG(INFO) << "malformed version; assuming HTTP/1.0"; - raw_headers_ = "HTTP/1.0"; - return; + DLOG(INFO) << "malformed version"; + return HttpVersion(); } ++p; // from / to first digit. ++dot; // from . to second digit. if (!(*p >= '0' && *p <= '9' && *dot >= '0' && *dot <= '9')) { - DLOG(INFO) << "malformed version number; assuming HTTP/1.0"; - raw_headers_ = "HTTP/1.0"; - return; + DLOG(INFO) << "malformed version number"; + return HttpVersion(); } - int major = *p - '0'; - int minor = *dot - '0'; + uint16 major = *p - '0'; + uint16 minor = *dot - '0'; - if ((major > 1) || ((major == 1) && (minor >= 1))) { - // at least HTTP/1.1 - raw_headers_ = "HTTP/1.1"; - } else { - // treat anything else as version 1.0 - raw_headers_ = "HTTP/1.0"; - } + return HttpVersion(major, minor); } // Note: this implementation implicitly assumes that line_end points at a valid // sentinel character (such as '\0'). void HttpResponseHeaders::ParseStatusLine(string::const_iterator line_begin, - string::const_iterator line_end) { - ParseVersion(line_begin, line_end); + string::const_iterator line_end, + bool has_headers) { + // Extract the version number + parsed_http_version_ = ParseVersion(line_begin, line_end); + + // Clamp the version number to one of: {0.9, 1.0, 1.1} + if (parsed_http_version_ == HttpVersion(0, 9) && !has_headers) { + http_version_ = HttpVersion(0, 9); + raw_headers_ = "HTTP/0.9"; + } else if (parsed_http_version_ >= HttpVersion(1, 1)) { + http_version_ = HttpVersion(1, 1); + raw_headers_ = "HTTP/1.1"; + } else { + // Treat everything else like HTTP 1.0 + http_version_ = HttpVersion(1, 0); + raw_headers_ = "HTTP/1.0"; + } + if (parsed_http_version_ != http_version_) { + DLOG(INFO) << "assuming HTTP/" << http_version_.major() << "." + << http_version_.minor(); + } + // TODO(eroman): this doesn't make sense if ParseVersion failed. string::const_iterator p = find(line_begin, line_end, ' '); if (p == line_end) { @@ -470,6 +499,8 @@ void HttpResponseHeaders::ParseStatusLine(string::const_iterator line_begin, if (p == line_end) { DLOG(INFO) << "missing response status text; assuming OK"; + // Not super critical what we put here. Just use "OK" + // even if it isn't descriptive of response_code_. raw_headers_.append("OK"); } else { raw_headers_.append(p, line_end); diff --git a/net/http/http_response_headers.h b/net/http/http_response_headers.h index 1c9f518..feba262 100644 --- a/net/http/http_response_headers.h +++ b/net/http/http_response_headers.h @@ -11,6 +11,7 @@ #include "base/basictypes.h" #include "base/hash_tables.h" #include "base/ref_counted.h" +#include "net/http/http_version.h" class Pickle; class Time; @@ -89,6 +90,19 @@ class HttpResponseHeaders : // "HTTP/0.9 200 OK". std::string GetStatusLine() const; + // Get the HTTP version of the normalized status line. + HttpVersion GetHttpVersion() const { + return http_version_; + } + + // Get the HTTP version determined while parsing; or (0,0) if parsing failed + HttpVersion GetParsedHttpVersion() const { + return parsed_http_version_; + } + + // Get the HTTP status text of the normalized status line. + std::string GetStatusText() const; + // Enumerate the "lines" of the response headers. This skips over the status // line. Use GetStatusLine if you are interested in that. Note that this // method returns the un-coalesced response header lines, so if a response @@ -192,10 +206,9 @@ class HttpResponseHeaders : // Tries to extract the "HTTP/X.Y" from a status line formatted like: // HTTP/1.1 200 OK // with line_begin and end pointing at the begin and end of this line. If the - // status line is malformed, we'll guess a version number. - // Output will be a normalized version of this, with a trailing \n. - void ParseVersion(std::string::const_iterator line_begin, - std::string::const_iterator line_end); + // status line is malformed, returns HttpVersion(0,0). + static HttpVersion ParseVersion(std::string::const_iterator line_begin, + std::string::const_iterator line_end); // Tries to extract the status line from a header block, given the first // line of said header block. If the status line is malformed, we'll construct @@ -204,7 +217,8 @@ class HttpResponseHeaders : // with line_begin and end pointing at the begin and end of this line. // Output will be a normalized version of this, with a trailing \n. void ParseStatusLine(std::string::const_iterator line_begin, - std::string::const_iterator line_end); + std::string::const_iterator line_end, + bool has_headers); // Find the header in our list (case-insensitive) starting with parsed_ at // index |from|. Returns string::npos if not found. @@ -258,6 +272,12 @@ class HttpResponseHeaders : // This is the parsed HTTP response code. int response_code_; + // The normalized http version (consistent with what GetStatusLine() returns). + HttpVersion http_version_; + + // The parsed http version number (not normalized). + HttpVersion parsed_http_version_; + DISALLOW_COPY_AND_ASSIGN(HttpResponseHeaders); }; diff --git a/net/http/http_response_headers_unittest.cc b/net/http/http_response_headers_unittest.cc index e403a86..f02cd7f 100644 --- a/net/http/http_response_headers_unittest.cc +++ b/net/http/http_response_headers_unittest.cc @@ -12,6 +12,7 @@ using namespace std; using net::HttpResponseHeaders; +using net::HttpVersion; namespace { @@ -19,6 +20,8 @@ struct TestData { const char* raw_headers; const char* expected_headers; int expected_response_code; + HttpVersion expected_parsed_version; + HttpVersion expected_version; }; struct ContentTypeTestData { @@ -60,6 +63,9 @@ void TestCommon(const TestData& test) { EXPECT_EQ(expected_headers, headers); EXPECT_EQ(test.expected_response_code, parsed->response_code()); + + EXPECT_TRUE(test.expected_parsed_version == parsed->GetParsedHttpVersion()); + EXPECT_TRUE(test.expected_version == parsed->GetHttpVersion()); } } // end namespace @@ -76,7 +82,9 @@ TEST(HttpResponseHeadersTest, NormalizeHeadersWhitespace) { "Content-TYPE: text/html; charset=utf-8\n" "Set-Cookie: a, b\n", - 202 + 202, + HttpVersion(1,1), + HttpVersion(1,1) }; TestCommon(test); } @@ -94,7 +102,9 @@ TEST(HttpResponseHeadersTest, NormalizeHeadersLeadingWhitespace) { "HTTP/1.1 202 Accepted\n" "Set-Cookie: a, b\n", - 202 + 202, + HttpVersion(1,1), + HttpVersion(1,1) }; TestCommon(test); } @@ -114,12 +124,15 @@ TEST(HttpResponseHeadersTest, BlankHeaders) { "Header3: \n" "Header5: \n", - 200 + 200, + HttpVersion(1,1), + HttpVersion(1,1) }; TestCommon(test); } TEST(HttpResponseHeadersTest, NormalizeHeadersVersion) { + // Don't believe the http/0.9 version if there are headers! TestData test = { "hTtP/0.9 201\n" "Content-TYPE: text/html; charset=utf-8\n", @@ -127,7 +140,24 @@ TEST(HttpResponseHeadersTest, NormalizeHeadersVersion) { "HTTP/1.0 201 OK\n" "Content-TYPE: text/html; charset=utf-8\n", - 201 + 201, + HttpVersion(0,9), + HttpVersion(1,0) + }; + TestCommon(test); +} + +TEST(HttpResponseHeadersTest, PreserveHttp09) { + // Accept the HTTP/0.9 version number if there are no headers. + // This is how HTTP/0.9 responses get constructed from HttpNetworkTransaction. + TestData test = { + "hTtP/0.9 200 OK\n", + + "HTTP/0.9 200 OK\n", + + 200, + HttpVersion(0,9), + HttpVersion(0,9) }; TestCommon(test); } @@ -140,7 +170,9 @@ TEST(HttpResponseHeadersTest, NormalizeHeadersMissingOK) { "HTTP/1.1 201 OK\n" "Content-TYPE: text/html; charset=utf-8\n", - 201 + 201, + HttpVersion(1,1), + HttpVersion(1,1) }; TestCommon(test); } @@ -153,7 +185,9 @@ TEST(HttpResponseHeadersTest, NormalizeHeadersBadStatus) { "HTTP/1.0 200 OK\n" "Content-TYPE: text/html; charset=utf-8\n", - 200 + 200, + HttpVersion(0,0), // Parse error + HttpVersion(1,0) }; TestCommon(test); } @@ -164,7 +198,9 @@ TEST(HttpResponseHeadersTest, NormalizeHeadersEmpty) { "HTTP/1.0 200 OK\n", - 200 + 200, + HttpVersion(0,0), // Parse Error + HttpVersion(1,0) }; TestCommon(test); } @@ -181,7 +217,9 @@ TEST(HttpResponseHeadersTest, NormalizeHeadersStartWithColon) { "foo: bar\n" "baz: blat\n", - 202 + 202, + HttpVersion(1,1), + HttpVersion(1,1) }; TestCommon(test); } @@ -200,7 +238,9 @@ TEST(HttpResponseHeadersTest, NormalizeHeadersStartWithColonAtEOL) { "baz: blat\n" "zip: \n", - 202 + 202, + HttpVersion(1,1), + HttpVersion(1,1) }; TestCommon(test); } @@ -211,7 +251,9 @@ TEST(HttpResponseHeadersTest, NormalizeHeadersOfWhitepace) { "HTTP/1.0 200 OK\n", - 200 + 200, + HttpVersion(0,0), // Parse error + HttpVersion(1,0) }; TestCommon(test); } @@ -225,7 +267,9 @@ TEST(HttpResponseHeadersTest, RepeatedSetCookie) { "HTTP/1.1 200 OK\n" "Set-Cookie: x=1, y=2\n", - 200 + 200, + HttpVersion(1,1), + HttpVersion(1,1) }; TestCommon(test); } diff --git a/net/http/http_util.cc b/net/http/http_util.cc index 0cf8c23..e67ea32 100644 --- a/net/http/http_util.cc +++ b/net/http/http_util.cc @@ -234,10 +234,28 @@ void HttpUtil::TrimLWS(string::const_iterator* begin, --(*end); } -int HttpUtil::LocateEndOfHeaders(const char* buf, int buf_len) { +// Find the "http" substring in a status line. This allows for +// some slop at the start. If the "http" string could not be found +// then returns -1. +// static +int HttpUtil::LocateStartOfStatusLine(const char* buf, int buf_len) { + const int slop = 4; + const int http_len = 4; + + if (buf_len >= http_len) { + int i_max = std::min(buf_len - http_len, slop); + for (int i = 0; i <= i_max; ++i) { + if (LowerCaseEqualsASCII(buf + i, buf + i + http_len, "http")) + return i; + } + } + return -1; // Not found +} + +int HttpUtil::LocateEndOfHeaders(const char* buf, int buf_len, int i) { bool was_lf = false; char last_c = '\0'; - for (int i = 0; i < buf_len; ++i) { + for (; i < buf_len; ++i) { char c = buf[i]; if (c == '\n') { if (was_lf) @@ -300,6 +318,12 @@ std::string HttpUtil::AssembleRawHeaders(const char* input_begin, const char* input_end = input_begin + input_len; + // Skip any leading slop, since the consumers of this output + // (HttpResponseHeaders) don't deal with it. + int status_begin_offset = LocateStartOfStatusLine(input_begin, input_len); + if (status_begin_offset != -1) + input_begin += status_begin_offset; + // Copy the status line. const char* status_line_end = FindStatusLineEnd(input_begin, input_end); raw_headers.append(input_begin, status_line_end); diff --git a/net/http/http_util.h b/net/http/http_util.h index 5bfb71a..77ae117 100644 --- a/net/http/http_util.h +++ b/net/http/http_util.h @@ -55,12 +55,18 @@ class HttpUtil { static void TrimLWS(std::string::const_iterator* begin, std::string::const_iterator* end); + // Returns the start of the status line, or -1 if no status line was found. + // This allows for 4 bytes of junk to precede the status line (which is what + // mozilla does too). + static int HttpUtil::LocateStartOfStatusLine(const char* buf, int buf_len); + // Returns index beyond the end-of-headers marker or -1 if not found. RFC // 2616 defines the end-of-headers marker as a double CRLF; however, some // servers only send back LFs (e.g., Unix-based CGI scripts written using the // ASIS Apache module). This function therefore accepts the pattern LF[CR]LF // as end-of-headers (just like Mozilla). - static int LocateEndOfHeaders(const char* buf, int buf_len); + // The parameter |i| is the offset within |buf| to begin searching from. + static int LocateEndOfHeaders(const char* buf, int buf_len, int i = 0); // Assemble "raw headers" in the format required by HttpResponseHeaders. // This involves normalizing line terminators, converting [CR]LF to \0 and diff --git a/net/http/http_version.h b/net/http/http_version.h new file mode 100644 index 0000000..9f4cfba --- /dev/null +++ b/net/http/http_version.h @@ -0,0 +1,59 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_HTTP_VERSION_H_ +#define NET_HTTP_VERSION_H_ + +#include "base/basictypes.h" + +namespace net { + +// Wrapper for an HTTP (major,minor) version pair. +class HttpVersion { + private: + uint32 value; // Packed as <major>:<minor> + + public: + // Default constructor (major=0, minor=0). + HttpVersion() : value(0) { } + + // Build from unsigned major/minor pair. + HttpVersion(uint16 major, uint16 minor) : value(major << 16 | minor) { } + + // Major version number. + uint16 major() const { + return value >> 16; + } + + // Minor version number. + uint16 minor() const { + return value & 0xffff; + } + + // Overloaded operators: + + bool operator==(const HttpVersion& v) const { + return value == v.value; + } + bool operator!=(const HttpVersion& v) const { + return value != v.value; + } + bool operator>(const HttpVersion& v) const { + return value > v.value; + } + bool operator>=(const HttpVersion& v) const { + return value >= v.value; + } + bool operator<(const HttpVersion& v) const { + return value < v.value; + } + bool operator<=(const HttpVersion& v) const { + return value <= v.value; + } +}; + +} // namespace net + +#endif // NET_HTTP_VERSION_H_ + diff --git a/webkit/glue/resource_handle_win.cc b/webkit/glue/resource_handle_win.cc index 2771b21..09a22c2 100644 --- a/webkit/glue/resource_handle_win.cc +++ b/webkit/glue/resource_handle_win.cc @@ -90,14 +90,8 @@ static void ExtractInfoFromHeaders(const HttpResponseHeaders* headers, long long* expected_content_length) { *status_code = headers->response_code(); - // Set the status text (the returned status line is normalized). - const std::string& status = headers->GetStatusLine(); - StringTokenizer status_tokenizer(status, " "); - if (status_tokenizer.GetNext() && // identifies "HTTP/1.1" - status_tokenizer.GetNext() && // identifies "200" - status_tokenizer.GetNext()) // identifies first word of status text - *status_text = webkit_glue::StdStringToString( - std::string(status_tokenizer.token_begin(), status.end())); + // Set the status text + *status_text = webkit_glue::StdStringToString(headers->GetStatusText()); // Set the content length. std::string length_val; |