| Index: net/cert/cert_net_fetcher_unittest.cc
|
| diff --git a/net/cert/cert_net_fetcher_unittest.cc b/net/cert/cert_net_fetcher_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0e99afa5d566d5fb72874ab529685cf4fc625986
|
| --- /dev/null
|
| +++ b/net/cert/cert_net_fetcher_unittest.cc
|
| @@ -0,0 +1,476 @@
|
| +// Copyright 2015 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.
|
| +
|
| +#include "net/cert/cert_net_fetcher.h"
|
| +
|
| +#include <string>
|
| +
|
| +#include "base/compiler_specific.h"
|
| +#include "net/cert/mock_cert_verifier.h"
|
| +#include "net/dns/mock_host_resolver.h"
|
| +#include "net/http/http_server_properties_impl.h"
|
| +#include "net/test/spawned_test_server/spawned_test_server.h"
|
| +#include "net/url_request/url_request_job_factory_impl.h"
|
| +#include "net/url_request/url_request_test_util.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +#include "testing/platform_test.h"
|
| +
|
| +// TODO(eroman): Test that cookies aren't sent.
|
| +// TODO(eroman): Request de-duplication
|
| +// TODO(eroman): Cancel duplicated requests within a callback
|
| +// TODO(eroman): Start requests for the same job within a callback
|
| +// TODO(eroman): Delete the CertNetFetcher within callback
|
| +
|
| +using base::ASCIIToUTF16;
|
| +
|
| +namespace net {
|
| +
|
| +namespace {
|
| +
|
| +const base::FilePath::CharType kDocRoot[] =
|
| + FILE_PATH_LITERAL("net/data/cert_net_fetcher_unittest");
|
| +
|
| +// A non-mock URL request which can access http:// urls.
|
| +class RequestContext : public URLRequestContext {
|
| + public:
|
| + RequestContext() : storage_(this) {
|
| + ProxyConfig no_proxy;
|
| + storage_.set_host_resolver(scoped_ptr<HostResolver>(new MockHostResolver));
|
| + storage_.set_cert_verifier(new MockCertVerifier);
|
| + storage_.set_transport_security_state(new TransportSecurityState);
|
| + storage_.set_proxy_service(ProxyService::CreateFixed(no_proxy));
|
| + storage_.set_ssl_config_service(new SSLConfigServiceDefaults);
|
| + storage_.set_http_server_properties(
|
| + scoped_ptr<HttpServerProperties>(new HttpServerPropertiesImpl()));
|
| +
|
| + HttpNetworkSession::Params params;
|
| + params.host_resolver = host_resolver();
|
| + params.cert_verifier = cert_verifier();
|
| + params.transport_security_state = transport_security_state();
|
| + params.proxy_service = proxy_service();
|
| + params.ssl_config_service = ssl_config_service();
|
| + params.http_server_properties = http_server_properties();
|
| + scoped_refptr<HttpNetworkSession> network_session(
|
| + new HttpNetworkSession(params));
|
| + storage_.set_http_transaction_factory(new HttpCache(
|
| + network_session.get(), HttpCache::DefaultBackend::InMemory(0)));
|
| + URLRequestJobFactoryImpl* job_factory = new URLRequestJobFactoryImpl();
|
| + storage_.set_job_factory(job_factory);
|
| + }
|
| +
|
| + ~RequestContext() override { AssertNoURLRequests(); }
|
| +
|
| + private:
|
| + URLRequestContextStorage storage_;
|
| +};
|
| +
|
| +struct FetchResult {
|
| + std::string GetBodyAsString() const {
|
| + return std::string(response_body.begin(), response_body.end());
|
| + }
|
| +
|
| + int net_error;
|
| + std::vector<uint8_t> response_body;
|
| +};
|
| +
|
| +// Helper to synchronously wait on the callback to be called.
|
| +// Similar to TestCompletionCallback but built around FetchCallback.
|
| +class TestFetchCallback {
|
| + public:
|
| + TestFetchCallback()
|
| + : callback_(
|
| + base::Bind(&TestFetchCallback::OnCallback, base::Unretained(this))),
|
| + have_result_(false),
|
| + waiting_for_result_(false) {}
|
| +
|
| + const CertNetFetcher::FetchCallback& callback() const { return callback_; }
|
| +
|
| + FetchResult WaitForResult() {
|
| + DCHECK(!waiting_for_result_);
|
| + while (!have_result_) {
|
| + waiting_for_result_ = true;
|
| + base::MessageLoop::current()->Run();
|
| + waiting_for_result_ = false;
|
| + }
|
| + have_result_ = false; // Auto-reset for next callback.
|
| + return result_;
|
| + }
|
| +
|
| + bool HasResult() const { return have_result_; }
|
| +
|
| + private:
|
| + void OnCallback(int net_error, const std::vector<uint8_t>& response_body) {
|
| + DCHECK(!have_result_);
|
| + have_result_ = true;
|
| + result_.net_error = net_error;
|
| + result_.response_body = response_body;
|
| + if (waiting_for_result_)
|
| + base::MessageLoop::current()->Quit();
|
| + }
|
| +
|
| + CertNetFetcher::FetchCallback callback_;
|
| + FetchResult result_;
|
| + bool have_result_;
|
| + bool waiting_for_result_;
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +class CertNetFetcherTest : public PlatformTest {
|
| + public:
|
| + CertNetFetcherTest()
|
| + : test_server_(SpawnedTestServer::TYPE_HTTP,
|
| + net::SpawnedTestServer::kLocalhost,
|
| + base::FilePath(kDocRoot)) {}
|
| +
|
| + protected:
|
| + SpawnedTestServer test_server_;
|
| + RequestContext context_;
|
| +};
|
| +
|
| +scoped_ptr<CertNetFetcher::RequestParams> CreateSimpleRequest(const GURL& url) {
|
| + scoped_ptr<CertNetFetcher::RequestParams> request_params(
|
| + new CertNetFetcher::RequestParams(
|
| + url, CertNetFetcher::REQUEST_TYPE_CA_ISSUERS));
|
| + return request_params;
|
| +}
|
| +
|
| +// Fetch a few unique URLs using GET in parallel. Each URL has different content
|
| +// and Content-Type.
|
| +TEST_F(CertNetFetcherTest, ParallelFetchNoDupes) {
|
| + ASSERT_TRUE(test_server_.Start());
|
| +
|
| + CertNetFetcher fetcher(&context_);
|
| + TestFetchCallback callback1;
|
| + TestFetchCallback callback2;
|
| + TestFetchCallback callback3;
|
| +
|
| + // Request a URL with Content-Type "application/pkix-cert"
|
| + GURL url1 = test_server_.GetURL("files/cert.crt");
|
| + fetcher.Fetch(CreateSimpleRequest(url1), callback1.callback());
|
| +
|
| + // Request a URL with Content-Type "application/pkix-crl"
|
| + GURL url2 = test_server_.GetURL("files/root.crl");
|
| + fetcher.Fetch(CreateSimpleRequest(url2), callback2.callback());
|
| +
|
| + // Request a URL with Content-Type "application/pkcs7-mime"
|
| + GURL url3 = test_server_.GetURL("files/certs.p7c");
|
| + fetcher.Fetch(CreateSimpleRequest(url3), callback3.callback());
|
| +
|
| + // Wait for all of the requests to complete.
|
| + FetchResult result1 = callback1.WaitForResult();
|
| + FetchResult result2 = callback2.WaitForResult();
|
| + FetchResult result3 = callback3.WaitForResult();
|
| +
|
| + // Verify the fetch results.
|
| + EXPECT_EQ(OK, result1.net_error);
|
| + EXPECT_EQ("-cert.crt-\n", result1.GetBodyAsString());
|
| + EXPECT_EQ(OK, result2.net_error);
|
| + EXPECT_EQ("-root.crl-\n", result2.GetBodyAsString());
|
| + EXPECT_EQ(OK, result3.net_error);
|
| + EXPECT_EQ("-certs.p7c-\n", result3.GetBodyAsString());
|
| +}
|
| +
|
| +// Fetch a caIssuers URL which has an unexpected extension and Content-Type.
|
| +// The extension is .txt and the Content-Type is text/plain. Despite being
|
| +// unusual this succeeds as the extension and Content-Type are not required to
|
| +// be meaningful.
|
| +TEST_F(CertNetFetcherTest, ContentTypeDoesntMatter) {
|
| + ASSERT_TRUE(test_server_.Start());
|
| +
|
| + CertNetFetcher fetcher(&context_);
|
| +
|
| + TestFetchCallback callback;
|
| + GURL url = test_server_.GetURL("files/foo.txt");
|
| + fetcher.Fetch(CreateSimpleRequest(url), callback.callback());
|
| + FetchResult result = callback.WaitForResult();
|
| + EXPECT_EQ(OK, result.net_error);
|
| + EXPECT_EQ("-foo.txt-\n", result.GetBodyAsString());
|
| +}
|
| +
|
| +// Fetch a URLs whose HTTP response code is not 200. These are considered
|
| +// failures.
|
| +TEST_F(CertNetFetcherTest, HttpStatusCode) {
|
| + ASSERT_TRUE(test_server_.Start());
|
| +
|
| + CertNetFetcher fetcher(&context_);
|
| +
|
| + // Response with a 404.
|
| + {
|
| + TestFetchCallback callback;
|
| + GURL url = test_server_.GetURL("files/404.html");
|
| + fetcher.Fetch(CreateSimpleRequest(url), callback.callback());
|
| + FetchResult result = callback.WaitForResult();
|
| + EXPECT_EQ(ERR_FAILED, result.net_error);
|
| + EXPECT_TRUE(result.response_body.empty());
|
| + }
|
| +
|
| + // Response with a 500.
|
| + {
|
| + TestFetchCallback callback;
|
| + GURL url = test_server_.GetURL("files/500.html");
|
| + fetcher.Fetch(CreateSimpleRequest(url), callback.callback());
|
| + FetchResult result = callback.WaitForResult();
|
| + EXPECT_EQ(ERR_FAILED, result.net_error);
|
| + EXPECT_TRUE(result.response_body.empty());
|
| + }
|
| +}
|
| +
|
| +// Fetching a URL with a Content-Disposition header should have no effect.
|
| +TEST_F(CertNetFetcherTest, ContentDisposition) {
|
| + ASSERT_TRUE(test_server_.Start());
|
| +
|
| + CertNetFetcher fetcher(&context_);
|
| +
|
| + TestFetchCallback callback;
|
| + GURL url = test_server_.GetURL("files/downloadable.js");
|
| + fetcher.Fetch(CreateSimpleRequest(url), callback.callback());
|
| + FetchResult result = callback.WaitForResult();
|
| + EXPECT_EQ(OK, result.net_error);
|
| + EXPECT_EQ("-downloadable.js-\n", result.GetBodyAsString());
|
| +}
|
| +
|
| +// Verifies that a cachable request will be served from the HTTP cache the
|
| +// second time it is requested.
|
| +TEST_F(CertNetFetcherTest, Cache) {
|
| + ASSERT_TRUE(test_server_.Start());
|
| +
|
| + CertNetFetcher fetcher(&context_);
|
| +
|
| + // Fetch a URL whose HTTP headers make it cacheable for 1 hour.
|
| + GURL url(test_server_.GetURL("files/cacheable_1hr.crt"));
|
| + {
|
| + TestFetchCallback callback;
|
| + fetcher.Fetch(CreateSimpleRequest(url), callback.callback());
|
| + FetchResult result = callback.WaitForResult();
|
| + EXPECT_EQ(OK, result.net_error);
|
| + EXPECT_EQ("-cacheable_1hr.crt-\n", result.GetBodyAsString());
|
| + }
|
| +
|
| + // Kill the HTTP server.
|
| + ASSERT_TRUE(test_server_.Stop());
|
| +
|
| + // Fetch again -- will fail unless served from cache.
|
| + {
|
| + TestFetchCallback callback;
|
| + fetcher.Fetch(CreateSimpleRequest(url), callback.callback());
|
| + FetchResult result = callback.WaitForResult();
|
| + EXPECT_EQ(OK, result.net_error);
|
| + EXPECT_EQ("-cacheable_1hr.crt-\n", result.GetBodyAsString());
|
| + }
|
| +}
|
| +
|
| +// Verify that the maximum response body constraints are enforced by fetching a
|
| +// resource that is larger than the limit.
|
| +TEST_F(CertNetFetcherTest, TooLarge) {
|
| + ASSERT_TRUE(test_server_.Start());
|
| +
|
| + CertNetFetcher fetcher(&context_);
|
| +
|
| + // This file has a response body 12 bytes long. So setting the maximum to 11
|
| + // bytes will cause it to fail.
|
| + GURL url(test_server_.GetURL("files/certs.p7c"));
|
| + scoped_ptr<CertNetFetcher::RequestParams> params(CreateSimpleRequest(url));
|
| + params->max_response_size_in_bytes = 11;
|
| +
|
| + TestFetchCallback callback;
|
| + fetcher.Fetch(params.Pass(), callback.callback());
|
| + FetchResult result = callback.WaitForResult();
|
| + EXPECT_EQ(ERR_FILE_TOO_BIG, result.net_error);
|
| + EXPECT_TRUE(result.response_body.empty());
|
| +}
|
| +
|
| +// Set the timeout to 10 milliseconds, and try fetching a URL that takes 5
|
| +// seconds to complete. It should fail due to a timeout.
|
| +TEST_F(CertNetFetcherTest, Hang) {
|
| + ASSERT_TRUE(test_server_.Start());
|
| +
|
| + CertNetFetcher fetcher(&context_);
|
| +
|
| + GURL url(test_server_.GetURL("slow/certs.p7c?5.1"));
|
| + scoped_ptr<CertNetFetcher::RequestParams> params(CreateSimpleRequest(url));
|
| + params->timeout = base::TimeDelta::FromMilliseconds(10);
|
| +
|
| + TestFetchCallback callback;
|
| + fetcher.Fetch(params.Pass(), callback.callback());
|
| + FetchResult result = callback.WaitForResult();
|
| + EXPECT_EQ(ERR_TIMED_OUT, result.net_error);
|
| + EXPECT_TRUE(result.response_body.empty());
|
| +}
|
| +
|
| +// Verify that if a response is gzip-encoded it gets inflated before being
|
| +// returned to the caller.
|
| +TEST_F(CertNetFetcherTest, Gzip) {
|
| + ASSERT_TRUE(test_server_.Start());
|
| +
|
| + CertNetFetcher fetcher(&context_);
|
| +
|
| + GURL url(test_server_.GetURL("files/gzipped_crl"));
|
| + TestFetchCallback callback;
|
| + fetcher.Fetch(CreateSimpleRequest(url), callback.callback());
|
| + FetchResult result = callback.WaitForResult();
|
| + EXPECT_EQ(OK, result.net_error);
|
| + EXPECT_EQ("-gzipped_crl-\n", result.GetBodyAsString());
|
| +}
|
| +
|
| +// Try fetching an unsupported URL scheme (https).
|
| +TEST_F(CertNetFetcherTest, HttpsNotAllowed) {
|
| + ASSERT_TRUE(test_server_.Start());
|
| +
|
| + CertNetFetcher fetcher(&context_);
|
| +
|
| + GURL url("https://foopy/foo.crt");
|
| + TestFetchCallback callback;
|
| + fetcher.Fetch(CreateSimpleRequest(url), callback.callback());
|
| + // Should NOT complete synchronously despite being a test that could be done
|
| + // immediately.
|
| + EXPECT_FALSE(callback.HasResult());
|
| + FetchResult result = callback.WaitForResult();
|
| + EXPECT_EQ(ERR_DISALLOWED_URL_SCHEME, result.net_error);
|
| + EXPECT_TRUE(result.response_body.empty());
|
| +}
|
| +
|
| +// Try fetching a URL which redirects to https.
|
| +TEST_F(CertNetFetcherTest, RedirectToHttpsNotAllowed) {
|
| + ASSERT_TRUE(test_server_.Start());
|
| +
|
| + CertNetFetcher fetcher(&context_);
|
| +
|
| + GURL url(test_server_.GetURL("files/redirect_https"));
|
| + TestFetchCallback callback;
|
| + fetcher.Fetch(CreateSimpleRequest(url), callback.callback());
|
| + FetchResult result = callback.WaitForResult();
|
| + EXPECT_EQ(ERR_DISALLOWED_URL_SCHEME, result.net_error);
|
| + EXPECT_TRUE(result.response_body.empty());
|
| +}
|
| +
|
| +// Try fetching an unsupported URL scheme (https) and then immediately
|
| +// cancelling. This is a bit special because this codepath needs to post a task.
|
| +TEST_F(CertNetFetcherTest, CancelHttpsNotAllowed) {
|
| + ASSERT_TRUE(test_server_.Start());
|
| +
|
| + CertNetFetcher fetcher(&context_);
|
| +
|
| + GURL url("https://foopy/foo.crt");
|
| + TestFetchCallback callback;
|
| + CertNetFetcher::RequestId id =
|
| + fetcher.Fetch(CreateSimpleRequest(url), callback.callback());
|
| +
|
| + // Should NOT complete synchronously despite being a test that could be done
|
| + // immediately.
|
| + EXPECT_FALSE(callback.HasResult());
|
| +
|
| + fetcher.CancelRequest(id);
|
| +}
|
| +
|
| +// Start a few requests, and cancel one of them before running the message loop
|
| +// again.
|
| +TEST_F(CertNetFetcherTest, CancelBeforeRunningMessageLoop) {
|
| + ASSERT_TRUE(test_server_.Start());
|
| +
|
| + CertNetFetcher fetcher(&context_);
|
| + TestFetchCallback callback1;
|
| + TestFetchCallback callback2;
|
| + TestFetchCallback callback3;
|
| +
|
| + // Request a URL with Content-Type "application/pkix-cert"
|
| + GURL url1 = test_server_.GetURL("files/cert.crt");
|
| + fetcher.Fetch(CreateSimpleRequest(url1), callback1.callback());
|
| +
|
| + // Request a URL with Content-Type "application/pkix-crl"
|
| + GURL url2 = test_server_.GetURL("files/root.crl");
|
| + CertNetFetcher::RequestId id2 =
|
| + fetcher.Fetch(CreateSimpleRequest(url2), callback2.callback());
|
| +
|
| + // Request a URL with Content-Type "application/pkcs7-mime"
|
| + GURL url3 = test_server_.GetURL("files/certs.p7c");
|
| + fetcher.Fetch(CreateSimpleRequest(url3), callback3.callback());
|
| +
|
| + EXPECT_FALSE(callback1.HasResult());
|
| + EXPECT_FALSE(callback2.HasResult());
|
| + EXPECT_FALSE(callback3.HasResult());
|
| +
|
| + // Cancel the second request.
|
| + fetcher.CancelRequest(id2);
|
| +
|
| + // Wait for the non-cancelled requests to complete.
|
| + FetchResult result1 = callback1.WaitForResult();
|
| + FetchResult result3 = callback3.WaitForResult();
|
| +
|
| + // Verify the fetch results.
|
| + EXPECT_EQ(OK, result1.net_error);
|
| + EXPECT_EQ("-cert.crt-\n", result1.GetBodyAsString());
|
| + EXPECT_EQ(OK, result3.net_error);
|
| + EXPECT_EQ("-certs.p7c-\n", result3.GetBodyAsString());
|
| +
|
| + EXPECT_FALSE(callback2.HasResult());
|
| +}
|
| +
|
| +// Start several requests, and cancel one of them after the first has completed.
|
| +TEST_F(CertNetFetcherTest, CancelSlowRequest) {
|
| + ASSERT_TRUE(test_server_.Start());
|
| +
|
| + CertNetFetcher fetcher(&context_);
|
| + TestFetchCallback callback1;
|
| + TestFetchCallback callback2;
|
| + TestFetchCallback callback3;
|
| + TestFetchCallback callback4;
|
| +
|
| + // Request a URL with Content-Type "application/pkix-cert"
|
| + GURL url1 = test_server_.GetURL("files/cert.crt");
|
| + fetcher.Fetch(CreateSimpleRequest(url1), callback1.callback());
|
| +
|
| + // This request will take 10 seconds to complete.
|
| + GURL url2 = test_server_.GetURL("slow/certs.p7c?10.1");
|
| + CertNetFetcher::RequestId id2 =
|
| + fetcher.Fetch(CreateSimpleRequest(url2), callback2.callback());
|
| +
|
| + // Request a URL with Content-Type "application/pkcs7-mime"
|
| + GURL url3 = test_server_.GetURL("files/certs.p7c");
|
| + fetcher.Fetch(CreateSimpleRequest(url3), callback3.callback());
|
| +
|
| + // Request a URL with Content-Type "application/pkcs7-mime"
|
| + GURL url4("ftp://www.not.supported.com/foo");
|
| + fetcher.Fetch(CreateSimpleRequest(url4), callback4.callback());
|
| +
|
| + EXPECT_FALSE(callback1.HasResult());
|
| + EXPECT_FALSE(callback2.HasResult());
|
| + EXPECT_FALSE(callback3.HasResult());
|
| + EXPECT_FALSE(callback4.HasResult());
|
| +
|
| + // Wait for the fast request to complete.
|
| + FetchResult result4 = callback4.WaitForResult();
|
| + EXPECT_EQ(ERR_DISALLOWED_URL_SCHEME, result4.net_error);
|
| + EXPECT_TRUE(result4.response_body.empty());
|
| +
|
| + // Cancel the second request.
|
| + fetcher.CancelRequest(id2);
|
| +
|
| + // Wait for the other requests to complete.
|
| + FetchResult result1 = callback1.WaitForResult();
|
| + FetchResult result3 = callback3.WaitForResult();
|
| +
|
| + // Verify the fetch results.
|
| + EXPECT_EQ(OK, result1.net_error);
|
| + EXPECT_EQ("-cert.crt-\n", result1.GetBodyAsString());
|
| + EXPECT_EQ(OK, result3.net_error);
|
| + EXPECT_EQ("-certs.p7c-\n", result3.GetBodyAsString());
|
| +
|
| + EXPECT_FALSE(callback2.HasResult());
|
| +}
|
| +
|
| +// Delete a CertNetFetcher with outstanding requests on it.
|
| +TEST_F(CertNetFetcherTest, DeleteCancels) {
|
| + ASSERT_TRUE(test_server_.Start());
|
| +
|
| + CertNetFetcher fetcher(&context_);
|
| +
|
| + GURL url(test_server_.GetURL("slow/certs.p7c?20.1"));
|
| + TestFetchCallback callback;
|
| + fetcher.Fetch(CreateSimpleRequest(url), callback.callback());
|
| +
|
| + // Note that the request is never completed, nor cancelled.
|
| +}
|
| +
|
| +} // namespace net
|
|
|