From ed26f6af2a4334bfdee6e70bf86307ba4c527402 Mon Sep 17 00:00:00 2001 From: Ben Kelly Date: Tue, 14 Jul 2015 13:11:26 -0700 Subject: [PATCH] Bug 1173912 Fail opaque responses for client requests. r=ehsan --- docshell/base/nsDocShell.cpp | 2 + dom/base/nsContentUtils.cpp | 2 + dom/fetch/InternalRequest.cpp | 39 ++++++++++++++++++ dom/fetch/InternalRequest.h | 10 +++++ dom/locales/en-US/chrome/dom/dom.properties | 2 + dom/workers/ServiceWorkerEvents.cpp | 27 +++++++++--- .../app-protocol/application.zip | Bin 3463 -> 3451 bytes .../app-protocol/controlled.html | 10 +++-- xpcom/base/ErrorList.h | 2 + 9 files changed, 84 insertions(+), 10 deletions(-) diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 46f0f5017dc..0e4550100d8 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -5203,6 +5203,7 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, case NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE: case NS_ERROR_INTERCEPTED_ERROR_RESPONSE: case NS_ERROR_INTERCEPTED_USED_RESPONSE: + case NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION: // ServiceWorker intercepted request, but something went wrong. nsContentUtils::MaybeReportInterceptionErrorToConsole(GetDocument(), aError); @@ -7840,6 +7841,7 @@ nsDocShell::EndPageLoad(nsIWebProgress* aProgress, aStatus == NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE || aStatus == NS_ERROR_INTERCEPTED_ERROR_RESPONSE || aStatus == NS_ERROR_INTERCEPTED_USED_RESPONSE || + aStatus == NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION || NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) { // Errors to be shown for any frame DisplayLoadError(aStatus, url, nullptr, aChannel); diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 8076f8d98c7..5abc257bb38 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -3432,6 +3432,8 @@ nsContentUtils::MaybeReportInterceptionErrorToConsole(nsIDocument* aDocument, messageName = "InterceptedErrorResponse"; } else if (aError == NS_ERROR_INTERCEPTED_USED_RESPONSE) { messageName = "InterceptedUsedResponse"; + } else if (aError == NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION) { + messageName = "ClientRequestOpaqueInterception"; } if (messageName) { diff --git a/dom/fetch/InternalRequest.cpp b/dom/fetch/InternalRequest.cpp index 448d5d4ff07..86b5c42e563 100644 --- a/dom/fetch/InternalRequest.cpp +++ b/dom/fetch/InternalRequest.cpp @@ -195,5 +195,44 @@ InternalRequest::MapContentPolicyTypeToRequestContext(nsContentPolicyType aConte return context; } +bool +InternalRequest::IsNavigationRequest() const +{ + // https://fetch.spec.whatwg.org/#navigation-request-context + // + // A navigation request context is one of "form", "frame", "hyperlink", + // "iframe", "internal" (as long as context frame type is not "none"), + // "location", "metarefresh", and "prerender". + // + // TODO: include equivalent check for "form" context + // TODO: include equivalent check for "prerender" context + return mContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT || + mContentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT || + mContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_FRAME || + mContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IFRAME || + mContentPolicyType == nsIContentPolicy::TYPE_REFRESH; +} + +bool +InternalRequest::IsWorkerRequest() const +{ + // https://fetch.spec.whatwg.org/#worker-request-context + // + // A worker request context is one of "serviceworker", "sharedworker", and + // "worker". + // + // Note, service workers are not included here because currently there is + // no way to generate a Request with a "serviceworker" RequestContext. + // ServiceWorker scripts cannot be intercepted. + return mContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_WORKER || + mContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER; +} + +bool +InternalRequest::IsClientRequest() const +{ + return IsNavigationRequest() || IsWorkerRequest(); +} + } // namespace dom } // namespace mozilla diff --git a/dom/fetch/InternalRequest.h b/dom/fetch/InternalRequest.h index 92b6cc3bab1..d20ac226f00 100644 --- a/dom/fetch/InternalRequest.h +++ b/dom/fetch/InternalRequest.h @@ -356,6 +356,16 @@ public: mCreatedByFetchEvent = false; } + bool + IsNavigationRequest() const; + + bool + IsWorkerRequest() const; + + bool + IsClientRequest() const; + + private: // Does not copy mBodyStream. Use fallible Clone() for complete copy. explicit InternalRequest(const InternalRequest& aOther); diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index 3c4b63fb43d..c76539dbf82 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -176,3 +176,5 @@ BadOpaqueInterceptionRequestMode=A ServiceWorker passed an opaque Response to Fe InterceptedErrorResponse=A ServiceWorker passed an Error Response to FetchEvent.respondWith(). This typically means the ServiceWorker performed an invalid fetch() call. # LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", or "Response.clone()". InterceptedUsedResponse=A ServiceWorker passed a used Response to FetchEvent.respondWith(). The body of a Response may only be read once. Use Response.clone() to access the body multiple times. +# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", "FetchEvent.request", or "Worker". +ClientRequestOpaqueInterception=A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while FetchEvent.request was a client request. A client request is generally a browser navigation or top-level Worker script. diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp index ce27b34ae9b..cb925d9c68a 100644 --- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -149,15 +149,17 @@ class RespondWithHandler final : public PromiseNativeHandler nsMainThreadPtrHandle mInterceptedChannel; nsMainThreadPtrHandle mServiceWorker; RequestMode mRequestMode; + bool mIsClientRequest; public: NS_DECL_ISUPPORTS RespondWithHandler(nsMainThreadPtrHandle& aChannel, nsMainThreadPtrHandle& aServiceWorker, - RequestMode aRequestMode) + RequestMode aRequestMode, bool aIsClientRequest) : mInterceptedChannel(aChannel) , mServiceWorker(aServiceWorker) , mRequestMode(aRequestMode) + , mIsClientRequest(aIsClientRequest) { } @@ -262,15 +264,26 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle aValu return; } - // Section 4.2, step 2.2 "If either response's type is "opaque" and request's - // mode is not "no-cors" or response's type is error, return a network error." + // Section 4.2, step 2.2: + // If one of the following conditions is true, return a network error: + // * response's type is "error". + // * request's mode is not "no-cors" and response's type is "opaque". + // * request is a client request and response's type is neither "basic" + // nor "default". + + if (response->Type() == ResponseType::Error) { + autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_ERROR_RESPONSE); + return; + } + if (response->Type() == ResponseType::Opaque && mRequestMode != RequestMode::No_cors) { autoCancel.SetCancelStatus(NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE); return; } - if (response->Type() == ResponseType::Error) { - autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_ERROR_RESPONSE); + if (mIsClientRequest && response->Type() != ResponseType::Basic && + response->Type() != ResponseType::Default) { + autoCancel.SetCancelStatus(NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION); return; } @@ -357,9 +370,11 @@ FetchEvent::RespondWith(const ResponseOrPromise& aArg, ErrorResult& aRv) } else if (aArg.IsPromise()) { promise = &aArg.GetAsPromise(); } + nsRefPtr ir = mRequest->GetInternalRequest(); mWaitToRespond = true; nsRefPtr handler = - new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode()); + new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode(), + ir->IsClientRequest()); promise->AppendNativeHandler(handler); } diff --git a/dom/workers/test/serviceworkers/app-protocol/application.zip b/dom/workers/test/serviceworkers/app-protocol/application.zip index f8669cd77e4a380a774e1672fd64fa37e2306777..7d4be7f4617e28c7a259cfda1a45e324135521db 100644 GIT binary patch delta 1749 zcmZpd{w-A>;LXe;!oa}5!Eht-t()pR#)BP<3=BV+85sB(WEhh3^Gb^Hb8=Es^fF3v zb3#Kn8JIQrmV|B^~!{deaqd0ALhQ#D0ZQ7+r?e_+bg zN2#kLLb*B5==%HW#p)65#D?u#qlseI=!ocl%f zMutz^+*yqEZ+cY9-E>=9Z#0DF@?Wgjp>d2YMI}=I8PC*C-5GHOJHnrF_9RDcy~Q#& zC5~rR;@YjXxz`)^?7UF*>Wg)FluP!~q#DPnAD!=`uGm>;M|pC8nXuz|)j^l-J|0$f z(k%D>Wj)#RZKBQH>l0XN%HDs7U)R?5V6N*{ot;I06*hkPU!53Vtyn5#ly$5(r(9lNww&>Ep!oyO86V0)amodZQwE0ZR(mGjWRw-HR7nr@;YN$I$=poR z%sWj_Ox9#l5bc{&8Y+jbVsji*HzPC1kT=Y#I1Eu^Nii3)yX(fpcWQar-i2P$uluZd_C#HWc}0*v1_)AKEL&QdGYMj17GFxXE{FBiA*eg zDWvq7f1mxUPVsdg=coL;zmYd;&yky-S1j-G{+Zd=rZ-jH2fnYBaB$iB)6czY^LL|T`OE&?IC4~@b@dAUHDO18&-0n+^7WRW zsju~!hwCF>FwM3+J7waIJ+s=hvL$sjwI4i^u@iZDt@xe7lGgffQZWbao_(6#u|zlR z*Qs+`{>c1vc5V57V(yA{J0EXN|L&1px>6(SmX?-T-1^9x3Vv%G0t~HofmGj`qp6rq--`DTsOT07B zF?Z=KttlTqb6&d7a`*ie{Y@7Y#okPidE)rPdy9|_m%jXuIprMczZBEv<=6_!9&b@P z{_ghe>xcLhJ9A9s?w#z)s@?P9YlhRwsD|3A-R8Tknf(;JCx3`I^)bG1+v5)hs`b?_ z_e-pQ&vWB!hv=GT>-S3M3WdvFIGOg(|GM=1z43Ft?Po+3QNVOIS(9B>6qKSclJn+h zc57yEdY;K;3{KA&N+v(#imJyf=On;+1z7AcY-t2BF-kpFNU4Wb;stm!GRZN+Gn(5Q z+lF`iKvByp+@m zX!#4&3(Bc@^n!A$0gq(;mc}Q*qB%D)FEcH*xJ0i!H7T*6Ae0EpF*7r=S6W#ZU|AdJ z7EnZBcMAtF5I7h>5q5z`(qv1c0I*mn0XYOzDB#rz)(8p#SzbxzEsYJ6EqPTnv3i=7 S4X73v)a(pSz+h752Jrye{i1^a delta 1783 zcmew@)h=Bh;LXe;!oa}5!EiqAyjxk9Ku$9w1H(sV1_pix8HVKiypp2)oSf7Yy^NCF zoX`+X2IgxE>O$`cH-?s0a5FHnd;_Wm8?ZGv*88x5NbUXg9sV~Hwurd2ZWW!`m48a2 z!(Y`qKX;3_Q&F_+rvG`D&hQ`cb3Ehi{LWJS`Lg=&OW2=Z|NQjsS=N<>cb_aasp0U< zPFQ+>hb~u+LVT0!p)G1tjZ&`Oo2+8#)#t}|+2RWGwIXE`q3jB`i-wEXy+51q9cP}g zC}dBDh&H#PAP;-5(@9&OI~gZde7?kS&c>`RL6-YyB>S;li*-8tGVP>7*FKSR^3ks4 zF0)b8_dCl_zs+ru^x|uWoKg;~TGsq=$$pJPQcof_`Q0%NQQMSYb?{->Om_pm)2Gg+ zFPMDhQ=I9Vx!c~q6`F88JxYI0-Rmgn%U&xv=1=sy|M0iiR{wLe!Y(n*75W}?UgC8d zr>FYnxP;%~%FCy&PdS;->v=%^JKz7OtK>QEMTNX>(l+N8;{LxsraoIg;CbWDH=Wka zbzAm-eZD@%L6fJTrD$VzjznSWinjte=PKU1)=m1|B`u;mmF0QWYv-SuyqSNdT_{<4 ziZ}3*y2fJpWIyIJ;jtSYPk0j!icT(IbTTk}UA1HKJw{nkTh%1Y=$b6RB+a}x?dW7Z zCI!)B^@W%!HYYQ6Gctn=`N*t_!w_wj6mtfbi*9#rEp&=yVqoCmU|?Vcrkdh%y{uwz zT1gOT3=1tim)iobk`s4HR*XIXlwKqMG`6U`? ztm7T?Y1hRQEo;4<{P#^s+_K@F;N1Lhul~GsEbFdUT;`EVKgqS)d%D2P`VW5t{r^0a z+OP8Ye_!r}N2=%Mx!b13oboR=DOqW=&~bXYmdC@W^z;eIcQhaTVVYOX@V2L3cgG@Y zXRqgpoC{b5V?tic%)RiW_Vty$M$H-vV&9ABDljb8dedzh^WAdEOyh#LIcse;In2rb z#;`BHm(e9;_Irzr9^BIMEc96-DxAA>&I*P%EM`mAB=XxuGT**e@i4^M(`iM@F;lBkzr*9( z4f4J(*cRj~>hOtK3$+GOSqMxx9 zN1OfGt(lq4Lk~~xUSTfm%Te2Qh+M%W10B=Sn zIc9h+b9-ak@Qxp-0fgBZkPK)%12iWsKVPq;q9ingm4O+Q7qJ=!DS?3*(rw4W&AaP? z+5~_lFc*VF48l2$GQcdBnU|7U0WFJx27lEIAz#RuJz2W6W(K-$izPpw~_Siwba9Sp%H~4(&!6b_Rx$ z)Z!9w*#R^S6tQ^x28vukUP|bAxyQlS_(q diff --git a/dom/workers/test/serviceworkers/app-protocol/controlled.html b/dom/workers/test/serviceworkers/app-protocol/controlled.html index 27d16c19f9d..ce8ce987e0a 100644 --- a/dom/workers/test/serviceworkers/app-protocol/controlled.html +++ b/dom/workers/test/serviceworkers/app-protocol/controlled.html @@ -21,10 +21,12 @@ function runTests() { return testFetchAppResource('test_custom_content_type', 'customContentType', 'text/html'); }) - .then(testRedirectedResponse) - .then(testRedirectedHttpsResponse) - .then(testCachedRedirectedResponse) - .then(testCachedRedirectedHttpsResponse) + // XXX: Cross-origin interceptions without CORS result in opaque responses + // which are illegal for navigations like iframes. (bug 1183313) + //.then(testRedirectedResponse) + //.then(testRedirectedHttpsResponse) + //.then(testCachedRedirectedResponse) + //.then(testCachedRedirectedHttpsResponse) .then(done); } diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h index 13a42de1990..e05c9dd70b2 100644 --- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -331,6 +331,8 @@ ERROR(NS_ERROR_INTERCEPTED_ERROR_RESPONSE, FAILURE(103)), /* Service worker intercepted with a response with bodyUsed set to true */ ERROR(NS_ERROR_INTERCEPTED_USED_RESPONSE, FAILURE(104)), + /* Service worker intercepted a client request with an opaque response */ + ERROR(NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION, FAILURE(105)), #undef MODULE