summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-13 00:45:27 +0000
committerericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-13 00:45:27 +0000
commit231d5a36e476d013a91ca742bb8a0a2973cfee54 (patch)
treedc5c60f8fc054503f4971b770196ed89fbfc18c1
parentf4f2df8024d7adf583a61b742a32eaa17522f154 (diff)
downloadchromium_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.cc72
-rw-r--r--net/http/http_network_transaction.h9
-rw-r--r--net/http/http_network_transaction_unittest.cc122
-rw-r--r--net/http/http_response_headers.cc87
-rw-r--r--net/http/http_response_headers.h30
-rw-r--r--net/http/http_response_headers_unittest.cc66
-rw-r--r--net/http/http_util.cc28
-rw-r--r--net/http/http_util.h8
-rw-r--r--net/http/http_version.h59
-rw-r--r--webkit/glue/resource_handle_win.cc10
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;