OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 5 #include "ios/web/net/request_tracker_impl.h" |
| 6 |
| 7 #include <pthread.h> |
| 8 |
| 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" |
| 32 |
| 33 namespace { |
| 34 |
| 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; |
| 40 } |
| 41 }; |
| 42 |
| 43 struct HashNSString { |
| 44 size_t operator()(const base::scoped_nsobject<NSString>& s) const { |
| 45 return [s hash]; |
| 46 } |
| 47 }; |
| 48 |
| 49 // A map of all RequestTrackerImpls for tabs that are: |
| 50 // * Currently open |
| 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; |
| 59 |
| 60 TrackerMap* g_trackers = NULL; |
| 61 base::Lock* g_trackers_lock = NULL; |
| 62 pthread_once_t g_once_control = PTHREAD_ONCE_INIT; |
| 63 |
| 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; |
| 76 } |
| 77 |
| 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 |
| 80 // threads). |
| 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; |
| 85 } |
| 86 |
| 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; |
| 90 |
| 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 = '.'; |
| 95 |
| 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; |
| 99 } |
| 100 |
| 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); |
| 105 { |
| 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; |
| 110 } |
| 111 } |
| 112 |
| 113 // Empty callback. |
| 114 void DoNothing(bool flag) {} |
| 115 |
| 116 } // namespace |
| 117 |
| 118 // The structure used to gather the information about the resources loaded. |
| 119 struct TrackerCounts { |
| 120 public: |
| 121 TrackerCounts(const GURL& tracked_url, const net::URLRequest* tracked_request) |
| 122 : url(tracked_url), |
| 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), |
| 129 expected_length(0), |
| 130 processed(0), |
| 131 done(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(); |
| 135 }; |
| 136 |
| 137 // The resource url. |
| 138 const GURL url; |
| 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 |
| 141 // content. |
| 142 const GURL first_party_for_cookies_origin; |
| 143 // The request associated with this struct. As a void* to prevent access from |
| 144 // the wrong thread. |
| 145 const void* request; |
| 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. |
| 157 uint64_t processed; |
| 158 // Set to true is the resource is fully loaded. |
| 159 bool done; |
| 160 // Set to true if the request has a main request set. |
| 161 bool is_subrequest; |
| 162 |
| 163 NSString* Description() { |
| 164 NSString* spec = base::SysUTF8ToNSString(url.spec()); |
| 165 NSString* status = nil; |
| 166 if (done) { |
| 167 status = [NSString stringWithFormat:@"\t-- Done -- (%04qu) bytes", |
| 168 processed]; |
| 169 } else if (!expected_length) { |
| 170 status = [NSString stringWithFormat:@"\t>> Loading (%04qu) bytes", |
| 171 processed]; |
| 172 } else { |
| 173 status = [NSString stringWithFormat:@"\t>> Loading (%04qu/%04qu)", |
| 174 processed, expected_length]; |
| 175 } |
| 176 |
| 177 NSString* ssl = @""; |
| 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()); |
| 183 |
| 184 ssl = [NSString stringWithFormat: |
| 185 @"\n\t\tcert for '%@' issued by '%@'", subject, issuer]; |
| 186 |
| 187 if (!net::IsCertStatusMinorError(ssl_info.cert_status)) { |
| 188 ssl = [NSString stringWithFormat:@"%@ (status: %0xd)", |
| 189 ssl, ssl_info.cert_status]; |
| 190 } |
| 191 } |
| 192 return [NSString stringWithFormat:@"%@\n\t\t%@%@", status, spec, ssl]; |
| 193 } |
| 194 |
| 195 DISALLOW_COPY_AND_ASSIGN(TrackerCounts); |
| 196 }; |
| 197 |
| 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 { |
| 203 @private |
| 204 scoped_refptr<web::RequestTrackerImpl> tracker_; |
| 205 net::SSLInfo sslInfo_; |
| 206 GURL url_; |
| 207 web::SSLStatus status_; |
| 208 } |
| 209 |
| 210 // Designated initializer. |
| 211 - (id)initWithTracker:(web::RequestTrackerImpl*)tracker |
| 212 counts:(const TrackerCounts*)counts; |
| 213 // URL of the request. |
| 214 - (const GURL&)url; |
| 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; |
| 225 @end |
| 226 |
| 227 @implementation CRWSSLCarrier |
| 228 |
| 229 - (id)initWithTracker:(web::RequestTrackerImpl*)tracker |
| 230 counts:(const TrackerCounts*)counts { |
| 231 self = [super init]; |
| 232 if (self) { |
| 233 tracker_ = tracker; |
| 234 url_ = counts->url; |
| 235 sslInfo_ = counts->ssl_info; |
| 236 [self buildSSLStatus]; |
| 237 } |
| 238 return self; |
| 239 } |
| 240 |
| 241 - (const GURL&)url { |
| 242 return url_; |
| 243 } |
| 244 |
| 245 - (const net::SSLInfo&)sslInfo { |
| 246 return sslInfo_; |
| 247 } |
| 248 |
| 249 - (const web::SSLStatus&)sslStatus { |
| 250 return status_; |
| 251 } |
| 252 |
| 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)); |
| 258 } |
| 259 |
| 260 - (void)buildSSLStatus { |
| 261 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 262 if (!sslInfo_.is_valid()) |
| 263 return; |
| 264 |
| 265 status_.cert_id = web::CertStore::GetInstance()->StoreCert( |
| 266 sslInfo_.cert.get(), tracker_->identifier()); |
| 267 |
| 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; |
| 274 } |
| 275 } |
| 276 |
| 277 status_.security_bits = sslInfo_.security_bits; |
| 278 status_.connection_status = sslInfo_.connection_status; |
| 279 |
| 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 |
| 285 // cases. |
| 286 status_.content_status = web::SSLStatus::DISPLAYED_INSECURE_CONTENT; |
| 287 } else { |
| 288 status_.content_status = web::SSLStatus::NORMAL_CONTENT; |
| 289 } |
| 290 |
| 291 if (!url_.SchemeIsSecure()) { |
| 292 // Should not happen as the sslInfo is valid. |
| 293 NOTREACHED(); |
| 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; |
| 300 } else { |
| 301 // This page is secure. |
| 302 status_.security_style = web::SECURITY_STYLE_AUTHENTICATED; |
| 303 } |
| 304 } |
| 305 |
| 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 "; |
| 313 break; |
| 314 case web::SECURITY_STYLE_AUTHENTICATION_BROKEN: |
| 315 sslInfo = @"Not secure "; |
| 316 break; |
| 317 case web::SECURITY_STYLE_AUTHENTICATED: |
| 318 if (status_.content_status == |
| 319 web::SSLStatus::DISPLAYED_INSECURE_CONTENT) |
| 320 sslInfo = @"Mixed "; |
| 321 else |
| 322 sslInfo = @"Secure "; |
| 323 break; |
| 324 } |
| 325 } |
| 326 |
| 327 NSURL* url = net::NSURLWithGURL(url_); |
| 328 |
| 329 return [NSString stringWithFormat:@"<%@%@>", sslInfo, url]; |
| 330 } |
| 331 |
| 332 @end |
| 333 |
| 334 namespace web { |
| 335 |
| 336 #pragma mark Consumer API |
| 337 |
| 338 // static |
| 339 scoped_refptr<RequestTrackerImpl> |
| 340 RequestTrackerImpl::CreateTrackerForRequestGroupID( |
| 341 NSString* request_group_id, |
| 342 BrowserState* browser_state, |
| 343 net::URLRequestContextGetter* context_getter, |
| 344 id<CRWRequestTrackerDelegate> delegate) { |
| 345 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 346 DCHECK(request_group_id); |
| 347 |
| 348 scoped_refptr<RequestTrackerImpl> tracker = |
| 349 new RequestTrackerImpl(request_group_id, context_getter, delegate); |
| 350 |
| 351 scoped_refptr<CertificatePolicyCache> policy_cache = |
| 352 BrowserState::GetCertificatePolicyCache(browser_state); |
| 353 DCHECK(policy_cache); |
| 354 |
| 355 // Take care of the IO-thread init. |
| 356 web::WebThread::PostTask( |
| 357 web::WebThread::IO, FROM_HERE, |
| 358 base::Bind(&RequestTrackerImpl::InitOnIOThread, tracker, policy_cache)); |
| 359 RegisterTracker(tracker.get(), request_group_id); |
| 360 return tracker; |
| 361 } |
| 362 |
| 363 void RequestTrackerImpl::StartPageLoad(const GURL& url, id user_info) { |
| 364 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 365 base::scoped_nsobject<id> scoped_user_info([user_info retain]); |
| 366 web::WebThread::PostTask( |
| 367 web::WebThread::IO, FROM_HERE, |
| 368 base::Bind(&RequestTrackerImpl::TrimToURL, this, url, scoped_user_info)); |
| 369 } |
| 370 |
| 371 void RequestTrackerImpl::FinishPageLoad(const GURL& url, bool load_success) { |
| 372 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 373 web::WebThread::PostTask( |
| 374 web::WebThread::IO, FROM_HERE, |
| 375 base::Bind(&RequestTrackerImpl::StopPageLoad, this, url, load_success)); |
| 376 } |
| 377 |
| 378 void RequestTrackerImpl::HistoryStateChange(const GURL& url) { |
| 379 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 380 web::WebThread::PostTask( |
| 381 web::WebThread::IO, FROM_HERE, |
| 382 base::Bind(&RequestTrackerImpl::HistoryStateChangeToURL, this, url)); |
| 383 } |
| 384 |
| 385 // Close is called when an owning object (a Tab or something that acts like |
| 386 // it) is done with the RequestTrackerImpl. There may still be queued calls on |
| 387 // the UI thread that will make use of the fields being cleaned-up here; they |
| 388 // must ensure they they operate without crashing with the cleaned-up values. |
| 389 void RequestTrackerImpl::Close() { |
| 390 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 391 // Mark the tracker as closing on the IO thread. Note that because the local |
| 392 // scoped_refptr here retains |this|, we a are guaranteed that destruiction |
| 393 // won't begin until the block completes, and thus |is_closing_| will always |
| 394 // be set before destruction begins. |
| 395 scoped_refptr<RequestTrackerImpl> tracker = this; |
| 396 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, |
| 397 base::BindBlock(^{ |
| 398 tracker->is_closing_ = true; |
| 399 tracker->CancelRequests(); |
| 400 })); |
| 401 |
| 402 // Disable the delegate. |
| 403 delegate_ = nil; |
| 404 // The user_info is no longer needed. |
| 405 user_info_.reset(); |
| 406 // Get rid of the stored certificates |
| 407 web::CertStore::GetInstance()->RemoveCertsForGroup(identifier_); |
| 408 } |
| 409 |
| 410 // static |
| 411 void RequestTrackerImpl::RunAfterRequestsCancel(const base::Closure& callback) { |
| 412 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 413 // Post a no-op to the IO thread, and after that has executed, run |callback|. |
| 414 // This ensures that |callback| runs after anything elese queued on the IO |
| 415 // thread, in particular CancelRequest() calls made from closing trackers. |
| 416 web::WebThread::PostTaskAndReply(web::WebThread::IO, FROM_HERE, |
| 417 base::Bind(&base::DoNothing), callback); |
| 418 } |
| 419 |
| 420 // static |
| 421 void RequestTrackerImpl::BlockUntilTrackersShutdown() { |
| 422 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 423 { |
| 424 base::AutoLock scoped_lock(*g_waiting_on_io_thread_lock); |
| 425 g_waiting_on_io_thread = true; |
| 426 } |
| 427 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, |
| 428 base::Bind(&StopIOThreadWaiting)); |
| 429 |
| 430 // Poll endlessly until the wait flag is unset on the IO thread by |
| 431 // StopIOThreadWaiting(). |
| 432 // (Consider instead having a hard time cap, like 100ms or so, after which |
| 433 // we stop blocking. In that case this method would return a boolean |
| 434 // indicating if the wait completed or not). |
| 435 while (1) { |
| 436 base::AutoLock scoped_lock(*g_waiting_on_io_thread_lock); |
| 437 if (!g_waiting_on_io_thread) |
| 438 return; |
| 439 // Ensure that other threads have a chance to run even on a single-core |
| 440 // devices. |
| 441 pthread_yield_np(); |
| 442 } |
| 443 } |
| 444 |
| 445 #pragma mark Provider API |
| 446 |
| 447 // static |
| 448 RequestTrackerImpl* RequestTrackerImpl::GetTrackerForRequestGroupID( |
| 449 NSString* request_group_id) { |
| 450 DCHECK(request_group_id); |
| 451 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 452 RequestTrackerImpl* tracker = nullptr; |
| 453 TrackerMap::iterator map_it; |
| 454 pthread_once(&g_once_control, &InitializeGlobals); |
| 455 { |
| 456 base::AutoLock scoped_lock(*g_trackers_lock); |
| 457 map_it = g_trackers->find( |
| 458 base::scoped_nsobject<NSString>([request_group_id copy])); |
| 459 if (map_it != g_trackers->end()) |
| 460 tracker = map_it->second; |
| 461 } |
| 462 return tracker; |
| 463 } |
| 464 |
| 465 net::URLRequestContext* RequestTrackerImpl::GetRequestContext() { |
| 466 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 467 return request_context_getter_->GetURLRequestContext(); |
| 468 } |
| 469 |
| 470 void RequestTrackerImpl::StartRequest(net::URLRequest* request) { |
| 471 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 472 DCHECK(!counts_by_request_.count(request)); |
| 473 DCHECK_EQ(is_for_static_file_requests_, request->url().SchemeIsFile()); |
| 474 |
| 475 bool addedRequest = live_requests_.insert(request).second; |
| 476 if (!is_for_static_file_requests_ && addedRequest) { |
| 477 NSString* networkActivityKey = GetNetworkActivityKey(); |
| 478 web::WebThread::PostTask( |
| 479 web::WebThread::UI, FROM_HERE, |
| 480 base::BindBlock(^{ |
| 481 [[CRWNetworkActivityIndicatorManager sharedInstance] |
| 482 startNetworkTaskForGroup:networkActivityKey]; |
| 483 })); |
| 484 } |
| 485 |
| 486 if (new_estimate_round_) { |
| 487 // Starting a new estimate round. Ignore the previous requests for the |
| 488 // calculation. |
| 489 counts_by_request_.clear(); |
| 490 estimate_start_index_ = counts_.size(); |
| 491 new_estimate_round_ = false; |
| 492 } |
| 493 const GURL& url = request->original_url(); |
| 494 TrackerCounts* counts = new TrackerCounts( |
| 495 GURLByRemovingRefFromGURL(url), request); |
| 496 counts_.push_back(counts); |
| 497 counts_by_request_[request] = counts; |
| 498 if (page_url_.SchemeIsSecure() && !url.SchemeIsSecure()) |
| 499 has_mixed_content_ = true; |
| 500 Notify(); |
| 501 } |
| 502 |
| 503 void RequestTrackerImpl::CaptureHeaders(net::URLRequest* request) { |
| 504 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 505 if (is_closing_) |
| 506 return; |
| 507 |
| 508 if (!request->response_headers()) |
| 509 return; |
| 510 |
| 511 scoped_refptr<net::HttpResponseHeaders> headers(request->response_headers()); |
| 512 web::WebThread::PostTask( |
| 513 web::WebThread::UI, FROM_HERE, |
| 514 base::Bind(&RequestTrackerImpl::NotifyResponseHeaders, this, headers, |
| 515 request->url())); |
| 516 } |
| 517 |
| 518 void RequestTrackerImpl::CaptureExpectedLength(const net::URLRequest* request, |
| 519 uint64_t length) { |
| 520 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 521 if (counts_by_request_.count(request)) { |
| 522 TrackerCounts* counts = counts_by_request_[request]; |
| 523 DCHECK(!counts->done); |
| 524 if (length < counts->processed) { |
| 525 // Something is wrong with the estimate. Ignore it. |
| 526 counts->expected_length = 0; |
| 527 } else { |
| 528 counts->expected_length = length; |
| 529 } |
| 530 Notify(); |
| 531 } |
| 532 } |
| 533 |
| 534 void RequestTrackerImpl::CaptureReceivedBytes(const net::URLRequest* request, |
| 535 uint64_t byte_count) { |
| 536 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 537 if (counts_by_request_.count(request)) { |
| 538 TrackerCounts* counts = counts_by_request_[request]; |
| 539 DCHECK(!counts->done); |
| 540 const net::SSLInfo& ssl_info = request->ssl_info(); |
| 541 if (ssl_info.is_valid()) |
| 542 counts->ssl_info = ssl_info; |
| 543 counts->processed += byte_count; |
| 544 if (counts->expected_length > 0 && |
| 545 counts->expected_length < counts->processed) { |
| 546 // Something is wrong with the estimate, it is too low. Ignore it. |
| 547 counts->expected_length = 0; |
| 548 } |
| 549 Notify(); |
| 550 } |
| 551 } |
| 552 |
| 553 void RequestTrackerImpl::StopRequest(net::URLRequest* request) { |
| 554 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 555 |
| 556 int removedRequests = live_requests_.erase(request); |
| 557 if (!is_for_static_file_requests_ && removedRequests > 0) { |
| 558 NSString* networkActivityKey = GetNetworkActivityKey(); |
| 559 web::WebThread::PostTask( |
| 560 web::WebThread::UI, FROM_HERE, |
| 561 base::BindBlock(^{ |
| 562 [[CRWNetworkActivityIndicatorManager sharedInstance] |
| 563 stopNetworkTaskForGroup:networkActivityKey]; |
| 564 })); |
| 565 } |
| 566 |
| 567 if (counts_by_request_.count(request)) { |
| 568 StopRedirectedRequest(request); |
| 569 Notify(); |
| 570 } |
| 571 } |
| 572 |
| 573 void RequestTrackerImpl::StopRedirectedRequest(net::URLRequest* request) { |
| 574 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 575 |
| 576 int removedRequests = live_requests_.erase(request); |
| 577 if (!is_for_static_file_requests_ && removedRequests > 0) { |
| 578 NSString* networkActivityKey = GetNetworkActivityKey(); |
| 579 web::WebThread::PostTask( |
| 580 web::WebThread::UI, FROM_HERE, |
| 581 base::BindBlock(^{ |
| 582 [[CRWNetworkActivityIndicatorManager sharedInstance] |
| 583 stopNetworkTaskForGroup:networkActivityKey]; |
| 584 })); |
| 585 } |
| 586 |
| 587 if (counts_by_request_.count(request)) { |
| 588 TrackerCounts* counts = counts_by_request_[request]; |
| 589 DCHECK(!counts->done); |
| 590 const net::SSLInfo& ssl_info = request->ssl_info(); |
| 591 if (ssl_info.is_valid()) |
| 592 counts->ssl_info = ssl_info; |
| 593 counts->done = true; |
| 594 counts_by_request_.erase(request); |
| 595 } |
| 596 } |
| 597 |
| 598 void RequestTrackerImpl::CaptureCertificatePolicyCache( |
| 599 const net::URLRequest* request, |
| 600 const RequestTracker::SSLCallback& should_continue) { |
| 601 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 602 std::string host = request->url().host(); |
| 603 CertPolicy::Judgment judgment = policy_cache_->QueryPolicy( |
| 604 request->ssl_info().cert.get(), host, request->ssl_info().cert_status); |
| 605 if (judgment == CertPolicy::UNKNOWN) { |
| 606 // The request comes from the cache, and has been loaded even though the |
| 607 // policy is UNKNOWN. Display the interstitial page now. |
| 608 OnSSLCertificateError(request, request->ssl_info(), true, should_continue); |
| 609 return; |
| 610 } |
| 611 |
| 612 // Notify the delegate that a judgment has been used. |
| 613 DCHECK(judgment == CertPolicy::ALLOWED); |
| 614 if (counts_by_request_.count(request)) { |
| 615 const net::SSLInfo& ssl_info = request->ssl_info(); |
| 616 TrackerCounts* counts = counts_by_request_[request]; |
| 617 counts->allowed_by_user = true; |
| 618 if (ssl_info.is_valid()) |
| 619 counts->ssl_info = ssl_info; |
| 620 web::WebThread::PostTask( |
| 621 web::WebThread::UI, FROM_HERE, |
| 622 base::Bind(&RequestTrackerImpl::NotifyCertificateUsed, this, |
| 623 ssl_info.cert, host, ssl_info.cert_status)); |
| 624 } |
| 625 should_continue.Run(true); |
| 626 } |
| 627 |
| 628 void RequestTrackerImpl::OnSSLCertificateError( |
| 629 const net::URLRequest* request, |
| 630 const net::SSLInfo& ssl_info, |
| 631 bool recoverable, |
| 632 const RequestTracker::SSLCallback& should_continue) { |
| 633 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 634 DCHECK(ssl_info.is_valid()); |
| 635 |
| 636 if (counts_by_request_.count(request)) { |
| 637 TrackerCounts* counts = counts_by_request_[request]; |
| 638 |
| 639 DCHECK(!counts->done); |
| 640 // Store the ssl error. |
| 641 counts->ssl_info = ssl_info; |
| 642 counts->ssl_callback = should_continue; |
| 643 counts->ssl_judgment = |
| 644 recoverable ? CertPolicy::UNKNOWN : CertPolicy::DENIED; |
| 645 ReevaluateCallbacksForAllCounts(); |
| 646 } |
| 647 } |
| 648 |
| 649 void RequestTrackerImpl::ErrorCallback(CRWSSLCarrier* carrier, bool allow) { |
| 650 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 651 DCHECK(policy_cache_); |
| 652 |
| 653 if (allow) { |
| 654 policy_cache_->AllowCertForHost([carrier sslInfo].cert.get(), |
| 655 [carrier url].host(), |
| 656 [carrier sslInfo].cert_status); |
| 657 ReevaluateCallbacksForAllCounts(); |
| 658 } |
| 659 current_ssl_error_ = NULL; |
| 660 } |
| 661 |
| 662 #pragma mark Client utility methods. |
| 663 |
| 664 // TODO(marq): Convert all internal task-posting to use these. |
| 665 void RequestTrackerImpl::PostUITaskIfOpen(const base::Closure& task) { |
| 666 PostTask(task, web::WebThread::UI); |
| 667 } |
| 668 |
| 669 // static |
| 670 void RequestTrackerImpl::PostUITaskIfOpen( |
| 671 const base::WeakPtr<RequestTracker> tracker, |
| 672 const base::Closure& task) { |
| 673 if (!tracker) |
| 674 return; |
| 675 RequestTrackerImpl* tracker_impl = |
| 676 static_cast<RequestTrackerImpl*>(tracker.get()); |
| 677 tracker_impl->PostUITaskIfOpen(task); |
| 678 } |
| 679 |
| 680 void RequestTrackerImpl::PostIOTask(const base::Closure& task) { |
| 681 PostTask(task, web::WebThread::IO); |
| 682 } |
| 683 |
| 684 void RequestTrackerImpl::ScheduleIOTask(const base::Closure& task) { |
| 685 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 686 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, task); |
| 687 } |
| 688 |
| 689 void RequestTrackerImpl::SetCacheModeFromUIThread( |
| 690 RequestTracker::CacheMode mode) { |
| 691 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 692 web::WebThread::PostTask( |
| 693 web::WebThread::IO, FROM_HERE, |
| 694 base::Bind(&RequestTracker::SetCacheMode, this, mode)); |
| 695 } |
| 696 |
| 697 #pragma mark Private Object Lifecycle API |
| 698 |
| 699 RequestTrackerImpl::RequestTrackerImpl( |
| 700 NSString* request_group_id, |
| 701 net::URLRequestContextGetter* context_getter, |
| 702 id<CRWRequestTrackerDelegate> delegate) |
| 703 : delegate_(delegate), |
| 704 previous_estimate_(0.0f), // Not active by default. |
| 705 estimate_start_index_(0), |
| 706 notification_depth_(0), |
| 707 current_ssl_error_(NULL), |
| 708 has_mixed_content_(false), |
| 709 is_loading_(false), |
| 710 new_estimate_round_(true), |
| 711 is_for_static_file_requests_([delegate isForStaticFileRequests]), |
| 712 request_context_getter_(context_getter), |
| 713 identifier_(++g_next_request_tracker_id), |
| 714 request_group_id_([request_group_id copy]), |
| 715 is_closing_(false) { |
| 716 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 717 } |
| 718 |
| 719 void RequestTrackerImpl::InitOnIOThread( |
| 720 const scoped_refptr<CertificatePolicyCache>& policy_cache) { |
| 721 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 722 Init(); |
| 723 DCHECK(policy_cache); |
| 724 policy_cache_ = policy_cache; |
| 725 } |
| 726 |
| 727 RequestTrackerImpl::~RequestTrackerImpl() { |
| 728 } |
| 729 |
| 730 void RequestTrackerImplTraits::Destruct(const RequestTrackerImpl* t) { |
| 731 // RefCountedThreadSafe assumes we can do all the destruct tasks with a |
| 732 // const pointer, but we actually can't. |
| 733 RequestTrackerImpl* inconstant_t = const_cast<RequestTrackerImpl*>(t); |
| 734 if (web::WebThread::CurrentlyOn(web::WebThread::IO)) { |
| 735 inconstant_t->Destruct(); |
| 736 } else { |
| 737 // Use BindBlock rather than Bind to avoid creating another scoped_refpter |
| 738 // to |this|. |inconstant_t| isn't retained by the block, but since this |
| 739 // method is the mechanism by which all RequestTrackerImpl instances are |
| 740 // destroyed, the object inconstant_t points to won't be deleted while |
| 741 // the block is executing (and Destruct() itself will do the deleting). |
| 742 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, |
| 743 base::BindBlock(^{ |
| 744 inconstant_t->Destruct(); |
| 745 })); |
| 746 } |
| 747 } |
| 748 |
| 749 void RequestTrackerImpl::Destruct() { |
| 750 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 751 DCHECK(is_closing_); |
| 752 |
| 753 pthread_once(&g_once_control, &InitializeGlobals); |
| 754 { |
| 755 base::AutoLock scoped_lock(*g_trackers_lock); |
| 756 g_trackers->erase(request_group_id_); |
| 757 } |
| 758 InvalidateWeakPtrs(); |
| 759 // Delete on the UI thread. |
| 760 web::WebThread::PostTask(web::WebThread::UI, FROM_HERE, base::BindBlock(^{ |
| 761 delete this; |
| 762 })); |
| 763 } |
| 764 |
| 765 #pragma mark Other private methods |
| 766 // TODO(marq): Reorder method implementations to match header and add grouping |
| 767 // marks/comments. |
| 768 |
| 769 void RequestTrackerImpl::Notify() { |
| 770 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 771 if (is_closing_) |
| 772 return; |
| 773 // Notify() is called asynchronously, it runs later on the same |
| 774 // thread. This is used to collate notifications together, avoiding |
| 775 // blanketing the UI with a stream of information. |
| 776 notification_depth_ += 1; |
| 777 web::WebThread::PostTask( |
| 778 web::WebThread::IO, FROM_HERE, |
| 779 base::Bind(&RequestTrackerImpl::StackNotification, this)); |
| 780 } |
| 781 |
| 782 void RequestTrackerImpl::StackNotification() { |
| 783 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 784 if (is_closing_) |
| 785 return; |
| 786 |
| 787 // There is no point in sending the notification if there is another one |
| 788 // already queued. This queue is processing very lightweight changes and |
| 789 // should be exhausted very easily. |
| 790 --notification_depth_; |
| 791 if (notification_depth_) |
| 792 return; |
| 793 |
| 794 SSLNotify(); |
| 795 if (is_loading_) { |
| 796 float estimate = EstimatedProgress(); |
| 797 if (estimate != -1.0f) { |
| 798 web::WebThread::PostTask( |
| 799 web::WebThread::UI, FROM_HERE, |
| 800 base::Bind(&RequestTrackerImpl::NotifyUpdatedProgress, this, |
| 801 estimate)); |
| 802 } |
| 803 } |
| 804 } |
| 805 |
| 806 void RequestTrackerImpl::SSLNotify() { |
| 807 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 808 if (is_closing_) |
| 809 return; |
| 810 |
| 811 if (!counts_.size()) |
| 812 return; // Nothing yet to notify. |
| 813 |
| 814 if (!page_url_.SchemeIsSecure()) |
| 815 return; |
| 816 |
| 817 const GURL page_origin = page_url_.GetOrigin(); |
| 818 ScopedVector<TrackerCounts>::iterator it; |
| 819 for (it = counts_.begin(); it != counts_.end(); ++it) { |
| 820 if (!(*it)->ssl_info.is_valid()) |
| 821 continue; // No SSL info at this point in time on this tracker. |
| 822 |
| 823 GURL request_origin = (*it)->url.GetOrigin(); |
| 824 if (request_origin != page_origin) |
| 825 continue; // Not interesting in the context of the page. |
| 826 |
| 827 base::scoped_nsobject<CRWSSLCarrier> carrier( |
| 828 [[CRWSSLCarrier alloc] initWithTracker:this counts:*it]); |
| 829 web::WebThread::PostTask( |
| 830 web::WebThread::UI, FROM_HERE, |
| 831 base::Bind(&RequestTrackerImpl::NotifyUpdatedSSLStatus, this, carrier)); |
| 832 break; |
| 833 } |
| 834 } |
| 835 |
| 836 void RequestTrackerImpl::NotifyResponseHeaders( |
| 837 net::HttpResponseHeaders* headers, |
| 838 const GURL& request_url) { |
| 839 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 840 [delegate_ handleResponseHeaders:headers requestUrl:request_url]; |
| 841 } |
| 842 |
| 843 void RequestTrackerImpl::NotifyCertificateUsed( |
| 844 net::X509Certificate* certificate, |
| 845 const std::string& host, |
| 846 net::CertStatus status) { |
| 847 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 848 [delegate_ certificateUsed:certificate forHost:host status:status]; |
| 849 } |
| 850 |
| 851 void RequestTrackerImpl::NotifyUpdatedProgress(float estimate) { |
| 852 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 853 [delegate_ updatedProgress:estimate]; |
| 854 } |
| 855 |
| 856 void RequestTrackerImpl::NotifyClearCertificates() { |
| 857 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 858 [delegate_ clearCertificates]; |
| 859 } |
| 860 |
| 861 void RequestTrackerImpl::NotifyUpdatedSSLStatus( |
| 862 base::scoped_nsobject<CRWSSLCarrier> carrier) { |
| 863 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 864 [delegate_ updatedSSLStatus:[carrier sslStatus] |
| 865 forPageUrl:[carrier url] |
| 866 userInfo:user_info_]; |
| 867 } |
| 868 |
| 869 void RequestTrackerImpl::NotifyPresentSSLError( |
| 870 base::scoped_nsobject<CRWSSLCarrier> carrier, |
| 871 bool recoverable) { |
| 872 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| 873 [delegate_ presentSSLError:[carrier sslInfo] |
| 874 forSSLStatus:[carrier sslStatus] |
| 875 onUrl:[carrier url] |
| 876 recoverable:recoverable |
| 877 callback:^(BOOL flag) { |
| 878 [carrier errorCallback:flag && recoverable]; |
| 879 }]; |
| 880 } |
| 881 |
| 882 void RequestTrackerImpl::EvaluateSSLCallbackForCounts(TrackerCounts* counts) { |
| 883 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 884 DCHECK(policy_cache_); |
| 885 |
| 886 // Ignore non-SSL requests. |
| 887 if (!counts->ssl_info.is_valid()) |
| 888 return; |
| 889 |
| 890 CertPolicy::Judgment judgment = |
| 891 policy_cache_->QueryPolicy(counts->ssl_info.cert.get(), |
| 892 counts->url.host(), |
| 893 counts->ssl_info.cert_status); |
| 894 |
| 895 if (judgment != CertPolicy::ALLOWED) { |
| 896 // Apply some fine tuning. |
| 897 // TODO(droger): This logic is duplicated from SSLPolicy. Sharing the code |
| 898 // would be better. |
| 899 switch (net::MapCertStatusToNetError(counts->ssl_info.cert_status)) { |
| 900 case net::ERR_CERT_NO_REVOCATION_MECHANISM: |
| 901 // Ignore this error. |
| 902 judgment = CertPolicy::ALLOWED; |
| 903 break; |
| 904 case net::ERR_CERT_UNABLE_TO_CHECK_REVOCATION: |
| 905 // We ignore this error but it will show a warning status in the |
| 906 // location bar. |
| 907 judgment = CertPolicy::ALLOWED; |
| 908 break; |
| 909 case net::ERR_CERT_CONTAINS_ERRORS: |
| 910 case net::ERR_CERT_REVOKED: |
| 911 case net::ERR_CERT_INVALID: |
| 912 case net::ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY: |
| 913 case net::ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN: |
| 914 judgment = CertPolicy::DENIED; |
| 915 break; |
| 916 case net::ERR_CERT_WEAK_SIGNATURE_ALGORITHM: |
| 917 case net::ERR_CERT_COMMON_NAME_INVALID: |
| 918 case net::ERR_CERT_DATE_INVALID: |
| 919 case net::ERR_CERT_AUTHORITY_INVALID: |
| 920 // Nothing. If DENIED it will stay denied. If UNKNOWN it will be |
| 921 // shown to the user for decision. |
| 922 break; |
| 923 default: |
| 924 NOTREACHED(); |
| 925 judgment = CertPolicy::DENIED; |
| 926 break; |
| 927 } |
| 928 } |
| 929 |
| 930 counts->ssl_judgment = judgment; |
| 931 |
| 932 switch (judgment) { |
| 933 case CertPolicy::UNKNOWN: |
| 934 case CertPolicy::DENIED: |
| 935 // Simply cancel the request. |
| 936 CancelRequestForCounts(counts); |
| 937 break; |
| 938 case CertPolicy::ALLOWED: |
| 939 counts->ssl_callback.Run(YES); |
| 940 counts->ssl_callback = base::Bind(&DoNothing); |
| 941 break; |
| 942 default: |
| 943 NOTREACHED(); |
| 944 // For now simply cancel the request. |
| 945 CancelRequestForCounts(counts); |
| 946 break; |
| 947 } |
| 948 } |
| 949 |
| 950 void RequestTrackerImpl::ReevaluateCallbacksForAllCounts() { |
| 951 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 952 if (is_closing_) |
| 953 return; |
| 954 |
| 955 ScopedVector<TrackerCounts>::iterator it; |
| 956 for (it = counts_.begin(); it != counts_.end(); ++it) { |
| 957 // Check if the value hasn't changed via a user action. |
| 958 if ((*it)->ssl_judgment == CertPolicy::UNKNOWN) |
| 959 EvaluateSSLCallbackForCounts(*it); |
| 960 |
| 961 CertPolicy::Judgment judgment = (*it)->ssl_judgment; |
| 962 if (judgment == CertPolicy::ALLOWED) |
| 963 continue; |
| 964 |
| 965 // SSL errors on subrequests are simply ignored. The call to |
| 966 // EvaluateSSLCallbackForCounts() cancelled the request and nothing will |
| 967 // restart it. |
| 968 if ((*it)->is_subrequest) |
| 969 continue; |
| 970 |
| 971 if (!current_ssl_error_) { |
| 972 // For the UNKNOWN and DENIED state the information should be pushed to |
| 973 // the delegate. But only one at a time. |
| 974 current_ssl_error_ = (*it); |
| 975 base::scoped_nsobject<CRWSSLCarrier> carrier([[CRWSSLCarrier alloc] |
| 976 initWithTracker:this counts:current_ssl_error_]); |
| 977 web::WebThread::PostTask( |
| 978 web::WebThread::UI, FROM_HERE, |
| 979 base::Bind(&RequestTrackerImpl::NotifyPresentSSLError, this, carrier, |
| 980 judgment == CertPolicy::UNKNOWN)); |
| 981 } |
| 982 } |
| 983 } |
| 984 |
| 985 void RequestTrackerImpl::CancelRequestForCounts(TrackerCounts* counts) { |
| 986 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 987 // Cancel the request. |
| 988 counts->done = true; |
| 989 counts_by_request_.erase(counts->request); |
| 990 counts->ssl_callback.Run(NO); |
| 991 counts->ssl_callback = base::Bind(&DoNothing); |
| 992 Notify(); |
| 993 } |
| 994 |
| 995 PageCounts RequestTrackerImpl::pageCounts() { |
| 996 DCHECK_GE(counts_.size(), estimate_start_index_); |
| 997 |
| 998 PageCounts page_counts; |
| 999 |
| 1000 ScopedVector<TrackerCounts>::iterator it; |
| 1001 for (it = counts_.begin() + estimate_start_index_; |
| 1002 it != counts_.end(); ++it) { |
| 1003 if ((*it)->done) { |
| 1004 uint64_t size = (*it)->processed; |
| 1005 page_counts.finished += 1; |
| 1006 page_counts.finished_bytes += size; |
| 1007 if (page_counts.largest_byte_size_known < size) { |
| 1008 page_counts.largest_byte_size_known = size; |
| 1009 } |
| 1010 } else { |
| 1011 page_counts.unfinished += 1; |
| 1012 if ((*it)->expected_length) { |
| 1013 uint64_t size = (*it)->expected_length; |
| 1014 page_counts.unfinished_estimate_bytes_done += (*it)->processed; |
| 1015 page_counts.unfinished_estimated_bytes_left += size; |
| 1016 if (page_counts.largest_byte_size_known < size) { |
| 1017 page_counts.largest_byte_size_known = size; |
| 1018 } |
| 1019 } else { |
| 1020 page_counts.unfinished_no_estimate += 1; |
| 1021 page_counts.unfinished_no_estimate_bytes_done += (*it)->processed; |
| 1022 } |
| 1023 } |
| 1024 } |
| 1025 |
| 1026 return page_counts; |
| 1027 } |
| 1028 |
| 1029 float RequestTrackerImpl::EstimatedProgress() { |
| 1030 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 1031 |
| 1032 const PageCounts page_counts = pageCounts(); |
| 1033 |
| 1034 // Nothing in progress and the last time was the same. |
| 1035 if (!page_counts.unfinished && previous_estimate_ == 0.0f) |
| 1036 return -1.0f; |
| 1037 |
| 1038 // First request. |
| 1039 if (previous_estimate_ == 0.0f) { |
| 1040 // start low. |
| 1041 previous_estimate_ = 0.1f; |
| 1042 return previous_estimate_; // Return the just started status. |
| 1043 } |
| 1044 |
| 1045 // The very simple case where everything is probably done and dusted. |
| 1046 if (!page_counts.unfinished) { |
| 1047 // Add 60%, and return. Another task is going to finish this. |
| 1048 float bump = (1.0f - previous_estimate_) * 0.6f; |
| 1049 previous_estimate_ += bump; |
| 1050 return previous_estimate_; |
| 1051 } |
| 1052 |
| 1053 // Calculate some ratios. |
| 1054 // First the ratio of the finished vs the unfinished counts of resources |
| 1055 // loaded. |
| 1056 float unfinishedRatio = |
| 1057 static_cast<float>(page_counts.finished) / |
| 1058 static_cast<float>(page_counts.unfinished + page_counts.finished); |
| 1059 |
| 1060 // The ratio of bytes left vs bytes already downloaded for the resources where |
| 1061 // no estimates of final size are known. For this ratio it is assumed the size |
| 1062 // of a resource not downloaded yet is the maximum size of all the resources |
| 1063 // seen so far. |
| 1064 float noEstimateRatio = (!page_counts.unfinished_no_estimate_bytes_done) ? |
| 1065 0.0f : |
| 1066 static_cast<float>(page_counts.unfinished_no_estimate * |
| 1067 page_counts.largest_byte_size_known) / |
| 1068 static_cast<float>(page_counts.finished_bytes + |
| 1069 page_counts.unfinished_no_estimate_bytes_done); |
| 1070 |
| 1071 // The ratio of bytes left vs bytes already downloaded for the resources with |
| 1072 // available estimated size. |
| 1073 float estimateRatio = (!page_counts.unfinished_estimated_bytes_left) ? |
| 1074 noEstimateRatio : |
| 1075 static_cast<float>(page_counts.unfinished_estimate_bytes_done) / |
| 1076 static_cast<float>(page_counts.unfinished_estimate_bytes_done + |
| 1077 page_counts.unfinished_estimated_bytes_left); |
| 1078 |
| 1079 // Reassemble all of this. |
| 1080 float total = |
| 1081 0.1f + // Minimum value. |
| 1082 unfinishedRatio * 0.6f + |
| 1083 estimateRatio * 0.3f; |
| 1084 |
| 1085 if (previous_estimate_ >= total) |
| 1086 return -1.0f; |
| 1087 |
| 1088 // 10% of what's left. |
| 1089 float maxBump = (1.0f - previous_estimate_) / 10.0f; |
| 1090 // total is greater than previous estimate, need to bump the estimate up. |
| 1091 if ((previous_estimate_ + maxBump) > total) { |
| 1092 // Less than a 10% bump, bump to the new value. |
| 1093 previous_estimate_ = total; |
| 1094 } else { |
| 1095 // Just bump by 10% toward the total. |
| 1096 previous_estimate_ += maxBump; |
| 1097 } |
| 1098 |
| 1099 return previous_estimate_; |
| 1100 } |
| 1101 |
| 1102 void RequestTrackerImpl::RecomputeMixedContent( |
| 1103 const TrackerCounts* split_position) { |
| 1104 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 1105 // Check if the mixed content before trimming was correct. |
| 1106 if (page_url_.SchemeIsSecure() && has_mixed_content_) { |
| 1107 bool old_url_has_mixed_content = false; |
| 1108 const GURL origin = page_url_.GetOrigin(); |
| 1109 ScopedVector<TrackerCounts>::iterator it = counts_.begin(); |
| 1110 while (it != counts_.end() && *it != split_position) { |
| 1111 if (!(*it)->url.SchemeIsSecure() && |
| 1112 origin == (*it)->first_party_for_cookies_origin) { |
| 1113 old_url_has_mixed_content = true; |
| 1114 break; |
| 1115 } |
| 1116 ++it; |
| 1117 } |
| 1118 if (!old_url_has_mixed_content) { |
| 1119 // We marked the previous page with incorrect data about its mixed |
| 1120 // content. Turns out that the elements that triggered that condition |
| 1121 // where in fact in a subsequent page. Duh. |
| 1122 // Resend a notification for the |page_url_| informing the upper layer |
| 1123 // that the mixed content was a red herring. |
| 1124 has_mixed_content_ = false; |
| 1125 SSLNotify(); |
| 1126 } |
| 1127 } |
| 1128 } |
| 1129 |
| 1130 void RequestTrackerImpl::RecomputeCertificatePolicy( |
| 1131 const TrackerCounts* splitPosition) { |
| 1132 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 1133 // Clear the judgments for the old URL. |
| 1134 web::WebThread::PostTask( |
| 1135 web::WebThread::UI, FROM_HERE, |
| 1136 base::Bind(&RequestTrackerImpl::NotifyClearCertificates, this)); |
| 1137 // Report judgements for the new URL. |
| 1138 ScopedVector<TrackerCounts>::const_reverse_iterator it; |
| 1139 for (it = counts_.rbegin(); it != counts_.rend(); ++it) { |
| 1140 TrackerCounts* counts = *it; |
| 1141 if (counts->allowed_by_user) { |
| 1142 std::string host = counts->url.host(); |
| 1143 web::WebThread::PostTask( |
| 1144 web::WebThread::UI, FROM_HERE, |
| 1145 base::Bind(&RequestTrackerImpl::NotifyCertificateUsed, this, |
| 1146 counts->ssl_info.cert, host, |
| 1147 counts->ssl_info.cert_status)); |
| 1148 } |
| 1149 if (counts == splitPosition) |
| 1150 break; |
| 1151 } |
| 1152 } |
| 1153 |
| 1154 void RequestTrackerImpl::HistoryStateChangeToURL(const GURL& full_url) { |
| 1155 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 1156 GURL url = GURLByRemovingRefFromGURL(full_url); |
| 1157 |
| 1158 if (is_loading_ && |
| 1159 web::history_state_util::IsHistoryStateChangeValid(url, page_url_)) { |
| 1160 page_url_ = url; |
| 1161 } |
| 1162 } |
| 1163 |
| 1164 void RequestTrackerImpl::TrimToURL(const GURL& full_url, id user_info) { |
| 1165 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 1166 |
| 1167 GURL url = GURLByRemovingRefFromGURL(full_url); |
| 1168 |
| 1169 // Locate the request with this url, if present. |
| 1170 bool new_url_has_mixed_content = false; |
| 1171 bool url_scheme_is_secure = url.SchemeIsSecure(); |
| 1172 ScopedVector<TrackerCounts>::const_reverse_iterator rit = counts_.rbegin(); |
| 1173 while (rit != counts_.rend() && (*rit)->url != url) { |
| 1174 if (url_scheme_is_secure && !(*rit)->url.SchemeIsSecure() && |
| 1175 (*rit)->first_party_for_cookies_origin == url.GetOrigin()) { |
| 1176 new_url_has_mixed_content = true; |
| 1177 } |
| 1178 ++rit; |
| 1179 } |
| 1180 |
| 1181 // |split_position| will be set to the count for the passed url if it exists. |
| 1182 TrackerCounts* split_position = NULL; |
| 1183 if (rit != counts_.rend()) { |
| 1184 split_position = (*rit); |
| 1185 } else { |
| 1186 // The URL was not found, everything will be trimmed. The mixed content |
| 1187 // calculation is invalid. |
| 1188 new_url_has_mixed_content = false; |
| 1189 |
| 1190 // In the case of a page loaded via a HTML5 manifest there is no page |
| 1191 // boundary to be found. However the latest count is a request for a |
| 1192 // manifest. This tries to detect this peculiar case. |
| 1193 // This is important as if this request for the manifest is on the same |
| 1194 // domain as the page itself this will allow retrieval of the SSL |
| 1195 // information. |
| 1196 if (url_scheme_is_secure && counts_.size()) { |
| 1197 TrackerCounts* back = counts_.back(); |
| 1198 const GURL& back_url = back->url; |
| 1199 if (back_url.SchemeIsSecure() && |
| 1200 back_url.GetOrigin() == url.GetOrigin() && |
| 1201 !back->is_subrequest) { |
| 1202 split_position = back; |
| 1203 } |
| 1204 } |
| 1205 } |
| 1206 RecomputeMixedContent(split_position); |
| 1207 RecomputeCertificatePolicy(split_position); |
| 1208 |
| 1209 // Trim up to that element. |
| 1210 ScopedVector<TrackerCounts>::iterator it = counts_.begin(); |
| 1211 while (it != counts_.end() && *it != split_position) { |
| 1212 if (!(*it)->done) { |
| 1213 // This is for an unfinished request on a previous page. We do not care |
| 1214 // about those anymore. Cancel the request. |
| 1215 if ((*it)->ssl_judgment == CertPolicy::UNKNOWN) |
| 1216 CancelRequestForCounts(*it); |
| 1217 counts_by_request_.erase((*it)->request); |
| 1218 } |
| 1219 it = counts_.erase(it); |
| 1220 } |
| 1221 |
| 1222 has_mixed_content_ = new_url_has_mixed_content; |
| 1223 page_url_ = url; |
| 1224 user_info_.reset([user_info retain]); |
| 1225 estimate_start_index_ = 0; |
| 1226 is_loading_ = true; |
| 1227 previous_estimate_ = 0.0f; |
| 1228 new_estimate_round_ = true; |
| 1229 ReevaluateCallbacksForAllCounts(); |
| 1230 Notify(); |
| 1231 } |
| 1232 |
| 1233 void RequestTrackerImpl::StopPageLoad(const GURL& url, bool load_success) { |
| 1234 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 1235 DCHECK(page_url_ == GURLByRemovingRefFromGURL(url)); |
| 1236 is_loading_ = false; |
| 1237 } |
| 1238 |
| 1239 #pragma mark Internal utilities for task posting |
| 1240 |
| 1241 void RequestTrackerImpl::PostTask(const base::Closure& task, |
| 1242 web::WebThread::ID thread) { |
| 1243 // Absolute sanity test: |thread| is one of {UI, IO} |
| 1244 DCHECK(thread == web::WebThread::UI || thread == web::WebThread::IO); |
| 1245 // Check that we're on the counterpart thread to the one we're posting to. |
| 1246 DCHECK_CURRENTLY_ON_WEB_THREAD( |
| 1247 thread == web::WebThread::IO ? web::WebThread::UI : web::WebThread::IO); |
| 1248 // Don't post if the tracker is closing and we're on the IO thread. |
| 1249 // (there should be no way to call anything from the UI thread if |
| 1250 // the tracker is closing). |
| 1251 if (is_closing_ && web::WebThread::CurrentlyOn(web::WebThread::IO)) |
| 1252 return; |
| 1253 web::WebThread::PostTask(thread, FROM_HERE, task); |
| 1254 } |
| 1255 |
| 1256 #pragma mark Other internal methods. |
| 1257 |
| 1258 NSString* RequestTrackerImpl::UnsafeDescription() { |
| 1259 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 1260 |
| 1261 NSMutableArray* urls = [NSMutableArray array]; |
| 1262 ScopedVector<TrackerCounts>::iterator it; |
| 1263 for (it = counts_.begin(); it != counts_.end(); ++it) |
| 1264 [urls addObject:(*it)->Description()]; |
| 1265 |
| 1266 return [NSString stringWithFormat:@"RequestGroupID %@\n%@\n%@", |
| 1267 request_group_id_.get(), |
| 1268 net::NSURLWithGURL(page_url_), |
| 1269 [urls componentsJoinedByString:@"\n"]]; |
| 1270 } |
| 1271 |
| 1272 NSString* RequestTrackerImpl::GetNetworkActivityKey() { |
| 1273 return [NSString |
| 1274 stringWithFormat:@"RequestTrackerImpl.NetworkActivityIndicatorKey.%@", |
| 1275 request_group_id_.get()]; |
| 1276 } |
| 1277 |
| 1278 void RequestTrackerImpl::CancelRequests() { |
| 1279 DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| 1280 std::set<net::URLRequest*>::iterator it; |
| 1281 // TODO(droger): When canceling the request, we should in theory make sure |
| 1282 // that the NSURLProtocol client method |didFailWithError| is called, |
| 1283 // otherwise the iOS system may wait indefinitely for the request to complete. |
| 1284 // However, as we currently only cancel the requests when closing a tab, the |
| 1285 // requests are all canceled by the system shortly after and nothing bad |
| 1286 // happens. |
| 1287 for (it = live_requests_.begin(); it != live_requests_.end(); ++it) |
| 1288 (*it)->Cancel(); |
| 1289 |
| 1290 int removedRequests = live_requests_.size(); |
| 1291 live_requests_.clear(); |
| 1292 if (!is_for_static_file_requests_ && removedRequests > 0) { |
| 1293 NSString* networkActivityKey = GetNetworkActivityKey(); |
| 1294 web::WebThread::PostTask( |
| 1295 web::WebThread::UI, FROM_HERE, |
| 1296 base::BindBlock(^{ |
| 1297 [[CRWNetworkActivityIndicatorManager sharedInstance] |
| 1298 clearNetworkTasksForGroup:networkActivityKey]; |
| 1299 })); |
| 1300 } |
| 1301 } |
| 1302 |
| 1303 void RequestTrackerImpl::SetCertificatePolicyCacheForTest( |
| 1304 web::CertificatePolicyCache* cache) { |
| 1305 policy_cache_ = cache; |
| 1306 } |
| 1307 |
| 1308 } // namespace web |
OLD | NEW |