1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ios/web/net/request_tracker_impl.h"
9 #include "base/containers/hash_tables.h"
10 #include "base/location.h"
11 #include "base/logging.h"
12 #include "base/mac/bind_objc_block.h"
13 #include "base/mac/scoped_nsobject.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/synchronization/lock.h"
17 #import "ios/net/clients/crn_forwarding_network_client.h"
18 #import "ios/net/clients/crn_forwarding_network_client_factory.h"
19 #import "ios/web/crw_network_activity_indicator_manager.h"
20 #import "ios/web/history_state_util.h"
21 #import "ios/web/net/crw_request_tracker_delegate.h"
22 #include "ios/web/public/browser_state.h"
23 #include "ios/web/public/cert_store.h"
24 #include "ios/web/public/certificate_policy_cache.h"
25 #include "ios/web/public/ssl_status.h"
26 #include "ios/web/public/url_util.h"
27 #include "ios/web/public/web_thread.h"
28 #import "net/base/mac/url_conversions.h"
29 #include "net/base/net_errors.h"
30 #include "net/http/http_response_headers.h"
31 #include "net/url_request/url_request.h"
35 struct EqualNSStrings {
36 bool operator()(const base::scoped_nsobject<NSString>& s1,
37 const base::scoped_nsobject<NSString>& s2) const {
38 // Use a ternary due to the BOOL vs bool type difference.
39 return [s1 isEqualToString:s2] ? true : false;
44 size_t operator()(const base::scoped_nsobject<NSString>& s) const {
49 // A map of all RequestTrackerImpls for tabs that are:
51 // * Recently closed waiting for all their network operations to finish.
52 // The code accesses this variable from two threads: the consumer is expected to
53 // always access it from the main thread, the provider is accessing it from the
54 // WebThread, a thread created by the UIWebView/CFURL. For this reason access to
55 // this variable must always gated by |g_trackers_lock|.
56 typedef base::hash_map<base::scoped_nsobject<NSString>,
57 web::RequestTrackerImpl*,
58 HashNSString, EqualNSStrings> TrackerMap;
60 TrackerMap* g_trackers = NULL;
61 base::Lock* g_trackers_lock = NULL;
62 pthread_once_t g_once_control = PTHREAD_ONCE_INIT;
64 // Flag, lock, and function to implement BlockUntilTrackersShutdown().
65 // |g_waiting_on_io_thread| is guarded by |g_waiting_on_io_thread_lock|;
66 // it is set to true when the shutdown wait starts, then a call to
67 // StopIOThreadWaiting is posted to the IO thread (enqueued after any pending
68 // request terminations) while the posting method loops over a check on the
69 // |g_waiting_on_io_thread|.
70 static bool g_waiting_on_io_thread = false;
71 base::Lock* g_waiting_on_io_thread_lock = NULL;
72 void StopIOThreadWaiting() {
73 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
74 base::AutoLock scoped_lock(*g_waiting_on_io_thread_lock);
75 g_waiting_on_io_thread = false;
78 // Initialize global state. Calls to this should be conditional on
79 // |g_once_control| (that is, this should only be called once, across all
81 void InitializeGlobals() {
82 g_trackers = new TrackerMap;
83 g_trackers_lock = new base::Lock;
84 g_waiting_on_io_thread_lock = new base::Lock;
87 // Each request tracker get a unique increasing number, used anywhere an
88 // identifier is needed for tracker (e.g. storing certs).
89 int g_next_request_tracker_id = 0;
91 // IsIntranetHost logic and its associated kDot constant are lifted directly
92 // from content/browser/ssl/ssl_policy.cc. Unfortunately that particular file
93 // has way too many dependencies on content to be used on iOS.
94 static const char kDot = '.';
96 static bool IsIntranetHost(const std::string& host) {
97 const size_t dot = host.find(kDot);
98 return dot == std::string::npos || dot == host.length() - 1;
101 // Add |tracker| to |g_trackers| under |key|.
102 static void RegisterTracker(web::RequestTrackerImpl* tracker, NSString* key) {
103 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
104 pthread_once(&g_once_control, &InitializeGlobals);
106 base::scoped_nsobject<NSString> scoped_key([key copy]);
107 base::AutoLock scoped_lock(*g_trackers_lock);
108 DCHECK(!g_trackers->count(scoped_key));
109 (*g_trackers)[scoped_key] = tracker;
114 void DoNothing(bool flag) {}
118 // The structure used to gather the information about the resources loaded.
119 struct TrackerCounts {
121 TrackerCounts(const GURL& tracked_url, const net::URLRequest* tracked_request)
123 first_party_for_cookies_origin(
124 tracked_request->first_party_for_cookies().GetOrigin()),
125 request(tracked_request),
126 ssl_info(net::SSLInfo()),
127 ssl_judgment(web::CertPolicy::ALLOWED),
128 allowed_by_user(false),
132 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
133 is_subrequest = tracked_request->first_party_for_cookies().is_valid() &&
134 tracked_request->url() != tracked_request->first_party_for_cookies();
139 // The origin of the url of the top level document of the resource. This is
140 // used to ignore request coming from an old document when detecting mixed
142 const GURL first_party_for_cookies_origin;
143 // The request associated with this struct. As a void* to prevent access from
146 // SSLInfo for the request.
147 net::SSLInfo ssl_info;
148 // Is the SSL request blocked waiting for user choice.
149 web::CertPolicy::Judgment ssl_judgment;
150 // True if |ssl_judgment| is ALLOWED as the result of a user choice.
151 bool allowed_by_user;
152 // block to call to cancel or authorize a blocked request.
153 net::RequestTracker::SSLCallback ssl_callback;
154 // If known, the expected length of the resource in bytes.
155 uint64_t expected_length;
156 // Number of bytes loaded so far.
158 // Set to true is the resource is fully loaded.
160 // Set to true if the request has a main request set.
163 NSString* Description() {
164 NSString* spec = base::SysUTF8ToNSString(url.spec());
165 NSString* status = nil;
167 status = [NSString stringWithFormat:@"\t-- Done -- (%04qu) bytes",
169 } else if (!expected_length) {
170 status = [NSString stringWithFormat:@"\t>> Loading (%04qu) bytes",
173 status = [NSString stringWithFormat:@"\t>> Loading (%04qu/%04qu)",
174 processed, expected_length];
178 if (ssl_info.is_valid()) {
179 NSString* subject = base::SysUTF8ToNSString(
180 ssl_info.cert.get()->subject().GetDisplayName());
181 NSString* issuer = base::SysUTF8ToNSString(
182 ssl_info.cert.get()->issuer().GetDisplayName());
184 ssl = [NSString stringWithFormat:
185 @"\n\t\tcert for '%@' issued by '%@'", subject, issuer];
187 if (!net::IsCertStatusMinorError(ssl_info.cert_status)) {
188 ssl = [NSString stringWithFormat:@"%@ (status: %0xd)",
189 ssl, ssl_info.cert_status];
192 return [NSString stringWithFormat:@"%@\n\t\t%@%@", status, spec, ssl];
195 DISALLOW_COPY_AND_ASSIGN(TrackerCounts);
198 // A SSL carrier is used to transport SSL information to the UI via its
199 // encapsulation in a block. Once the object is constructed all public methods
200 // can be called from any thread safely. This object is designed so it is
201 // instantiated on the IO thread but may be accessed from the UI thread.
202 @interface CRWSSLCarrier : NSObject {
204 scoped_refptr<web::RequestTrackerImpl> tracker_;
205 net::SSLInfo sslInfo_;
207 web::SSLStatus status_;
210 // Designated initializer.
211 - (id)initWithTracker:(web::RequestTrackerImpl*)tracker
212 counts:(const TrackerCounts*)counts;
213 // URL of the request.
215 // Returns a SSLStatus representing the state of the page. This assumes the
216 // target carrier is the main page request.
217 - (const web::SSLStatus&)sslStatus;
218 // Returns a SSLInfo with a reference to the certificate and SSL information.
219 - (const net::SSLInfo&)sslInfo;
220 // Callback method to allow or deny the request from going through.
221 - (void)errorCallback:(BOOL)flag;
222 // Internal method used to build the SSLStatus object. Called from the
223 // initializer to make sure it is invoked on the network thread.
224 - (void)buildSSLStatus;
227 @implementation CRWSSLCarrier
229 - (id)initWithTracker:(web::RequestTrackerImpl*)tracker
230 counts:(const TrackerCounts*)counts {
235 sslInfo_ = counts->ssl_info;
236 [self buildSSLStatus];
245 - (const net::SSLInfo&)sslInfo {
249 - (const web::SSLStatus&)sslStatus {
253 - (void)errorCallback:(BOOL)flag {
254 base::scoped_nsobject<CRWSSLCarrier> scoped([self retain]);
255 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE,
256 base::Bind(&web::RequestTrackerImpl::ErrorCallback,
257 tracker_, scoped, flag));
260 - (void)buildSSLStatus {
261 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
262 if (!sslInfo_.is_valid())
265 status_.cert_id = web::CertStore::GetInstance()->StoreCert(
266 sslInfo_.cert.get(), tracker_->identifier());
268 status_.cert_status = sslInfo_.cert_status;
269 if (status_.cert_status & net::CERT_STATUS_COMMON_NAME_INVALID) {
270 // CAs issue certificates for intranet hosts to everyone. Therefore, we
271 // mark intranet hosts as being non-unique.
272 if (IsIntranetHost(url_.host())) {
273 status_.cert_status |= net::CERT_STATUS_NON_UNIQUE_NAME;
277 status_.security_bits = sslInfo_.security_bits;
278 status_.connection_status = sslInfo_.connection_status;
280 if (tracker_->has_mixed_content()) {
281 // TODO(noyau): In iOS there is no notion of resource type. The insecure
282 // content could be an image (DISPLAYED_INSECURE_CONTENT) or a script
283 // (RAN_INSECURE_CONTENT). The status of the page is different for both, but
284 // there is not enough information from UIWebView to differentiate the two
286 status_.content_status = web::SSLStatus::DISPLAYED_INSECURE_CONTENT;
288 status_.content_status = web::SSLStatus::NORMAL_CONTENT;
291 if (!url_.SchemeIsCryptographic()) {
292 // Should not happen as the sslInfo is valid.
294 status_.security_style = web::SECURITY_STYLE_UNAUTHENTICATED;
295 } else if (net::IsCertStatusError(status_.cert_status) &&
296 !net::IsCertStatusMinorError(status_.cert_status)) {
297 // Minor errors don't lower the security style to
298 // SECURITY_STYLE_AUTHENTICATION_BROKEN.
299 status_.security_style = web::SECURITY_STYLE_AUTHENTICATION_BROKEN;
301 // This page is secure.
302 status_.security_style = web::SECURITY_STYLE_AUTHENTICATED;
306 - (NSString*)description {
307 NSString* sslInfo = @"";
308 if (sslInfo_.is_valid()) {
309 switch (status_.security_style) {
310 case web::SECURITY_STYLE_UNKNOWN:
311 case web::SECURITY_STYLE_UNAUTHENTICATED:
312 sslInfo = @"Unexpected SSL state ";
314 case web::SECURITY_STYLE_AUTHENTICATION_BROKEN:
315 sslInfo = @"Not secure ";
317 case web::SECURITY_STYLE_WARNING:
318 sslInfo = @"Security warning";
320 case web::SECURITY_STYLE_AUTHENTICATED:
321 if (status_.content_status ==
322 web::SSLStatus::DISPLAYED_INSECURE_CONTENT)
325 sslInfo = @"Secure ";
330 NSURL* url = net::NSURLWithGURL(url_);
332 return [NSString stringWithFormat:@"<%@%@>", sslInfo, url];
339 #pragma mark Consumer API
342 scoped_refptr<RequestTrackerImpl>
343 RequestTrackerImpl::CreateTrackerForRequestGroupID(
344 NSString* request_group_id,
345 BrowserState* browser_state,
346 net::URLRequestContextGetter* context_getter,
347 id<CRWRequestTrackerDelegate> delegate) {
348 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
349 DCHECK(request_group_id);
351 scoped_refptr<RequestTrackerImpl> tracker =
352 new RequestTrackerImpl(request_group_id, context_getter, delegate);
354 scoped_refptr<CertificatePolicyCache> policy_cache =
355 BrowserState::GetCertificatePolicyCache(browser_state);
356 DCHECK(policy_cache);
358 // Take care of the IO-thread init.
359 web::WebThread::PostTask(
360 web::WebThread::IO, FROM_HERE,
361 base::Bind(&RequestTrackerImpl::InitOnIOThread, tracker, policy_cache));
362 RegisterTracker(tracker.get(), request_group_id);
366 void RequestTrackerImpl::StartPageLoad(const GURL& url, id user_info) {
367 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
368 base::scoped_nsobject<id> scoped_user_info([user_info retain]);
369 web::WebThread::PostTask(
370 web::WebThread::IO, FROM_HERE,
371 base::Bind(&RequestTrackerImpl::TrimToURL, this, url, scoped_user_info));
374 void RequestTrackerImpl::FinishPageLoad(const GURL& url, bool load_success) {
375 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
376 web::WebThread::PostTask(
377 web::WebThread::IO, FROM_HERE,
378 base::Bind(&RequestTrackerImpl::StopPageLoad, this, url, load_success));
381 void RequestTrackerImpl::HistoryStateChange(const GURL& url) {
382 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
383 web::WebThread::PostTask(
384 web::WebThread::IO, FROM_HERE,
385 base::Bind(&RequestTrackerImpl::HistoryStateChangeToURL, this, url));
388 // Close is called when an owning object (a Tab or something that acts like
389 // it) is done with the RequestTrackerImpl. There may still be queued calls on
390 // the UI thread that will make use of the fields being cleaned-up here; they
391 // must ensure they they operate without crashing with the cleaned-up values.
392 void RequestTrackerImpl::Close() {
393 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
394 // Mark the tracker as closing on the IO thread. Note that because the local
395 // scoped_refptr here retains |this|, we a are guaranteed that destruiction
396 // won't begin until the block completes, and thus |is_closing_| will always
397 // be set before destruction begins.
398 scoped_refptr<RequestTrackerImpl> tracker = this;
399 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE,
401 tracker->is_closing_ = true;
402 tracker->CancelRequests();
405 // Disable the delegate.
407 // The user_info is no longer needed.
409 // Get rid of the stored certificates
410 web::CertStore::GetInstance()->RemoveCertsForGroup(identifier_);
414 void RequestTrackerImpl::RunAfterRequestsCancel(const base::Closure& callback) {
415 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
416 // Post a no-op to the IO thread, and after that has executed, run |callback|.
417 // This ensures that |callback| runs after anything elese queued on the IO
418 // thread, in particular CancelRequest() calls made from closing trackers.
419 web::WebThread::PostTaskAndReply(web::WebThread::IO, FROM_HERE,
420 base::Bind(&base::DoNothing), callback);
424 void RequestTrackerImpl::BlockUntilTrackersShutdown() {
425 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
427 base::AutoLock scoped_lock(*g_waiting_on_io_thread_lock);
428 g_waiting_on_io_thread = true;
430 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE,
431 base::Bind(&StopIOThreadWaiting));
433 // Poll endlessly until the wait flag is unset on the IO thread by
434 // StopIOThreadWaiting().
435 // (Consider instead having a hard time cap, like 100ms or so, after which
436 // we stop blocking. In that case this method would return a boolean
437 // indicating if the wait completed or not).
439 base::AutoLock scoped_lock(*g_waiting_on_io_thread_lock);
440 if (!g_waiting_on_io_thread)
442 // Ensure that other threads have a chance to run even on a single-core
448 #pragma mark Provider API
451 RequestTrackerImpl* RequestTrackerImpl::GetTrackerForRequestGroupID(
452 NSString* request_group_id) {
453 DCHECK(request_group_id);
454 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
455 RequestTrackerImpl* tracker = nullptr;
456 TrackerMap::iterator map_it;
457 pthread_once(&g_once_control, &InitializeGlobals);
459 base::AutoLock scoped_lock(*g_trackers_lock);
460 map_it = g_trackers->find(
461 base::scoped_nsobject<NSString>([request_group_id copy]));
462 if (map_it != g_trackers->end())
463 tracker = map_it->second;
468 net::URLRequestContext* RequestTrackerImpl::GetRequestContext() {
469 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
470 return request_context_getter_->GetURLRequestContext();
473 void RequestTrackerImpl::StartRequest(net::URLRequest* request) {
474 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
475 DCHECK(!counts_by_request_.count(request));
476 DCHECK_EQ(is_for_static_file_requests_, request->url().SchemeIsFile());
478 bool addedRequest = live_requests_.insert(request).second;
479 if (!is_for_static_file_requests_ && addedRequest) {
480 NSString* networkActivityKey = GetNetworkActivityKey();
481 web::WebThread::PostTask(
482 web::WebThread::UI, FROM_HERE,
484 [[CRWNetworkActivityIndicatorManager sharedInstance]
485 startNetworkTaskForGroup:networkActivityKey];
489 if (new_estimate_round_) {
490 // Starting a new estimate round. Ignore the previous requests for the
492 counts_by_request_.clear();
493 estimate_start_index_ = counts_.size();
494 new_estimate_round_ = false;
496 const GURL& url = request->original_url();
497 TrackerCounts* counts = new TrackerCounts(
498 GURLByRemovingRefFromGURL(url), request);
499 counts_.push_back(counts);
500 counts_by_request_[request] = counts;
501 if (page_url_.SchemeIsCryptographic() && !url.SchemeIsCryptographic())
502 has_mixed_content_ = true;
506 void RequestTrackerImpl::CaptureHeaders(net::URLRequest* request) {
507 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
511 if (!request->response_headers())
514 scoped_refptr<net::HttpResponseHeaders> headers(request->response_headers());
515 web::WebThread::PostTask(
516 web::WebThread::UI, FROM_HERE,
517 base::Bind(&RequestTrackerImpl::NotifyResponseHeaders, this, headers,
521 void RequestTrackerImpl::CaptureExpectedLength(const net::URLRequest* request,
523 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
524 if (counts_by_request_.count(request)) {
525 TrackerCounts* counts = counts_by_request_[request];
526 DCHECK(!counts->done);
527 if (length < counts->processed) {
528 // Something is wrong with the estimate. Ignore it.
529 counts->expected_length = 0;
531 counts->expected_length = length;
537 void RequestTrackerImpl::CaptureReceivedBytes(const net::URLRequest* request,
538 uint64_t byte_count) {
539 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
540 if (counts_by_request_.count(request)) {
541 TrackerCounts* counts = counts_by_request_[request];
542 DCHECK(!counts->done);
543 const net::SSLInfo& ssl_info = request->ssl_info();
544 if (ssl_info.is_valid())
545 counts->ssl_info = ssl_info;
546 counts->processed += byte_count;
547 if (counts->expected_length > 0 &&
548 counts->expected_length < counts->processed) {
549 // Something is wrong with the estimate, it is too low. Ignore it.
550 counts->expected_length = 0;
556 void RequestTrackerImpl::StopRequest(net::URLRequest* request) {
557 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
559 int removedRequests = live_requests_.erase(request);
560 if (!is_for_static_file_requests_ && removedRequests > 0) {
561 NSString* networkActivityKey = GetNetworkActivityKey();
562 web::WebThread::PostTask(
563 web::WebThread::UI, FROM_HERE,
565 [[CRWNetworkActivityIndicatorManager sharedInstance]
566 stopNetworkTaskForGroup:networkActivityKey];
570 if (counts_by_request_.count(request)) {
571 StopRedirectedRequest(request);
576 void RequestTrackerImpl::StopRedirectedRequest(net::URLRequest* request) {
577 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
579 int removedRequests = live_requests_.erase(request);
580 if (!is_for_static_file_requests_ && removedRequests > 0) {
581 NSString* networkActivityKey = GetNetworkActivityKey();
582 web::WebThread::PostTask(
583 web::WebThread::UI, FROM_HERE,
585 [[CRWNetworkActivityIndicatorManager sharedInstance]
586 stopNetworkTaskForGroup:networkActivityKey];
590 if (counts_by_request_.count(request)) {
591 TrackerCounts* counts = counts_by_request_[request];
592 DCHECK(!counts->done);
593 const net::SSLInfo& ssl_info = request->ssl_info();
594 if (ssl_info.is_valid())
595 counts->ssl_info = ssl_info;
597 counts_by_request_.erase(request);
601 void RequestTrackerImpl::CaptureCertificatePolicyCache(
602 const net::URLRequest* request,
603 const RequestTracker::SSLCallback& should_continue) {
604 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
605 std::string host = request->url().host();
606 CertPolicy::Judgment judgment = policy_cache_->QueryPolicy(
607 request->ssl_info().cert.get(), host, request->ssl_info().cert_status);
608 if (judgment == CertPolicy::UNKNOWN) {
609 // The request comes from the cache, and has been loaded even though the
610 // policy is UNKNOWN. Display the interstitial page now.
611 OnSSLCertificateError(request, request->ssl_info(), true, should_continue);
615 // Notify the delegate that a judgment has been used.
616 DCHECK(judgment == CertPolicy::ALLOWED);
617 if (counts_by_request_.count(request)) {
618 const net::SSLInfo& ssl_info = request->ssl_info();
619 TrackerCounts* counts = counts_by_request_[request];
620 counts->allowed_by_user = true;
621 if (ssl_info.is_valid())
622 counts->ssl_info = ssl_info;
623 web::WebThread::PostTask(
624 web::WebThread::UI, FROM_HERE,
625 base::Bind(&RequestTrackerImpl::NotifyCertificateUsed, this,
626 ssl_info.cert, host, ssl_info.cert_status));
628 should_continue.Run(true);
631 void RequestTrackerImpl::OnSSLCertificateError(
632 const net::URLRequest* request,
633 const net::SSLInfo& ssl_info,
635 const RequestTracker::SSLCallback& should_continue) {
636 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
637 DCHECK(ssl_info.is_valid());
639 if (counts_by_request_.count(request)) {
640 TrackerCounts* counts = counts_by_request_[request];
642 DCHECK(!counts->done);
643 // Store the ssl error.
644 counts->ssl_info = ssl_info;
645 counts->ssl_callback = should_continue;
646 counts->ssl_judgment =
647 recoverable ? CertPolicy::UNKNOWN : CertPolicy::DENIED;
648 ReevaluateCallbacksForAllCounts();
652 void RequestTrackerImpl::ErrorCallback(CRWSSLCarrier* carrier, bool allow) {
653 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
654 DCHECK(policy_cache_);
657 policy_cache_->AllowCertForHost([carrier sslInfo].cert.get(),
658 [carrier url].host(),
659 [carrier sslInfo].cert_status);
660 ReevaluateCallbacksForAllCounts();
662 current_ssl_error_ = NULL;
665 #pragma mark Client utility methods.
667 // TODO(marq): Convert all internal task-posting to use these.
668 void RequestTrackerImpl::PostUITaskIfOpen(const base::Closure& task) {
669 PostTask(task, web::WebThread::UI);
673 void RequestTrackerImpl::PostUITaskIfOpen(
674 const base::WeakPtr<RequestTracker> tracker,
675 const base::Closure& task) {
678 RequestTrackerImpl* tracker_impl =
679 static_cast<RequestTrackerImpl*>(tracker.get());
680 tracker_impl->PostUITaskIfOpen(task);
683 void RequestTrackerImpl::PostIOTask(const base::Closure& task) {
684 PostTask(task, web::WebThread::IO);
687 void RequestTrackerImpl::ScheduleIOTask(const base::Closure& task) {
688 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
689 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, task);
692 void RequestTrackerImpl::SetCacheModeFromUIThread(
693 RequestTracker::CacheMode mode) {
694 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
695 web::WebThread::PostTask(
696 web::WebThread::IO, FROM_HERE,
697 base::Bind(&RequestTracker::SetCacheMode, this, mode));
700 #pragma mark Private Object Lifecycle API
702 RequestTrackerImpl::RequestTrackerImpl(
703 NSString* request_group_id,
704 net::URLRequestContextGetter* context_getter,
705 id<CRWRequestTrackerDelegate> delegate)
706 : delegate_(delegate),
707 previous_estimate_(0.0f), // Not active by default.
708 estimate_start_index_(0),
709 notification_depth_(0),
710 current_ssl_error_(NULL),
711 has_mixed_content_(false),
713 new_estimate_round_(true),
714 is_for_static_file_requests_([delegate isForStaticFileRequests]),
715 request_context_getter_(context_getter),
716 identifier_(++g_next_request_tracker_id),
717 request_group_id_([request_group_id copy]),
719 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
722 void RequestTrackerImpl::InitOnIOThread(
723 const scoped_refptr<CertificatePolicyCache>& policy_cache) {
724 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
726 DCHECK(policy_cache);
727 policy_cache_ = policy_cache;
730 RequestTrackerImpl::~RequestTrackerImpl() {
733 void RequestTrackerImplTraits::Destruct(const RequestTrackerImpl* t) {
734 // RefCountedThreadSafe assumes we can do all the destruct tasks with a
735 // const pointer, but we actually can't.
736 RequestTrackerImpl* inconstant_t = const_cast<RequestTrackerImpl*>(t);
737 if (web::WebThread::CurrentlyOn(web::WebThread::IO)) {
738 inconstant_t->Destruct();
740 // Use BindBlock rather than Bind to avoid creating another scoped_refpter
741 // to |this|. |inconstant_t| isn't retained by the block, but since this
742 // method is the mechanism by which all RequestTrackerImpl instances are
743 // destroyed, the object inconstant_t points to won't be deleted while
744 // the block is executing (and Destruct() itself will do the deleting).
745 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE,
747 inconstant_t->Destruct();
752 void RequestTrackerImpl::Destruct() {
753 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
756 pthread_once(&g_once_control, &InitializeGlobals);
758 base::AutoLock scoped_lock(*g_trackers_lock);
759 g_trackers->erase(request_group_id_);
761 InvalidateWeakPtrs();
762 // Delete on the UI thread.
763 web::WebThread::PostTask(web::WebThread::UI, FROM_HERE, base::BindBlock(^{
768 #pragma mark Other private methods
769 // TODO(marq): Reorder method implementations to match header and add grouping
772 void RequestTrackerImpl::Notify() {
773 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
776 // Notify() is called asynchronously, it runs later on the same
777 // thread. This is used to collate notifications together, avoiding
778 // blanketing the UI with a stream of information.
779 notification_depth_ += 1;
780 web::WebThread::PostTask(
781 web::WebThread::IO, FROM_HERE,
782 base::Bind(&RequestTrackerImpl::StackNotification, this));
785 void RequestTrackerImpl::StackNotification() {
786 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
790 // There is no point in sending the notification if there is another one
791 // already queued. This queue is processing very lightweight changes and
792 // should be exhausted very easily.
793 --notification_depth_;
794 if (notification_depth_)
799 float estimate = EstimatedProgress();
800 if (estimate != -1.0f) {
801 web::WebThread::PostTask(
802 web::WebThread::UI, FROM_HERE,
803 base::Bind(&RequestTrackerImpl::NotifyUpdatedProgress, this,
809 void RequestTrackerImpl::SSLNotify() {
810 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
815 return; // Nothing yet to notify.
817 if (!page_url_.SchemeIsCryptographic())
820 const GURL page_origin = page_url_.GetOrigin();
821 ScopedVector<TrackerCounts>::iterator it;
822 for (it = counts_.begin(); it != counts_.end(); ++it) {
823 if (!(*it)->ssl_info.is_valid())
824 continue; // No SSL info at this point in time on this tracker.
826 GURL request_origin = (*it)->url.GetOrigin();
827 if (request_origin != page_origin)
828 continue; // Not interesting in the context of the page.
830 base::scoped_nsobject<CRWSSLCarrier> carrier(
831 [[CRWSSLCarrier alloc] initWithTracker:this counts:*it]);
832 web::WebThread::PostTask(
833 web::WebThread::UI, FROM_HERE,
834 base::Bind(&RequestTrackerImpl::NotifyUpdatedSSLStatus, this, carrier));
839 void RequestTrackerImpl::NotifyResponseHeaders(
840 net::HttpResponseHeaders* headers,
841 const GURL& request_url) {
842 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
843 [delegate_ handleResponseHeaders:headers requestUrl:request_url];
846 void RequestTrackerImpl::NotifyCertificateUsed(
847 net::X509Certificate* certificate,
848 const std::string& host,
849 net::CertStatus status) {
850 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
851 [delegate_ certificateUsed:certificate forHost:host status:status];
854 void RequestTrackerImpl::NotifyUpdatedProgress(float estimate) {
855 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
856 [delegate_ updatedProgress:estimate];
859 void RequestTrackerImpl::NotifyClearCertificates() {
860 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
861 [delegate_ clearCertificates];
864 void RequestTrackerImpl::NotifyUpdatedSSLStatus(
865 base::scoped_nsobject<CRWSSLCarrier> carrier) {
866 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
867 [delegate_ updatedSSLStatus:[carrier sslStatus]
868 forPageUrl:[carrier url]
869 userInfo:user_info_];
872 void RequestTrackerImpl::NotifyPresentSSLError(
873 base::scoped_nsobject<CRWSSLCarrier> carrier,
875 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI);
876 [delegate_ presentSSLError:[carrier sslInfo]
877 forSSLStatus:[carrier sslStatus]
879 recoverable:recoverable
880 callback:^(BOOL flag) {
881 [carrier errorCallback:flag && recoverable];
885 void RequestTrackerImpl::EvaluateSSLCallbackForCounts(TrackerCounts* counts) {
886 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
887 DCHECK(policy_cache_);
889 // Ignore non-SSL requests.
890 if (!counts->ssl_info.is_valid())
893 CertPolicy::Judgment judgment =
894 policy_cache_->QueryPolicy(counts->ssl_info.cert.get(),
896 counts->ssl_info.cert_status);
898 if (judgment != CertPolicy::ALLOWED) {
899 // Apply some fine tuning.
900 // TODO(droger): This logic is duplicated from SSLPolicy. Sharing the code
902 switch (net::MapCertStatusToNetError(counts->ssl_info.cert_status)) {
903 case net::ERR_CERT_NO_REVOCATION_MECHANISM:
904 // Ignore this error.
905 judgment = CertPolicy::ALLOWED;
907 case net::ERR_CERT_UNABLE_TO_CHECK_REVOCATION:
908 // We ignore this error but it will show a warning status in the
910 judgment = CertPolicy::ALLOWED;
912 case net::ERR_CERT_CONTAINS_ERRORS:
913 case net::ERR_CERT_REVOKED:
914 case net::ERR_CERT_INVALID:
915 case net::ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY:
916 case net::ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN:
917 judgment = CertPolicy::DENIED;
919 case net::ERR_CERT_COMMON_NAME_INVALID:
920 case net::ERR_CERT_DATE_INVALID:
921 case net::ERR_CERT_AUTHORITY_INVALID:
922 case net::ERR_CERT_WEAK_SIGNATURE_ALGORITHM:
923 case net::ERR_CERT_WEAK_KEY:
924 case net::ERR_CERT_NAME_CONSTRAINT_VIOLATION:
925 case net::ERR_CERT_VALIDITY_TOO_LONG:
926 // Nothing. If DENIED it will stay denied. If UNKNOWN it will be
927 // shown to the user for decision.
931 judgment = CertPolicy::DENIED;
936 counts->ssl_judgment = judgment;
939 case CertPolicy::UNKNOWN:
940 case CertPolicy::DENIED:
941 // Simply cancel the request.
942 CancelRequestForCounts(counts);
944 case CertPolicy::ALLOWED:
945 counts->ssl_callback.Run(YES);
946 counts->ssl_callback = base::Bind(&DoNothing);
950 // For now simply cancel the request.
951 CancelRequestForCounts(counts);
956 void RequestTrackerImpl::ReevaluateCallbacksForAllCounts() {
957 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
961 ScopedVector<TrackerCounts>::iterator it;
962 for (it = counts_.begin(); it != counts_.end(); ++it) {
963 // Check if the value hasn't changed via a user action.
964 if ((*it)->ssl_judgment == CertPolicy::UNKNOWN)
965 EvaluateSSLCallbackForCounts(*it);
967 CertPolicy::Judgment judgment = (*it)->ssl_judgment;
968 if (judgment == CertPolicy::ALLOWED)
971 // SSL errors on subrequests are simply ignored. The call to
972 // EvaluateSSLCallbackForCounts() cancelled the request and nothing will
974 if ((*it)->is_subrequest)
977 if (!current_ssl_error_) {
978 // For the UNKNOWN and DENIED state the information should be pushed to
979 // the delegate. But only one at a time.
980 current_ssl_error_ = (*it);
981 base::scoped_nsobject<CRWSSLCarrier> carrier([[CRWSSLCarrier alloc]
982 initWithTracker:this counts:current_ssl_error_]);
983 web::WebThread::PostTask(
984 web::WebThread::UI, FROM_HERE,
985 base::Bind(&RequestTrackerImpl::NotifyPresentSSLError, this, carrier,
986 judgment == CertPolicy::UNKNOWN));
991 void RequestTrackerImpl::CancelRequestForCounts(TrackerCounts* counts) {
992 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
993 // Cancel the request.
995 counts_by_request_.erase(counts->request);
996 counts->ssl_callback.Run(NO);
997 counts->ssl_callback = base::Bind(&DoNothing);
1001 PageCounts RequestTrackerImpl::pageCounts() {
1002 DCHECK_GE(counts_.size(), estimate_start_index_);
1004 PageCounts page_counts;
1006 ScopedVector<TrackerCounts>::iterator it;
1007 for (it = counts_.begin() + estimate_start_index_;
1008 it != counts_.end(); ++it) {
1010 uint64_t size = (*it)->processed;
1011 page_counts.finished += 1;
1012 page_counts.finished_bytes += size;
1013 if (page_counts.largest_byte_size_known < size) {
1014 page_counts.largest_byte_size_known = size;
1017 page_counts.unfinished += 1;
1018 if ((*it)->expected_length) {
1019 uint64_t size = (*it)->expected_length;
1020 page_counts.unfinished_estimate_bytes_done += (*it)->processed;
1021 page_counts.unfinished_estimated_bytes_left += size;
1022 if (page_counts.largest_byte_size_known < size) {
1023 page_counts.largest_byte_size_known = size;
1026 page_counts.unfinished_no_estimate += 1;
1027 page_counts.unfinished_no_estimate_bytes_done += (*it)->processed;
1035 float RequestTrackerImpl::EstimatedProgress() {
1036 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
1038 const PageCounts page_counts = pageCounts();
1040 // Nothing in progress and the last time was the same.
1041 if (!page_counts.unfinished && previous_estimate_ == 0.0f)
1045 if (previous_estimate_ == 0.0f) {
1047 previous_estimate_ = 0.1f;
1048 return previous_estimate_; // Return the just started status.
1051 // The very simple case where everything is probably done and dusted.
1052 if (!page_counts.unfinished) {
1053 // Add 60%, and return. Another task is going to finish this.
1054 float bump = (1.0f - previous_estimate_) * 0.6f;
1055 previous_estimate_ += bump;
1056 return previous_estimate_;
1059 // Calculate some ratios.
1060 // First the ratio of the finished vs the unfinished counts of resources
1062 float unfinishedRatio =
1063 static_cast<float>(page_counts.finished) /
1064 static_cast<float>(page_counts.unfinished + page_counts.finished);
1066 // The ratio of bytes left vs bytes already downloaded for the resources where
1067 // no estimates of final size are known. For this ratio it is assumed the size
1068 // of a resource not downloaded yet is the maximum size of all the resources
1070 float noEstimateRatio = (!page_counts.unfinished_no_estimate_bytes_done) ?
1072 static_cast<float>(page_counts.unfinished_no_estimate *
1073 page_counts.largest_byte_size_known) /
1074 static_cast<float>(page_counts.finished_bytes +
1075 page_counts.unfinished_no_estimate_bytes_done);
1077 // The ratio of bytes left vs bytes already downloaded for the resources with
1078 // available estimated size.
1079 float estimateRatio = (!page_counts.unfinished_estimated_bytes_left) ?
1081 static_cast<float>(page_counts.unfinished_estimate_bytes_done) /
1082 static_cast<float>(page_counts.unfinished_estimate_bytes_done +
1083 page_counts.unfinished_estimated_bytes_left);
1085 // Reassemble all of this.
1087 0.1f + // Minimum value.
1088 unfinishedRatio * 0.6f +
1089 estimateRatio * 0.3f;
1091 if (previous_estimate_ >= total)
1094 // 10% of what's left.
1095 float maxBump = (1.0f - previous_estimate_) / 10.0f;
1096 // total is greater than previous estimate, need to bump the estimate up.
1097 if ((previous_estimate_ + maxBump) > total) {
1098 // Less than a 10% bump, bump to the new value.
1099 previous_estimate_ = total;
1101 // Just bump by 10% toward the total.
1102 previous_estimate_ += maxBump;
1105 return previous_estimate_;
1108 void RequestTrackerImpl::RecomputeMixedContent(
1109 const TrackerCounts* split_position) {
1110 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
1111 // Check if the mixed content before trimming was correct.
1112 if (page_url_.SchemeIsCryptographic() && has_mixed_content_) {
1113 bool old_url_has_mixed_content = false;
1114 const GURL origin = page_url_.GetOrigin();
1115 ScopedVector<TrackerCounts>::iterator it = counts_.begin();
1116 while (it != counts_.end() && *it != split_position) {
1117 if (!(*it)->url.SchemeIsCryptographic() &&
1118 origin == (*it)->first_party_for_cookies_origin) {
1119 old_url_has_mixed_content = true;
1124 if (!old_url_has_mixed_content) {
1125 // We marked the previous page with incorrect data about its mixed
1126 // content. Turns out that the elements that triggered that condition
1127 // where in fact in a subsequent page. Duh.
1128 // Resend a notification for the |page_url_| informing the upper layer
1129 // that the mixed content was a red herring.
1130 has_mixed_content_ = false;
1136 void RequestTrackerImpl::RecomputeCertificatePolicy(
1137 const TrackerCounts* splitPosition) {
1138 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
1139 // Clear the judgments for the old URL.
1140 web::WebThread::PostTask(
1141 web::WebThread::UI, FROM_HERE,
1142 base::Bind(&RequestTrackerImpl::NotifyClearCertificates, this));
1143 // Report judgements for the new URL.
1144 ScopedVector<TrackerCounts>::const_reverse_iterator it;
1145 for (it = counts_.rbegin(); it != counts_.rend(); ++it) {
1146 TrackerCounts* counts = *it;
1147 if (counts->allowed_by_user) {
1148 std::string host = counts->url.host();
1149 web::WebThread::PostTask(
1150 web::WebThread::UI, FROM_HERE,
1151 base::Bind(&RequestTrackerImpl::NotifyCertificateUsed, this,
1152 counts->ssl_info.cert, host,
1153 counts->ssl_info.cert_status));
1155 if (counts == splitPosition)
1160 void RequestTrackerImpl::HistoryStateChangeToURL(const GURL& full_url) {
1161 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
1162 GURL url = GURLByRemovingRefFromGURL(full_url);
1165 web::history_state_util::IsHistoryStateChangeValid(url, page_url_)) {
1170 void RequestTrackerImpl::TrimToURL(const GURL& full_url, id user_info) {
1171 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
1173 GURL url = GURLByRemovingRefFromGURL(full_url);
1175 // Locate the request with this url, if present.
1176 bool new_url_has_mixed_content = false;
1177 bool url_scheme_is_secure = url.SchemeIsCryptographic();
1178 ScopedVector<TrackerCounts>::const_reverse_iterator rit = counts_.rbegin();
1179 while (rit != counts_.rend() && (*rit)->url != url) {
1180 if (url_scheme_is_secure && !(*rit)->url.SchemeIsCryptographic() &&
1181 (*rit)->first_party_for_cookies_origin == url.GetOrigin()) {
1182 new_url_has_mixed_content = true;
1187 // |split_position| will be set to the count for the passed url if it exists.
1188 TrackerCounts* split_position = NULL;
1189 if (rit != counts_.rend()) {
1190 split_position = (*rit);
1192 // The URL was not found, everything will be trimmed. The mixed content
1193 // calculation is invalid.
1194 new_url_has_mixed_content = false;
1196 // In the case of a page loaded via a HTML5 manifest there is no page
1197 // boundary to be found. However the latest count is a request for a
1198 // manifest. This tries to detect this peculiar case.
1199 // This is important as if this request for the manifest is on the same
1200 // domain as the page itself this will allow retrieval of the SSL
1202 if (url_scheme_is_secure && counts_.size()) {
1203 TrackerCounts* back = counts_.back();
1204 const GURL& back_url = back->url;
1205 if (back_url.SchemeIsCryptographic() &&
1206 back_url.GetOrigin() == url.GetOrigin() && !back->is_subrequest) {
1207 split_position = back;
1211 RecomputeMixedContent(split_position);
1212 RecomputeCertificatePolicy(split_position);
1214 // Trim up to that element.
1215 ScopedVector<TrackerCounts>::iterator it = counts_.begin();
1216 while (it != counts_.end() && *it != split_position) {
1218 // This is for an unfinished request on a previous page. We do not care
1219 // about those anymore. Cancel the request.
1220 if ((*it)->ssl_judgment == CertPolicy::UNKNOWN)
1221 CancelRequestForCounts(*it);
1222 counts_by_request_.erase((*it)->request);
1224 it = counts_.erase(it);
1227 has_mixed_content_ = new_url_has_mixed_content;
1229 user_info_.reset([user_info retain]);
1230 estimate_start_index_ = 0;
1232 previous_estimate_ = 0.0f;
1233 new_estimate_round_ = true;
1234 ReevaluateCallbacksForAllCounts();
1238 void RequestTrackerImpl::StopPageLoad(const GURL& url, bool load_success) {
1239 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
1240 DCHECK(page_url_ == GURLByRemovingRefFromGURL(url));
1241 is_loading_ = false;
1244 #pragma mark Internal utilities for task posting
1246 void RequestTrackerImpl::PostTask(const base::Closure& task,
1247 web::WebThread::ID thread) {
1248 // Absolute sanity test: |thread| is one of {UI, IO}
1249 DCHECK(thread == web::WebThread::UI || thread == web::WebThread::IO);
1250 // Check that we're on the counterpart thread to the one we're posting to.
1251 DCHECK_CURRENTLY_ON_WEB_THREAD(
1252 thread == web::WebThread::IO ? web::WebThread::UI : web::WebThread::IO);
1253 // Don't post if the tracker is closing and we're on the IO thread.
1254 // (there should be no way to call anything from the UI thread if
1255 // the tracker is closing).
1256 if (is_closing_ && web::WebThread::CurrentlyOn(web::WebThread::IO))
1258 web::WebThread::PostTask(thread, FROM_HERE, task);
1261 #pragma mark Other internal methods.
1263 NSString* RequestTrackerImpl::UnsafeDescription() {
1264 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
1266 NSMutableArray* urls = [NSMutableArray array];
1267 ScopedVector<TrackerCounts>::iterator it;
1268 for (it = counts_.begin(); it != counts_.end(); ++it)
1269 [urls addObject:(*it)->Description()];
1271 return [NSString stringWithFormat:@"RequestGroupID %@\n%@\n%@",
1272 request_group_id_.get(),
1273 net::NSURLWithGURL(page_url_),
1274 [urls componentsJoinedByString:@"\n"]];
1277 NSString* RequestTrackerImpl::GetNetworkActivityKey() {
1279 stringWithFormat:@"RequestTrackerImpl.NetworkActivityIndicatorKey.%@",
1280 request_group_id_.get()];
1283 void RequestTrackerImpl::CancelRequests() {
1284 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO);
1285 std::set<net::URLRequest*>::iterator it;
1286 // TODO(droger): When canceling the request, we should in theory make sure
1287 // that the NSURLProtocol client method |didFailWithError| is called,
1288 // otherwise the iOS system may wait indefinitely for the request to complete.
1289 // However, as we currently only cancel the requests when closing a tab, the
1290 // requests are all canceled by the system shortly after and nothing bad
1292 for (it = live_requests_.begin(); it != live_requests_.end(); ++it)
1295 int removedRequests = live_requests_.size();
1296 live_requests_.clear();
1297 if (!is_for_static_file_requests_ && removedRequests > 0) {
1298 NSString* networkActivityKey = GetNetworkActivityKey();
1299 web::WebThread::PostTask(
1300 web::WebThread::UI, FROM_HERE,
1302 [[CRWNetworkActivityIndicatorManager sharedInstance]
1303 clearNetworkTasksForGroup:networkActivityKey];
1308 void RequestTrackerImpl::SetCertificatePolicyCacheForTest(
1309 web::CertificatePolicyCache* cache) {
1310 policy_cache_ = cache;