diff options
author | mbelshe@chromium.org <mbelshe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-08 18:05:57 +0000 |
---|---|---|
committer | mbelshe@chromium.org <mbelshe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-08 18:05:57 +0000 |
commit | a7a265efd24072f9dc7b5f737ec84d5ae0553cd6 (patch) | |
tree | 62180201efd7568a6db59bc3b8c06caa260625d7 | |
parent | c1784804a5634728d147baea37257a06ab5cc031 (diff) | |
download | chromium_src-a7a265efd24072f9dc7b5f737ec84d5ae0553cd6.zip chromium_src-a7a265efd24072f9dc7b5f737ec84d5ae0553cd6.tar.gz chromium_src-a7a265efd24072f9dc7b5f737ec84d5ae0553cd6.tar.bz2 |
Add origin checking for server pushed resources.
BUG=64108
TEST=PushedStream, ServerPushCrossOriginCorrectness
Review URL: http://codereview.chromium.org/5516012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@68605 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | net/spdy/spdy_network_transaction_unittest.cc | 119 | ||||
-rw-r--r-- | net/spdy/spdy_session.cc | 28 | ||||
-rw-r--r-- | net/spdy/spdy_stream.cc | 37 | ||||
-rw-r--r-- | net/spdy/spdy_stream.h | 8 | ||||
-rw-r--r-- | net/spdy/spdy_stream_unittest.cc | 54 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.cc | 8 |
6 files changed, 232 insertions, 22 deletions
diff --git a/net/spdy/spdy_network_transaction_unittest.cc b/net/spdy/spdy_network_transaction_unittest.cc index 730d53f..fc0cbe8 100644 --- a/net/spdy/spdy_network_transaction_unittest.cc +++ b/net/spdy/spdy_network_transaction_unittest.cc @@ -398,7 +398,7 @@ class SpdyNetworkTransactionTest void RunServerPushTest(OrderedSocketData* data, HttpResponseInfo* response, - HttpResponseInfo* response2, + HttpResponseInfo* push_response, std::string& expected) { NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); @@ -443,7 +443,7 @@ class SpdyNetworkTransactionTest // Verify the SYN_REPLY. // Copy the response info, because trans goes away. *response = *trans->GetResponseInfo(); - *response2 = *trans2->GetResponseInfo(); + *push_response = *trans2->GetResponseInfo(); VerifyStreamsClosed(helper); } @@ -2958,7 +2958,6 @@ TEST_P(SpdyNetworkTransactionTest, SynReplyHeaders) { "cookie: val2\n" "hello: bye\n" "status: 200\n" - "url: /index.php\n" "version: HTTP/1.1\n" }, // This is the minimalist set of headers. @@ -2966,7 +2965,6 @@ TEST_P(SpdyNetworkTransactionTest, SynReplyHeaders) { { NULL }, "hello: bye\n" "status: 200\n" - "url: /index.php\n" "version: HTTP/1.1\n" }, // Headers with a comma separated list. @@ -2977,7 +2975,6 @@ TEST_P(SpdyNetworkTransactionTest, SynReplyHeaders) { "cookie: val1,val2\n" "hello: bye\n" "status: 200\n" - "url: /index.php\n" "version: HTTP/1.1\n" } }; @@ -5303,4 +5300,116 @@ TEST_P(SpdyNetworkTransactionTest, SynReplyWithDuplicateLateHeaders) { EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); } +TEST_P(SpdyNetworkTransactionTest, ServerPushCrossOriginCorrectness) { + // In this test we want to verify that we can't accidentally push content + // which can't be pushed by this content server. + // This test assumes that: + // - if we're requesting http://www.foo.com/barbaz + // - the browser has made a connection to "www.foo.com". + + // A list of the URL to fetch, followed by the URL being pushed. + static const char* const kTestCases[] = { + "http://www.google.com/foo.html", + "http://www.google.com:81/foo.js", // Bad port + + "http://www.google.com/foo.html", + "https://www.google.com/foo.js", // Bad protocol + + "http://www.google.com/foo.html", + "ftp://www.google.com/foo.js", // Invalid Protocol + + "http://www.google.com/foo.html", + "http://blat.www.google.com/foo.js", // Cross subdomain + + "http://www.google.com/foo.html", + "http://www.foo.com/foo.js", // Cross domain + }; + + + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + + for (int index = 0; index < arraysize(kTestCases); index += 2) { + const char* url_to_fetch = kTestCases[index]; + const char* url_to_push = kTestCases[index + 1]; + + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(url_to_fetch, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> push_rst( + ConstructSpdyRstStream(2, spdy::REFUSED_STREAM)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*push_rst, 4), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + url_to_push)); + scoped_ptr<spdy::SpdyFrame> rst( + ConstructSpdyRstStream(2, spdy::CANCEL)); + + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 5, false), + MockRead(true, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 6), + MockRead(true, ERR_IO_PENDING, 7), // Force a pause + }; + + HttpResponseInfo response; + scoped_refptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL(url_to_fetch); + request.load_flags = 0; + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + + int rv = trans->Start(&request, &callback, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + // Read the response body. + std::string result; + ReadResult(trans, data, &result); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + response = *trans->GetResponseInfo(); + + VerifyStreamsClosed(helper); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + } +} + } // namespace net diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc index a788d13..bcdafe4 100644 --- a/net/spdy/spdy_session.cc +++ b/net/spdy/spdy_session.cc @@ -1026,19 +1026,19 @@ void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame, // Server-initiated streams should have even sequence numbers. if ((stream_id & 0x1) != 0) { - LOG(ERROR) << "Received invalid OnSyn stream id " << stream_id; + LOG(WARNING) << "Received invalid OnSyn stream id " << stream_id; return; } if (IsStreamActive(stream_id)) { - LOG(ERROR) << "Received OnSyn for active stream " << stream_id; + LOG(WARNING) << "Received OnSyn for active stream " << stream_id; return; } if (associated_stream_id == 0) { - LOG(ERROR) << "Received invalid OnSyn associated stream id " - << associated_stream_id - << " for stream " << stream_id; + LOG(WARNING) << "Received invalid OnSyn associated stream id " + << associated_stream_id + << " for stream " << stream_id; ResetStream(stream_id, spdy::INVALID_STREAM); return; } @@ -1047,10 +1047,9 @@ void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame, // TODO(mbelshe): DCHECK that this is a GET method? + // Verify that the response had a URL for us. const std::string& url = ContainsKey(*headers, "url") ? headers->find("url")->second : ""; - - // Verify that the response had a URL for us. if (url.empty()) { ResetStream(stream_id, spdy::PROTOCOL_ERROR); LOG(WARNING) << "Pushed stream did not contain a url."; @@ -1064,19 +1063,28 @@ void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame, return; } + // Verify we have a valid stream association. if (!IsStreamActive(associated_stream_id)) { - LOG(ERROR) << "Received OnSyn with inactive associated stream " + LOG(WARNING) << "Received OnSyn with inactive associated stream " << associated_stream_id; ResetStream(stream_id, spdy::INVALID_ASSOCIATED_STREAM); return; } - // TODO(erikchen): Actually do something with the associated id. + scoped_refptr<SpdyStream> associated_stream = + active_streams_[associated_stream_id]; + GURL associated_url(associated_stream->GetUrl()); + if (associated_url.GetOrigin() != gurl.GetOrigin()) { + LOG(WARNING) << "Rejected Cross Origin Push Stream " + << associated_stream_id; + ResetStream(stream_id, spdy::REFUSED_STREAM); + return; + } // There should not be an existing pushed stream with the same path. PushedStreamMap::iterator it = unclaimed_pushed_streams_.find(url); if (it != unclaimed_pushed_streams_.end()) { - LOG(ERROR) << "Received duplicate pushed stream with url: " << url; + LOG(WARNING) << "Received duplicate pushed stream with url: " << url; ResetStream(stream_id, spdy::PROTOCOL_ERROR); return; } diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc index 44d1243..8a5d4a4 100644 --- a/net/spdy/spdy_stream.cc +++ b/net/spdy/spdy_stream.cc @@ -397,6 +397,43 @@ bool SpdyStream::GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) { return session_->GetSSLCertRequestInfo(cert_request_info); } +bool SpdyStream::HasUrl() const { + if (pushed_) + return response_received(); + return request_.get() != NULL; +} + +GURL SpdyStream::GetUrl() const { + DCHECK(HasUrl()); + + if (pushed_) { + // assemble from the response + std::string url; + spdy::SpdyHeaderBlock::const_iterator it; + it = response_->find("url"); + if (it != (*response_).end()) + url = it->second; + return GURL(url); + } + + // assemble from the request + std::string scheme; + std::string host_port; + std::string path; + spdy::SpdyHeaderBlock::const_iterator it; + it = request_->find("scheme"); + if (it != (*request_).end()) + scheme = it->second; + it = request_->find("host"); + if (it != (*request_).end()) + host_port = it->second; + it = request_->find("path"); + if (it != (*request_).end()) + path = it->second; + std::string url = scheme + "://" + host_port + path; + return GURL(url); +} + int SpdyStream::DoLoop(int result) { do { State state = io_state_; diff --git a/net/spdy/spdy_stream.h b/net/spdy/spdy_stream.h index d3dd242..9a055c2 100644 --- a/net/spdy/spdy_stream.h +++ b/net/spdy/spdy_stream.h @@ -13,6 +13,7 @@ #include "base/linked_ptr.h" #include "base/ref_counted.h" #include "base/scoped_ptr.h" +#include "googleurl/src/gurl.h" #include "net/base/bandwidth_metrics.h" #include "net/base/io_buffer.h" #include "net/base/net_log.h" @@ -214,6 +215,13 @@ class SpdyStream : public base::RefCounted<SpdyStream> { int response_status() const { return response_status_; } + // Returns true if the URL for this stream is known. + bool HasUrl() const; + + // Get the URL associated with this stream. Only valid when has_url() is + // true. + GURL GetUrl() const; + private: enum State { STATE_NONE, diff --git a/net/spdy/spdy_stream_unittest.cc b/net/spdy/spdy_stream_unittest.cc index cdb116f..3bf0a23 100644 --- a/net/spdy/spdy_stream_unittest.cc +++ b/net/spdy/spdy_stream_unittest.cc @@ -150,8 +150,12 @@ TEST_F(SpdyStreamTest, SendDataAfterOpen) { static const char* const kGetHeaders[] = { "method", "GET", - "url", - "http://www.google.com/", + "scheme", + "http", + "host", + "www.google.com", + "path", + "/", "version", "HTTP/1.1", }; @@ -189,7 +193,8 @@ TEST_F(SpdyStreamTest, SendDataAfterOpen) { SpdySession::SetSSLMode(false); scoped_refptr<SpdySession> session(CreateSpdySession()); - GURL url("http://www.google.com/"); + const char* kStreamUrl = "http://www.google.com/"; + GURL url(kStreamUrl); HostPortPair host_port_pair("www.google.com", 80); scoped_refptr<TCPSocketParams> tcp_params( @@ -213,11 +218,17 @@ TEST_F(SpdyStreamTest, SendDataAfterOpen) { new TestSpdyStreamDelegate(stream.get(), buf.get(), &callback)); stream->SetDelegate(delegate.get()); + EXPECT_FALSE(stream->HasUrl()); + linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock); (*headers)["method"] = "GET"; - (*headers)["url"] = "http://www.google.com/"; + (*headers)["scheme"] = url.scheme(); + (*headers)["host"] = url.host(); + (*headers)["path"] = url.path(); (*headers)["version"] = "HTTP/1.1"; stream->set_spdy_headers(headers); + EXPECT_TRUE(stream->HasUrl()); + EXPECT_EQ(kStreamUrl, stream->GetUrl().spec()); EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true)); @@ -231,4 +242,39 @@ TEST_F(SpdyStreamTest, SendDataAfterOpen) { EXPECT_TRUE(delegate->closed()); } +TEST_F(SpdyStreamTest, PushedStream) { + const char kStreamUrl[] = "http://www.google.com/"; + + SpdySessionDependencies session_deps; + session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps); + SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool()); + scoped_refptr<SpdySession> spdy_session(CreateSpdySession()); + BoundNetLog net_log; + + // Conjure up a stream. + scoped_refptr<SpdyStream> stream = new SpdyStream(spdy_session, + 2, + true, + net_log); + EXPECT_FALSE(stream->response_received()); + EXPECT_FALSE(stream->HasUrl()); + + // Set a couple of headers. + spdy::SpdyHeaderBlock response; + response["url"] = kStreamUrl; + stream->OnResponseReceived(response); + + // Send some basic headers. + spdy::SpdyHeaderBlock headers; + response["status"] = "200"; + response["version"] = "OK"; + stream->OnHeaders(headers); + + stream->set_response_received(); + EXPECT_TRUE(stream->response_received()); + EXPECT_TRUE(stream->HasUrl()); + EXPECT_EQ(kStreamUrl, stream->GetUrl().spec()); +} + + } // namespace net diff --git a/net/spdy/spdy_test_util.cc b/net/spdy/spdy_test_util.cc index 5d6c9b0..3a7f771 100644 --- a/net/spdy/spdy_test_util.cc +++ b/net/spdy/spdy_test_util.cc @@ -334,7 +334,11 @@ spdy::SpdyFrame* ConstructSpdyGet(const char* const url, // This is so ugly. Why are we using char* in here again? std::string str_path = gurl.PathForRequest(); std::string str_scheme = gurl.scheme(); - std::string str_host = gurl.host(); // TODO(mbelshe): should have a port. + std::string str_host = gurl.host(); + if (gurl.has_port()) { + str_host += ":"; + str_host += gurl.port(); + } scoped_array<char> req(new char[str_path.size() + 1]); scoped_array<char> scheme(new char[str_scheme.size() + 1]); scoped_array<char> host(new char[str_host.size() + 1]); @@ -618,8 +622,6 @@ spdy::SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[], "bye", "status", "200", - "url", - "/index.php", "version", "HTTP/1.1" }; |