blob: 893f9abf92cbf798de47be4f3e138892a75f1231 [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.
#include "ios/web/net/request_tracker_impl.h"
#include "base/logging.h"
#include "base/mac/scoped_nsobject.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_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/test_browser_state.h"
#include "ios/web/public/test/test_web_thread.h"
#include "net/cert/x509_certificate.h"
#include "net/http/http_response_headers.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"
#include "testing/gtest_mac.h"
#include "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#include "third_party/ocmock/gtest_support.h"
@interface RequestTrackerNotificationReceiverTest
: NSObject<CRWRequestTrackerDelegate> {
@public
float value_;
float max_;
@private
base::scoped_nsobject<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;
}
- (BOOL)isForStaticFileRequests {
return NO;
}
- (void)updatedProgress:(float)progress {
if (progress > 0.0f) {
if (progress < value_) {
error_.reset(
[[NSString stringWithFormat:
@"going down from %f to %f", value_, progress] retain]);
}
value_ = progress;
} else {
value_ = 0.0f;
}
if (value_ > max_) {
max_ = value_;
}
}
- (NSString*)error {
return error_;
}
- (void)handleResponseHeaders:(net::HttpResponseHeaders*)headers
requestUrl:(const GURL&)requestUrl {
headers_ = headers;
}
- (net::HttpResponseHeaders*)headers {
return headers_.get();
}
- (void)updatedSSLStatus:(const web::SSLStatus&)sslStatus
forPageUrl:(const GURL&)url
userInfo:(id)userInfo {
// Nothing. yet.
}
- (void)presentSSLError:(const net::SSLInfo&)info
forSSLStatus:(const web::SSLStatus&)status
onUrl:(const GURL&)url
recoverable:(BOOL)recoverable
callback:(SSLErrorCallback)shouldContinue {
// Nothing, yet.
}
- (void)certificateUsed:(net::X509Certificate*)certificate
forHost:(const std::string&)host
status:(net::CertStatus)status {
// Nothing, yet.
}
- (void)clearCertificates {
// Nothing, yet.
}
- (void)handlePassKitObject:(NSData*)data {
// Nothing yet.
}
@end
namespace {
// Used and incremented each time a tabId is created.
int g_count = 0;
class RequestTrackerTest : public PlatformTest {
public:
RequestTrackerTest()
: loop_(base::MessageLoop::TYPE_IO),
ui_thread_(web::WebThread::UI, &loop_),
io_thread_(web::WebThread::IO, &loop_){};
~RequestTrackerTest() override {}
void SetUp() override {
DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
request_group_id_.reset(
[[NSString stringWithFormat:@"test%d", g_count++] retain]);
receiver_.reset([[RequestTrackerNotificationReceiverTest alloc] init]);
tracker_ = web::RequestTrackerImpl::CreateTrackerForRequestGroupID(
request_group_id_,
&browser_state_,
browser_state_.GetRequestContext(),
receiver_);
}
void TearDown() override {
DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
tracker_->Close();
}
base::MessageLoop loop_;
web::TestWebThread ui_thread_;
web::TestWebThread io_thread_;
base::scoped_nsobject<RequestTrackerNotificationReceiverTest> receiver_;
scoped_refptr<web::RequestTrackerImpl> tracker_;
base::scoped_nsobject<NSString> request_group_id_;
web::TestBrowserState browser_state_;
ScopedVector<net::URLRequestContext> contexts_;
ScopedVector<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_THREAD(web::WebThread::UI);
base::Time maxDate = base::Time::Now() + base::TimeDelta::FromSeconds(10);
while (!condition()) {
if ([receiver_ error])
return [receiver_ error];
if (base::Time::Now() > maxDate)
return @"Time is up, too slow to go";
loop_.RunUntilIdle();
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1));
}
return nil;
}
NSString* CheckActive() {
NSString* message = WaitUntilLoop(^{
return (receiver_.get()->value_ > 0.0f);
});
if (!message && (receiver_.get()->max_ == 0.0f))
message = @"Max should be greater than 0.0";
return message;
}
void TrimRequest(NSString* tab_id, const GURL& url) {
DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
receiver_.get()->value_ = 0.0f;
receiver_.get()->max_ = 0.0f;
tracker_->StartPageLoad(url, nil);
}
void EndPage(NSString* tab_id, const GURL& url) {
DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
tracker_->FinishPageLoad(url, false);
receiver_.get()->value_ = 0.0f;
receiver_.get()->max_ = 0.0f;
loop_.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", 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(new net::URLRequestContext());
requests_.push_back(contexts_[i]->CreateRequest(url,
net::DEFAULT_PRIORITY,
NULL).release());
if (secure) {
// Put a valid SSLInfo inside
net::HttpResponseInfo* response =
const_cast<net::HttpResponseInfo*>(&requests_[i]->response_info());
response->ssl_info.cert = new net::X509Certificate(
"subject", "issuer",
base::Time::Now() - base::TimeDelta::FromDays(2),
base::Time::Now() + base::TimeDelta::FromDays(2));
response->ssl_info.cert_status = 0; // No errors.
response->ssl_info.security_bits = 128;
EXPECT_TRUE(requests_[i]->ssl_info().is_valid());
}
}
EXPECT_TRUE(!secure == !requests_[i]->url().SchemeIsCryptographic());
return requests_[i];
}
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));
EXPECT_NSEQ(nil, CheckActive());
// 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);
EXPECT_NSEQ(nil, CheckActive());
// 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));
EXPECT_NSEQ(nil, CheckActive());
tracker_->StopRequest(GetRequest(0));
tracker_->StartRequest(GetRequest(2));
EXPECT_NSEQ(nil, CheckActive());
tracker_->StopRequest(GetRequest(1));
tracker_->StartRequest(GetRequest(3));
EXPECT_NSEQ(nil, CheckActive());
tracker_->StopRequest(GetRequest(2));
tracker_->StartRequest(GetRequest(4));
EXPECT_NSEQ(nil, CheckActive());
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));
EXPECT_NSEQ(nil, CheckActive());
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);
EXPECT_NSEQ(nil, CheckActive());
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));
EXPECT_NSEQ(nil, CheckActive());
tracker_->StartRequest(GetRequest(1));
tracker_->StartRequest(GetRequest(2));
EXPECT_NSEQ(nil, CheckActive());
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));
EXPECT_NSEQ(nil, CheckActive());
tracker_->StopRequest(GetRequest(3));
EndPage(request_group_id_, GetURL(3));
}
TEST_F(RequestTrackerTest, TwoPagesPreStart) {
tracker_->StartRequest(GetRequest(0));
TrimRequest(request_group_id_, GetURL(0));
EXPECT_NSEQ(nil, CheckActive());
tracker_->StartRequest(GetRequest(1));
tracker_->StartRequest(GetRequest(2));
EXPECT_NSEQ(nil, CheckActive());
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));
EXPECT_NSEQ(nil, CheckActive());
tracker_->StartRequest(GetRequest(1));
tracker_->StartRequest(GetRequest(2));
EXPECT_NSEQ(nil, CheckActive());
tracker_->StopRequest(GetRequest(0));
tracker_->StopRequest(GetRequest(1));
tracker_->StopRequest(GetRequest(2));
EXPECT_NSEQ(nil, CheckActive());
TrimRequest(request_group_id_, GetURL(3));
tracker_->StartRequest(GetRequest(3));
EXPECT_NSEQ(nil, CheckActive());
tracker_->StopRequest(GetRequest(3));
EXPECT_NSEQ(nil, CheckActive());
EndPage(request_group_id_, GetURL(3));
}
TEST_F(RequestTrackerTest, CaptureHeaders) {
std::string headers =
"HTTP/1.1 200 OK\n"
"content-type: multipart/mixed; boundary=inner\n"
"content-disposition: attachment; filename=\"name.pdf\"\n"
"X-Auto-Login: Hello World\n\n";
for (size_t i = 0; i < headers.length(); i++) {
if (headers.data()[i] == '\n')
const_cast<char*>(headers.data())[i] = '\0';
}
net::URLRequest* request = GetRequest(0);
const_cast<net::HttpResponseInfo&>(request->response_info()).headers =
new net::HttpResponseHeaders(headers);
// |job| will be owned by |request| and released from its destructor.
net::URLRequestTestJob* job = new net::URLRequestTestJob(
request, request->context()->network_delegate(), headers, "", false);
AddInterceptorToRequest(0)->set_main_intercept_job(job);
request->Start();
tracker_->StartRequest(request);
tracker_->CaptureHeaders(request);
tracker_->StopRequest(request);
loop_.RunUntilIdle();
EXPECT_TRUE([receiver_ headers]->HasHeaderValue("X-Auto-Login",
"Hello World"));
std::string mimeType;
EXPECT_TRUE([receiver_ headers]->GetMimeType(&mimeType));
EXPECT_EQ("multipart/mixed", mimeType);
EXPECT_TRUE([receiver_ headers]->HasHeaderValue(
"Content-Disposition", "attachment; filename=\"name.pdf\""));
}
// 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::X509Certificate* cert =
new net::X509Certificate("subject", "issuer", base::Time::Now(),
base::Time::Max());
net::SSLInfo ssl_info;
ssl_info.cert = cert;
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