blob: 3a1e22c68cbc4fcbab8762d01e0b6d9288759f3b [file] [log] [blame]
// Copyright 2012 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.
#import "ios/web/net/request_tracker_impl.h"
#include <stddef.h>
#include <memory>
#include <vector>
#include "base/bind.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/sys_string_conversions.h"
#include "ios/web/public/cert_policy.h"
#include "ios/web/public/certificate_policy_cache.h"
#include "ios/web/public/ssl_status.h"
#include "ios/web/public/test/fakes/test_browser_state.h"
#include "ios/web/public/test/test_web_thread_bundle.h"
#include "net/cert/x509_certificate.h"
#include "net/http/http_response_headers.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_job_factory.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_test_job.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#include "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#include "third_party/ocmock/gtest_support.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface RequestTrackerNotificationReceiverTest : NSObject {
@public
float value_;
float max_;
@private
NSString* error_;
scoped_refptr<net::HttpResponseHeaders> headers_;
}
- (NSString*)error;
- (net::HttpResponseHeaders*)headers;
@end
@implementation RequestTrackerNotificationReceiverTest
- (id)init {
self = [super init];
if (self) {
value_ = 0.0f;
max_ = 0.0f;
}
return self;
}
- (NSString*)error {
return error_;
}
- (net::HttpResponseHeaders*)headers {
return headers_.get();
}
@end
namespace {
// Used and incremented each time a tabId is created.
int g_count = 0;
// URLRequest::Delegate that does nothing.
class DummyURLRequestDelegate : public net::URLRequest::Delegate {
public:
DummyURLRequestDelegate() {}
~DummyURLRequestDelegate() override {}
void OnResponseStarted(net::URLRequest* request, int net_error) override {}
void OnReadCompleted(net::URLRequest* request, int bytes_read) override {}
private:
DISALLOW_COPY_AND_ASSIGN(DummyURLRequestDelegate);
};
class RequestTrackerTest : public PlatformTest {
public:
RequestTrackerTest()
: thread_bundle_(web::TestWebThreadBundle::IO_MAINLOOP) {}
~RequestTrackerTest() override {}
void SetUp() override {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
request_group_id_ = [NSString stringWithFormat:@"test%d", g_count++];
tracker_ = web::RequestTrackerImpl::CreateTrackerForRequestGroupID(
request_group_id_, &browser_state_, browser_state_.GetRequestContext());
}
void TearDown() override {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
tracker_->Close();
}
web::TestWebThreadBundle thread_bundle_;
scoped_refptr<web::RequestTrackerImpl> tracker_;
NSString* request_group_id_;
web::TestBrowserState browser_state_;
std::vector<std::unique_ptr<net::URLRequestContext>> contexts_;
std::vector<std::unique_ptr<net::URLRequest>> requests_;
net::URLRequestJobFactoryImpl job_factory_;
GURL GetURL(size_t i) {
std::stringstream ss;
ss << "http://www/";
ss << i;
return GURL(ss.str());
}
GURL GetSecureURL(size_t i) {
std::stringstream ss;
ss << "https://www/";
ss << i;
return GURL(ss.str());
}
net::URLRequest* GetRequest(size_t i) {
return GetInternalRequest(i, false);
}
net::URLRequest* GetSecureRequest(size_t i) {
return GetInternalRequest(i, true);
}
NSString* WaitUntilLoop(bool (^condition)(void)) {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
base::Time maxDate = base::Time::Now() + base::TimeDelta::FromSeconds(10);
while (!condition()) {
if (base::Time::Now() > maxDate)
return @"Time is up, too slow to go";
base::RunLoop().RunUntilIdle();
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1));
}
return nil;
}
void TrimRequest(NSString* tab_id, const GURL& url) {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
tracker_->StartPageLoad(url, nil);
}
void EndPage(NSString* tab_id, const GURL& url) {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
tracker_->FinishPageLoad(url, false);
base::RunLoop().RunUntilIdle();
}
net::TestJobInterceptor* AddInterceptorToRequest(size_t i) {
// |interceptor| will be deleted from |job_factory_|'s destructor.
net::TestJobInterceptor* protocol_handler = new net::TestJobInterceptor();
job_factory_.SetProtocolHandler("http", base::WrapUnique(protocol_handler));
contexts_[i]->set_job_factory(&job_factory_);
return protocol_handler;
}
private:
net::URLRequest* GetInternalRequest(size_t i, bool secure) {
GURL url;
if (secure)
url = GetSecureURL(requests_.size());
else
url = GetURL(requests_.size());
while (i >= requests_.size()) {
contexts_.push_back(std::make_unique<net::URLRequestContext>());
requests_.push_back(contexts_[i]->CreateRequest(
url, net::DEFAULT_PRIORITY, &request_delegate_));
if (secure) {
// Put a valid SSLInfo inside
net::HttpResponseInfo* response =
const_cast<net::HttpResponseInfo*>(&requests_[i]->response_info());
response->ssl_info.cert = net::ImportCertFromFile(
net::GetTestCertsDirectory(), "ok_cert.pem");
response->ssl_info.cert_status = 0; // No errors.
EXPECT_TRUE(requests_[i]->ssl_info().is_valid());
}
}
EXPECT_TRUE(!secure == !requests_[i]->url().SchemeIsCryptographic());
return requests_[i].get();
}
DummyURLRequestDelegate request_delegate_;
DISALLOW_COPY_AND_ASSIGN(RequestTrackerTest);
};
TEST_F(RequestTrackerTest, OnePage) {
// Start a request.
tracker_->StartRequest(GetRequest(0));
// Start page load.
TrimRequest(request_group_id_, GetURL(0));
// Stop the request.
tracker_->StopRequest(GetRequest(0));
EndPage(request_group_id_, GetURL(0));
}
TEST_F(RequestTrackerTest, OneSecurePage) {
net::URLRequest* request = GetSecureRequest(0);
GURL url = GetSecureURL(0);
// Start a page.
TrimRequest(request_group_id_, url);
// Start a request.
tracker_->StartRequest(request);
tracker_->CaptureReceivedBytes(request, 42);
// Stop the request.
tracker_->StopRequest(request);
EndPage(request_group_id_, url);
}
TEST_F(RequestTrackerTest, OnePageAndResources) {
// Start a page.
TrimRequest(request_group_id_, GetURL(0));
// Start two requests.
tracker_->StartRequest(GetRequest(0));
tracker_->StartRequest(GetRequest(1));
tracker_->StopRequest(GetRequest(0));
tracker_->StartRequest(GetRequest(2));
tracker_->StopRequest(GetRequest(1));
tracker_->StartRequest(GetRequest(3));
tracker_->StopRequest(GetRequest(2));
tracker_->StartRequest(GetRequest(4));
tracker_->StopRequest(GetRequest(3));
tracker_->StopRequest(GetRequest(4));
EndPage(request_group_id_, GetURL(0));
}
TEST_F(RequestTrackerTest, OnePageOneBigImage) {
// Start a page.
TrimRequest(request_group_id_, GetURL(0));
tracker_->StartRequest(GetRequest(0));
tracker_->StopRequest(GetRequest(0));
tracker_->StartRequest(GetRequest(1));
tracker_->CaptureReceivedBytes(GetRequest(1), 10);
tracker_->CaptureExpectedLength(GetRequest(1), 100);
tracker_->CaptureReceivedBytes(GetRequest(1), 10);
tracker_->CaptureReceivedBytes(GetRequest(1), 10);
tracker_->CaptureReceivedBytes(GetRequest(1), 10);
tracker_->CaptureReceivedBytes(GetRequest(1), 10);
tracker_->CaptureReceivedBytes(GetRequest(1), 10);
tracker_->CaptureReceivedBytes(GetRequest(1), 10);
tracker_->CaptureReceivedBytes(GetRequest(1), 10);
tracker_->CaptureReceivedBytes(GetRequest(1), 10);
tracker_->CaptureReceivedBytes(GetRequest(1), 10);
tracker_->StopRequest(GetRequest(1));
EndPage(request_group_id_, GetURL(0));
}
TEST_F(RequestTrackerTest, TwoPagesPostStart) {
tracker_->StartRequest(GetRequest(0));
TrimRequest(request_group_id_, GetURL(0));
tracker_->StartRequest(GetRequest(1));
tracker_->StartRequest(GetRequest(2));
tracker_->StopRequest(GetRequest(0));
tracker_->StopRequest(GetRequest(1));
tracker_->StopRequest(GetRequest(2));
EndPage(request_group_id_, GetURL(0));
tracker_->StartRequest(GetRequest(3));
TrimRequest(request_group_id_, GetURL(3));
tracker_->StopRequest(GetRequest(3));
EndPage(request_group_id_, GetURL(3));
}
TEST_F(RequestTrackerTest, TwoPagesPreStart) {
tracker_->StartRequest(GetRequest(0));
TrimRequest(request_group_id_, GetURL(0));
tracker_->StartRequest(GetRequest(1));
tracker_->StartRequest(GetRequest(2));
tracker_->StopRequest(GetRequest(0));
tracker_->StopRequest(GetRequest(1));
tracker_->StopRequest(GetRequest(2));
EndPage(request_group_id_, GetURL(0));
TrimRequest(request_group_id_, GetURL(3));
tracker_->StartRequest(GetRequest(3));
tracker_->StopRequest(GetRequest(3));
EndPage(request_group_id_, GetURL(3));
}
TEST_F(RequestTrackerTest, TwoPagesNoWait) {
tracker_->StartRequest(GetRequest(0));
TrimRequest(request_group_id_, GetURL(0));
tracker_->StartRequest(GetRequest(1));
tracker_->StartRequest(GetRequest(2));
tracker_->StopRequest(GetRequest(0));
tracker_->StopRequest(GetRequest(1));
tracker_->StopRequest(GetRequest(2));
TrimRequest(request_group_id_, GetURL(3));
tracker_->StartRequest(GetRequest(3));
tracker_->StopRequest(GetRequest(3));
EndPage(request_group_id_, GetURL(3));
}
// Do-nothing mock CertificatePolicyCache. Allows all certs for all hosts.
class MockCertificatePolicyCache : public web::CertificatePolicyCache {
public:
MockCertificatePolicyCache() {}
void AllowCertForHost(net::X509Certificate* cert,
const std::string& host,
net::CertStatus error) override {
}
web::CertPolicy::Judgment QueryPolicy(net::X509Certificate* cert,
const std::string& host,
net::CertStatus error) override {
return web::CertPolicy::Judgment::ALLOWED;
}
void ClearCertificatePolicies() override {
}
private:
~MockCertificatePolicyCache() override {}
};
void TwoStartsSSLCallback(bool* called, bool ok) {
*called = true;
}
// crbug/386180
TEST_F(RequestTrackerTest, DISABLED_TwoStartsNoEstimate) {
net::SSLInfo ssl_info;
ssl_info.cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
ssl_info.cert_status = net::CERT_STATUS_AUTHORITY_INVALID;
scoped_refptr<MockCertificatePolicyCache> cache;
tracker_->SetCertificatePolicyCacheForTest(cache.get());
TrimRequest(request_group_id_, GetSecureURL(0));
tracker_->StartRequest(GetSecureRequest(0));
tracker_->StartRequest(GetSecureRequest(1));
bool request_0_called = false;
bool request_1_called = false;
tracker_->OnSSLCertificateError(GetSecureRequest(0), ssl_info, true,
base::Bind(&TwoStartsSSLCallback,
&request_0_called));
tracker_->OnSSLCertificateError(GetSecureRequest(1), ssl_info, true,
base::Bind(&TwoStartsSSLCallback,
&request_1_called));
EXPECT_TRUE(request_0_called);
EXPECT_TRUE(request_1_called);
}
} // namespace