| OLD | NEW | 
|---|
| 1 /* | 1 /* | 
| 2     Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) | 2     Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) | 
| 3     Copyright (C) 2001 Dirk Mueller (mueller@kde.org) | 3     Copyright (C) 2001 Dirk Mueller (mueller@kde.org) | 
| 4     Copyright (C) 2002 Waldo Bastian (bastian@kde.org) | 4     Copyright (C) 2002 Waldo Bastian (bastian@kde.org) | 
| 5     Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All 
      rights reserved. | 5     Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All 
      rights reserved. | 
| 6     Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ | 6     Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ | 
| 7 | 7 | 
| 8     This library is free software; you can redistribute it and/or | 8     This library is free software; you can redistribute it and/or | 
| 9     modify it under the terms of the GNU Library General Public | 9     modify it under the terms of the GNU Library General Public | 
| 10     License as published by the Free Software Foundation; either | 10     License as published by the Free Software Foundation; either | 
| (...skipping 284 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 295     options.synchronousPolicy = RequestSynchronously; | 295     options.synchronousPolicy = RequestSynchronously; | 
| 296     request.setOptions(options); | 296     request.setOptions(options); | 
| 297     return requestResource(Resource::Raw, request); | 297     return requestResource(Resource::Raw, request); | 
| 298 } | 298 } | 
| 299 | 299 | 
| 300 ResourcePtr<ImageResource> ResourceFetcher::fetchImage(FetchRequest& request) | 300 ResourcePtr<ImageResource> ResourceFetcher::fetchImage(FetchRequest& request) | 
| 301 { | 301 { | 
| 302     if (LocalFrame* f = frame()) { | 302     if (LocalFrame* f = frame()) { | 
| 303         if (f->document()->pageDismissalEventBeingDispatched() != Document::NoDi
      smissal) { | 303         if (f->document()->pageDismissalEventBeingDispatched() != Document::NoDi
      smissal) { | 
| 304             KURL requestURL = request.resourceRequest().url(); | 304             KURL requestURL = request.resourceRequest().url(); | 
| 305             if (requestURL.isValid() && canRequest(Resource::Image, requestURL, 
      request.options(), request.forPreload(), request.originRestriction())) | 305             if (requestURL.isValid() && canRequest(Resource::Image, request.reso
      urceRequest(), requestURL, request.options(), request.forPreload(), request.orig
      inRestriction())) | 
| 306                 PingLoader::loadImage(f, requestURL); | 306                 PingLoader::loadImage(f, requestURL); | 
| 307             return 0; | 307             return 0; | 
| 308         } | 308         } | 
| 309     } | 309     } | 
| 310 | 310 | 
| 311     if (request.resourceRequest().url().protocolIsData()) | 311     if (request.resourceRequest().url().protocolIsData()) | 
| 312         preCacheDataURIImage(request); | 312         preCacheDataURIImage(request); | 
| 313 | 313 | 
| 314     request.setDefer(clientDefersImage(request.resourceRequest().url()) ? FetchR
      equest::DeferredByClient : FetchRequest::NoDefer); | 314     request.setDefer(clientDefersImage(request.resourceRequest().url()) ? FetchR
      equest::DeferredByClient : FetchRequest::NoDefer); | 
| 315     ResourcePtr<Resource> resource = requestResource(Resource::Image, request); | 315     ResourcePtr<Resource> resource = requestResource(Resource::Image, request); | 
| (...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 424     resource->setNeedsSynchronousCacheHit(substituteData.forceSynchronousLoad())
      ; | 424     resource->setNeedsSynchronousCacheHit(substituteData.forceSynchronousLoad())
      ; | 
| 425     resource->setOptions(request.options()); | 425     resource->setOptions(request.options()); | 
| 426     resource->setDataBufferingPolicy(BufferData); | 426     resource->setDataBufferingPolicy(BufferData); | 
| 427     resource->responseReceived(response); | 427     resource->responseReceived(response); | 
| 428     if (substituteData.content()->size()) | 428     if (substituteData.content()->size()) | 
| 429         resource->setResourceBuffer(substituteData.content()); | 429         resource->setResourceBuffer(substituteData.content()); | 
| 430     resource->finish(); | 430     resource->finish(); | 
| 431     memoryCache()->add(resource.get()); | 431     memoryCache()->add(resource.get()); | 
| 432 } | 432 } | 
| 433 | 433 | 
| 434 bool ResourceFetcher::checkInsecureContent(Resource::Type type, const KURL& url,
       MixedContentBlockingTreatment treatment) const | 434 bool ResourceFetcher::checkInsecureContent(Resource::Type type, const KURL& url,
       LocalFrame* frame, MixedContentBlockingTreatment treatment) const | 
| 435 { | 435 { | 
| 436     if (treatment == TreatAsDefaultForType) { | 436     if (treatment == TreatAsDefaultForType) { | 
| 437         switch (type) { | 437         switch (type) { | 
| 438         case Resource::XSLStyleSheet: | 438         case Resource::XSLStyleSheet: | 
| 439             ASSERT(RuntimeEnabledFeatures::xsltEnabled()); | 439             ASSERT(RuntimeEnabledFeatures::xsltEnabled()); | 
| 440         case Resource::Script: | 440         case Resource::Script: | 
| 441         case Resource::SVGDocument: | 441         case Resource::SVGDocument: | 
| 442         case Resource::CSSStyleSheet: | 442         case Resource::CSSStyleSheet: | 
| 443         case Resource::ImportResource: | 443         case Resource::ImportResource: | 
| 444             // These resource can inject script into the current document (Scrip
      t, | 444             // These resource can inject script into the current document (Scrip
      t, | 
| (...skipping 16 matching lines...) Expand all  Loading... | 
| 461             break; | 461             break; | 
| 462 | 462 | 
| 463         case Resource::MainResource: | 463         case Resource::MainResource: | 
| 464         case Resource::LinkPrefetch: | 464         case Resource::LinkPrefetch: | 
| 465         case Resource::LinkSubresource: | 465         case Resource::LinkSubresource: | 
| 466             // These cannot affect the current document. | 466             // These cannot affect the current document. | 
| 467             treatment = TreatAsAlwaysAllowedContent; | 467             treatment = TreatAsAlwaysAllowedContent; | 
| 468             break; | 468             break; | 
| 469         } | 469         } | 
| 470     } | 470     } | 
|  | 471 | 
|  | 472     // No frame, no mixed content. | 
|  | 473     if (!frame) | 
|  | 474         return true; | 
|  | 475 | 
| 471     if (treatment == TreatAsActiveContent) { | 476     if (treatment == TreatAsActiveContent) { | 
| 472         if (LocalFrame* f = frame()) { | 477         if (!frame->loader().mixedContentChecker()->canRunInsecureContent(frame-
      >document()->securityOrigin(), url)) | 
| 473             if (!f->loader().mixedContentChecker()->canRunInsecureContent(m_docu
      ment->securityOrigin(), url)) | 478             return false; | 
| 474                 return false; |  | 
| 475         } |  | 
| 476     } else if (treatment == TreatAsPassiveContent) { | 479     } else if (treatment == TreatAsPassiveContent) { | 
| 477         if (LocalFrame* f = frame()) { | 480         if (!frame->loader().mixedContentChecker()->canDisplayInsecureContent(fr
      ame->document()->securityOrigin(), url)) | 
| 478             if (!f->loader().mixedContentChecker()->canDisplayInsecureContent(m_
      document->securityOrigin(), url)) | 481             return false; | 
| 479                 return false; | 482         if (MixedContentChecker::isMixedContent(frame->document()->securityOrigi
      n(), url) || MixedContentChecker::isMixedContent(toLocalFrame(frame->tree().top(
      ))->document()->securityOrigin(), url)) { | 
| 480             if (MixedContentChecker::isMixedContent(f->document()->securityOrigi
      n(), url) || MixedContentChecker::isMixedContent(toLocalFrame(frame()->tree().to
      p())->document()->securityOrigin(), url)) { | 483             switch (type) { | 
| 481                 switch (type) { | 484             case Resource::Raw: | 
| 482                 case Resource::Raw: | 485                 UseCounter::count(frame->document(), UseCounter::MixedContentRaw
      ); | 
| 483                     UseCounter::count(f->document(), UseCounter::MixedContentRaw
      ); | 486                 break; | 
| 484                     break; |  | 
| 485 | 487 | 
| 486                 case Resource::Image: | 488             case Resource::Image: | 
| 487                     UseCounter::count(f->document(), UseCounter::MixedContentIma
      ge); | 489                 UseCounter::count(frame->document(), UseCounter::MixedContentIma
      ge); | 
| 488                     break; | 490                 break; | 
| 489 | 491 | 
| 490                 case Resource::Media: | 492             case Resource::Media: | 
| 491                     UseCounter::count(f->document(), UseCounter::MixedContentMed
      ia); | 493                 UseCounter::count(frame->document(), UseCounter::MixedContentMed
      ia); | 
| 492                     break; | 494                 break; | 
| 493 | 495 | 
| 494                 default: | 496             default: | 
| 495                     ASSERT_NOT_REACHED(); | 497                 ASSERT_NOT_REACHED(); | 
| 496                 } |  | 
| 497             } | 498             } | 
| 498         } | 499         } | 
| 499     } else { | 500     } else { | 
| 500         ASSERT(treatment == TreatAsAlwaysAllowedContent); | 501         ASSERT(treatment == TreatAsAlwaysAllowedContent); | 
| 501     } | 502     } | 
| 502     return true; | 503     return true; | 
| 503 } | 504 } | 
| 504 | 505 | 
| 505 bool ResourceFetcher::canRequest(Resource::Type type, const KURL& url, const Res
      ourceLoaderOptions& options, bool forPreload, FetchRequest::OriginRestriction or
      iginRestriction) const | 506 bool ResourceFetcher::canRequest(Resource::Type type, const ResourceRequest& res
      ourceRequest, const KURL& url, const ResourceLoaderOptions& options, bool forPre
      load, FetchRequest::OriginRestriction originRestriction) const | 
| 506 { | 507 { | 
| 507     SecurityOrigin* securityOrigin = options.securityOrigin.get(); | 508     SecurityOrigin* securityOrigin = options.securityOrigin.get(); | 
| 508     if (!securityOrigin && document()) | 509     if (!securityOrigin && document()) | 
| 509         securityOrigin = document()->securityOrigin(); | 510         securityOrigin = document()->securityOrigin(); | 
| 510 | 511 | 
| 511     if (originRestriction != FetchRequest::NoOriginRestriction && securityOrigin
       && !securityOrigin->canDisplay(url)) { | 512     if (originRestriction != FetchRequest::NoOriginRestriction && securityOrigin
       && !securityOrigin->canDisplay(url)) { | 
| 512         if (!forPreload) | 513         if (!forPreload) | 
| 513             context().reportLocalLoadFailed(url); | 514             context().reportLocalLoadFailed(url); | 
| 514         WTF_LOG(ResourceLoading, "ResourceFetcher::requestResource URL was not a
      llowed by SecurityOrigin::canDisplay"); | 515         WTF_LOG(ResourceLoading, "ResourceFetcher::requestResource URL was not a
      llowed by SecurityOrigin::canDisplay"); | 
| 515         return 0; | 516         return 0; | 
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 616     // except for data urls. | 617     // except for data urls. | 
| 617     if (type != Resource::MainResource) { | 618     if (type != Resource::MainResource) { | 
| 618         if (frame() && frame()->chromeClient().isSVGImageChromeClient() && !url.
      protocolIsData()) | 619         if (frame() && frame()->chromeClient().isSVGImageChromeClient() && !url.
      protocolIsData()) | 
| 619             return false; | 620             return false; | 
| 620     } | 621     } | 
| 621 | 622 | 
| 622     // Last of all, check for insecure content. We do this last so that when | 623     // Last of all, check for insecure content. We do this last so that when | 
| 623     // folks block insecure content with a CSP policy, they don't get a warning. | 624     // folks block insecure content with a CSP policy, they don't get a warning. | 
| 624     // They'll still get a warning in the console about CSP blocking the load. | 625     // They'll still get a warning in the console about CSP blocking the load. | 
| 625 | 626 | 
|  | 627     // If we're loading the main resource of a subframe, ensure that we treat th
      e resource as active | 
|  | 628     // content for the purposes of mixed content checks, and that we check again
      st the parent of the | 
|  | 629     // active frame, rather than the frame itself. | 
|  | 630     LocalFrame* effectiveFrame = frame(); | 
|  | 631     MixedContentBlockingTreatment effectiveTreatment = options.mixedContentBlock
      ingTreatment; | 
|  | 632     if (resourceRequest.frameType() == WebURLRequest::FrameTypeNested) { | 
|  | 633         effectiveTreatment = TreatAsActiveContent; | 
|  | 634         // FIXME: Deal with RemoteFrames. | 
|  | 635         if (frame()->tree().parent()->isLocalFrame()) | 
|  | 636             effectiveFrame = toLocalFrame(frame()->tree().parent()); | 
|  | 637     } | 
|  | 638 | 
| 626     // FIXME: Should we consider forPreload here? | 639     // FIXME: Should we consider forPreload here? | 
| 627     if (!checkInsecureContent(type, url, options.mixedContentBlockingTreatment)) | 640     if (!checkInsecureContent(type, url, effectiveFrame, effectiveTreatment)) | 
| 628         return false; | 641         return false; | 
| 629 | 642 | 
| 630     return true; | 643     return true; | 
| 631 } | 644 } | 
| 632 | 645 | 
| 633 bool ResourceFetcher::canAccessResource(Resource* resource, SecurityOrigin* sour
      ceOrigin, const KURL& url) const | 646 bool ResourceFetcher::canAccessResource(Resource* resource, SecurityOrigin* sour
      ceOrigin, const KURL& url) const | 
| 634 { | 647 { | 
| 635     // Redirects can change the response URL different from one of request. | 648     // Redirects can change the response URL different from one of request. | 
| 636     if (!canRequest(resource->type(), url, resource->options(), resource->isUnus
      edPreload(), FetchRequest::UseDefaultOriginRestrictionForType)) | 649     if (!canRequest(resource->type(), resource->resourceRequest(), url, resource
      ->options(), resource->isUnusedPreload(), FetchRequest::UseDefaultOriginRestrict
      ionForType)) | 
| 637         return false; | 650         return false; | 
| 638 | 651 | 
| 639     if (!sourceOrigin && document()) | 652     if (!sourceOrigin && document()) | 
| 640         sourceOrigin = document()->securityOrigin(); | 653         sourceOrigin = document()->securityOrigin(); | 
| 641 | 654 | 
| 642     if (sourceOrigin->canRequest(url)) | 655     if (sourceOrigin->canRequest(url)) | 
| 643         return true; | 656         return true; | 
| 644 | 657 | 
| 645     String errorDescription; | 658     String errorDescription; | 
| 646     if (!resource->passesAccessControlCheck(sourceOrigin, errorDescription)) { | 659     if (!resource->passesAccessControlCheck(sourceOrigin, errorDescription)) { | 
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 706     KURL url = request.resourceRequest().url(); | 719     KURL url = request.resourceRequest().url(); | 
| 707 | 720 | 
| 708     WTF_LOG(ResourceLoading, "ResourceFetcher::requestResource '%s', charset '%s
      ', priority=%d, forPreload=%u, type=%s", url.elidedString().latin1().data(), req
      uest.charset().latin1().data(), request.priority(), request.forPreload(), Resour
      ceTypeName(type)); | 721     WTF_LOG(ResourceLoading, "ResourceFetcher::requestResource '%s', charset '%s
      ', priority=%d, forPreload=%u, type=%s", url.elidedString().latin1().data(), req
      uest.charset().latin1().data(), request.priority(), request.forPreload(), Resour
      ceTypeName(type)); | 
| 709 | 722 | 
| 710     // If only the fragment identifiers differ, it is the same resource. | 723     // If only the fragment identifiers differ, it is the same resource. | 
| 711     url = MemoryCache::removeFragmentIdentifierIfNeeded(url); | 724     url = MemoryCache::removeFragmentIdentifierIfNeeded(url); | 
| 712 | 725 | 
| 713     if (!url.isValid()) | 726     if (!url.isValid()) | 
| 714         return 0; | 727         return 0; | 
| 715 | 728 | 
| 716     if (!canRequest(type, url, request.options(), request.forPreload(), request.
      originRestriction())) | 729     if (!canRequest(type, request.resourceRequest(), url, request.options(), req
      uest.forPreload(), request.originRestriction())) | 
| 717         return 0; | 730         return 0; | 
| 718 | 731 | 
| 719     if (LocalFrame* f = frame()) | 732     if (LocalFrame* f = frame()) | 
| 720         f->loader().client()->dispatchWillRequestResource(&request); | 733         f->loader().client()->dispatchWillRequestResource(&request); | 
| 721 | 734 | 
| 722     if (!request.forPreload()) { | 735     if (!request.forPreload()) { | 
| 723         V8DOMActivityLogger* activityLogger = 0; | 736         V8DOMActivityLogger* activityLogger = 0; | 
| 724         if (request.options().initiatorInfo.name == FetchInitiatorTypeNames::xml
      httprequest) | 737         if (request.options().initiatorInfo.name == FetchInitiatorTypeNames::xml
      httprequest) | 
| 725             activityLogger = V8DOMActivityLogger::currentActivityLogger(); | 738             activityLogger = V8DOMActivityLogger::currentActivityLogger(); | 
| 726         else | 739         else | 
| (...skipping 602 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 1329 | 1342 | 
| 1330 void ResourceFetcher::willSendRequest(unsigned long identifier, ResourceRequest&
       request, const ResourceResponse& redirectResponse, const FetchInitiatorInfo& in
      itiatorInfo) | 1343 void ResourceFetcher::willSendRequest(unsigned long identifier, ResourceRequest&
       request, const ResourceResponse& redirectResponse, const FetchInitiatorInfo& in
      itiatorInfo) | 
| 1331 { | 1344 { | 
| 1332     context().dispatchWillSendRequest(m_documentLoader, identifier, request, red
      irectResponse, initiatorInfo); | 1345     context().dispatchWillSendRequest(m_documentLoader, identifier, request, red
      irectResponse, initiatorInfo); | 
| 1333 } | 1346 } | 
| 1334 | 1347 | 
| 1335 void ResourceFetcher::didReceiveResponse(const Resource* resource, const Resourc
      eResponse& response) | 1348 void ResourceFetcher::didReceiveResponse(const Resource* resource, const Resourc
      eResponse& response) | 
| 1336 { | 1349 { | 
| 1337     // If the response is fetched via ServiceWorker, the original URL of the res
      ponse could be different from the URL of the request. | 1350     // If the response is fetched via ServiceWorker, the original URL of the res
      ponse could be different from the URL of the request. | 
| 1338     if (response.wasFetchedViaServiceWorker()) { | 1351     if (response.wasFetchedViaServiceWorker()) { | 
| 1339         if (!canRequest(resource->type(), response.url(), resource->options(), f
      alse, FetchRequest::UseDefaultOriginRestrictionForType)) { | 1352         if (!canRequest(resource->type(), resource->resourceRequest(), response.
      url(), resource->options(), false, FetchRequest::UseDefaultOriginRestrictionForT
      ype)) { | 
| 1340             resource->loader()->cancel(); | 1353             resource->loader()->cancel(); | 
| 1341             context().dispatchDidFail(m_documentLoader, resource->identifier(), 
      ResourceError(errorDomainBlinkInternal, 0, response.url().string(), "Unsafe atte
      mpt to load URL " + response.url().elidedString() + " fetched by a ServiceWorker
      .")); | 1354             context().dispatchDidFail(m_documentLoader, resource->identifier(), 
      ResourceError(errorDomainBlinkInternal, 0, response.url().string(), "Unsafe atte
      mpt to load URL " + response.url().elidedString() + " fetched by a ServiceWorker
      .")); | 
| 1342             return; | 1355             return; | 
| 1343         } | 1356         } | 
| 1344     } | 1357     } | 
| 1345     context().dispatchDidReceiveResponse(m_documentLoader, resource->identifier(
      ), response, resource->loader()); | 1358     context().dispatchDidReceiveResponse(m_documentLoader, resource->identifier(
      ), response, resource->loader()); | 
| 1346 } | 1359 } | 
| 1347 | 1360 | 
| 1348 void ResourceFetcher::didReceiveData(const Resource* resource, const char* data,
       int dataLength, int encodedDataLength) | 1361 void ResourceFetcher::didReceiveData(const Resource* resource, const char* data,
       int dataLength, int encodedDataLength) | 
| 1349 { | 1362 { | 
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 1420     return false; | 1433     return false; | 
| 1421 } | 1434 } | 
| 1422 | 1435 | 
| 1423 bool ResourceFetcher::isLoadedBy(ResourceLoaderHost* possibleOwner) const | 1436 bool ResourceFetcher::isLoadedBy(ResourceLoaderHost* possibleOwner) const | 
| 1424 { | 1437 { | 
| 1425     return this == possibleOwner; | 1438     return this == possibleOwner; | 
| 1426 } | 1439 } | 
| 1427 | 1440 | 
| 1428 bool ResourceFetcher::canAccessRedirect(Resource* resource, ResourceRequest& req
      uest, const ResourceResponse& redirectResponse, ResourceLoaderOptions& options) | 1441 bool ResourceFetcher::canAccessRedirect(Resource* resource, ResourceRequest& req
      uest, const ResourceResponse& redirectResponse, ResourceLoaderOptions& options) | 
| 1429 { | 1442 { | 
| 1430     if (!canRequest(resource->type(), request.url(), options, resource->isUnused
      Preload(), FetchRequest::UseDefaultOriginRestrictionForType)) | 1443     if (!canRequest(resource->type(), request, request.url(), options, resource-
      >isUnusedPreload(), FetchRequest::UseDefaultOriginRestrictionForType)) | 
| 1431         return false; | 1444         return false; | 
| 1432     if (options.corsEnabled == IsCORSEnabled) { | 1445     if (options.corsEnabled == IsCORSEnabled) { | 
| 1433         SecurityOrigin* sourceOrigin = options.securityOrigin.get(); | 1446         SecurityOrigin* sourceOrigin = options.securityOrigin.get(); | 
| 1434         if (!sourceOrigin && document()) | 1447         if (!sourceOrigin && document()) | 
| 1435             sourceOrigin = document()->securityOrigin(); | 1448             sourceOrigin = document()->securityOrigin(); | 
| 1436 | 1449 | 
| 1437         String errorMessage; | 1450         String errorMessage; | 
| 1438         if (!CrossOriginAccessControl::handleRedirect(resource, sourceOrigin, re
      quest, redirectResponse, options, errorMessage)) { | 1451         if (!CrossOriginAccessControl::handleRedirect(resource, sourceOrigin, re
      quest, redirectResponse, options, errorMessage)) { | 
| 1439             if (resource->type() == Resource::Font) | 1452             if (resource->type() == Resource::Font) | 
| 1440                 toFontResource(resource)->setCORSFailed(); | 1453                 toFontResource(resource)->setCORSFailed(); | 
| (...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 1553 | 1566 | 
| 1554 void ResourceFetcher::trace(Visitor* visitor) | 1567 void ResourceFetcher::trace(Visitor* visitor) | 
| 1555 { | 1568 { | 
| 1556     visitor->trace(m_document); | 1569     visitor->trace(m_document); | 
| 1557     visitor->trace(m_loaders); | 1570     visitor->trace(m_loaders); | 
| 1558     visitor->trace(m_multipartLoaders); | 1571     visitor->trace(m_multipartLoaders); | 
| 1559     ResourceLoaderHost::trace(visitor); | 1572     ResourceLoaderHost::trace(visitor); | 
| 1560 } | 1573 } | 
| 1561 | 1574 | 
| 1562 } | 1575 } | 
| OLD | NEW | 
|---|