fix(service-worker): ignore passive mixed content requests (#25994)
Although [passive mixed content][1] requests (like images) only produce a warning without a ServiceWorker, fetching it via a ServiceWorker results in an error. See https://github.com/angular/angular/issues/23012#issuecomment-376430187 for more details. This commit makes the ServiceWorker ignore such requests and let them be handled by the browser directly to avoid breaking apps that would work without the ServiceWorker. [1]: https://developers.google.com/web/fundamentals/security/prevent-mixed-content/what-is-mixed-content#passive_mixed_content Fixes #23012 PR Close #25994
This commit is contained in:
parent
95989a12dd
commit
48214e2a05
@ -176,11 +176,12 @@ export class Driver implements Debuggable, UpdateSource {
|
|||||||
*/
|
*/
|
||||||
private onFetch(event: FetchEvent): void {
|
private onFetch(event: FetchEvent): void {
|
||||||
const req = event.request;
|
const req = event.request;
|
||||||
|
const scopeUrl = this.scope.registration.scope;
|
||||||
|
const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl);
|
||||||
|
|
||||||
// The only thing that is served unconditionally is the debug page.
|
// The only thing that is served unconditionally is the debug page.
|
||||||
if (this.adapter.parseUrl(req.url, this.scope.registration.scope).path === '/ngsw/state') {
|
if (requestUrlObj.path === '/ngsw/state') {
|
||||||
// Allow the debugger to handle the request, but don't affect SW state in any
|
// Allow the debugger to handle the request, but don't affect SW state in any other way.
|
||||||
// other way.
|
|
||||||
event.respondWith(this.debugger.handleFetch(req));
|
event.respondWith(this.debugger.handleFetch(req));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -197,6 +198,16 @@ export class Driver implements Debuggable, UpdateSource {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Although "passive mixed content" (like images) only produces a warning without a
|
||||||
|
// ServiceWorker, fetching it via a ServiceWorker results in an error. Let such requests be
|
||||||
|
// handled by the browser, since handling with the ServiceWorker would fail anyway.
|
||||||
|
// See https://github.com/angular/angular/issues/23012#issuecomment-376430187 for more details.
|
||||||
|
if (requestUrlObj.origin.startsWith('http:') && scopeUrl.startsWith('https:')) {
|
||||||
|
// Still, log the incident for debugging purposes.
|
||||||
|
this.debugger.log(`Ignoring passive mixed content request: Driver.fetch(${req.url})`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// When opening DevTools in Chrome, a request is made for the current URL (and possibly related
|
// When opening DevTools in Chrome, a request is made for the current URL (and possibly related
|
||||||
// resources, e.g. scripts) with `cache: 'only-if-cached'` and `mode: 'no-cors'`. These request
|
// resources, e.g. scripts) with `cache: 'only-if-cached'` and `mode: 'no-cors'`. These request
|
||||||
// will eventually fail, because `only-if-cached` is only allowed to be used with
|
// will eventually fail, because `only-if-cached` is only allowed to be used with
|
||||||
@ -204,7 +215,7 @@ export class Driver implements Debuggable, UpdateSource {
|
|||||||
// This is likely a bug in Chrome DevTools. Avoid handling such requests.
|
// This is likely a bug in Chrome DevTools. Avoid handling such requests.
|
||||||
// (See also https://github.com/angular/angular/issues/22362.)
|
// (See also https://github.com/angular/angular/issues/22362.)
|
||||||
// TODO(gkalpak): Remove once no longer necessary (i.e. fixed in Chrome DevTools).
|
// TODO(gkalpak): Remove once no longer necessary (i.e. fixed in Chrome DevTools).
|
||||||
if ((req.cache as string) === 'only-if-cached' && req.mode !== 'same-origin') {
|
if (req.cache === 'only-if-cached' && req.mode !== 'same-origin') {
|
||||||
// Log the incident only the first time it happens, to avoid spamming the logs.
|
// Log the incident only the first time it happens, to avoid spamming the logs.
|
||||||
if (!this.loggedInvalidOnlyIfCachedRequest) {
|
if (!this.loggedInvalidOnlyIfCachedRequest) {
|
||||||
this.loggedInvalidOnlyIfCachedRequest = true;
|
this.loggedInvalidOnlyIfCachedRequest = true;
|
||||||
|
@ -204,8 +204,6 @@ const brokenServer =
|
|||||||
|
|
||||||
const server404 = new MockServerStateBuilder().withStaticFiles(dist).build();
|
const server404 = new MockServerStateBuilder().withStaticFiles(dist).build();
|
||||||
|
|
||||||
const scope = new SwTestHarnessBuilder().withServerState(server).build();
|
|
||||||
|
|
||||||
const manifestHash = sha1(JSON.stringify(manifest));
|
const manifestHash = sha1(JSON.stringify(manifest));
|
||||||
const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate));
|
const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate));
|
||||||
|
|
||||||
@ -1008,6 +1006,38 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate));
|
|||||||
expect(await requestFoo('only-if-cached', 'no-cors')).toBeNull();
|
expect(await requestFoo('only-if-cached', 'no-cors')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async_it('ignores passive mixed content requests ', async() => {
|
||||||
|
const scopeFetchSpy = spyOn(scope, 'fetch').and.callThrough();
|
||||||
|
const getRequestUrls = () => scopeFetchSpy.calls.allArgs().map(args => args[0].url);
|
||||||
|
|
||||||
|
const httpScopeUrl = 'http://mock.origin.dev';
|
||||||
|
const httpsScopeUrl = 'https://mock.origin.dev';
|
||||||
|
const httpRequestUrl = 'http://other.origin.sh/unknown.png';
|
||||||
|
const httpsRequestUrl = 'https://other.origin.sh/unknown.pnp';
|
||||||
|
|
||||||
|
// Registration scope: `http:`
|
||||||
|
(scope.registration.scope as string) = httpScopeUrl;
|
||||||
|
|
||||||
|
await makeRequest(scope, httpRequestUrl);
|
||||||
|
await makeRequest(scope, httpsRequestUrl);
|
||||||
|
const requestUrls1 = getRequestUrls();
|
||||||
|
|
||||||
|
expect(requestUrls1).toContain(httpRequestUrl);
|
||||||
|
expect(requestUrls1).toContain(httpsRequestUrl);
|
||||||
|
|
||||||
|
scopeFetchSpy.calls.reset();
|
||||||
|
|
||||||
|
// Registration scope: `https:`
|
||||||
|
(scope.registration.scope as string) = httpsScopeUrl;
|
||||||
|
|
||||||
|
await makeRequest(scope, httpRequestUrl);
|
||||||
|
await makeRequest(scope, httpsRequestUrl);
|
||||||
|
const requestUrls2 = getRequestUrls();
|
||||||
|
|
||||||
|
expect(requestUrls2).not.toContain(httpRequestUrl);
|
||||||
|
expect(requestUrls2).toContain(httpsRequestUrl);
|
||||||
|
});
|
||||||
|
|
||||||
describe('Backwards compatibility with v5', () => {
|
describe('Backwards compatibility with v5', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const serverV5 = new MockServerStateBuilder()
|
const serverV5 = new MockServerStateBuilder()
|
||||||
|
@ -181,7 +181,7 @@ export class SwTestHarness implements ServiceWorkerGlobalScope, Adapter, Context
|
|||||||
return {origin: obj.origin, path: obj.pathname};
|
return {origin: obj.origin, path: obj.pathname};
|
||||||
} else {
|
} else {
|
||||||
const obj = require('url').parse(url);
|
const obj = require('url').parse(url);
|
||||||
return {origin: obj.origin, path: obj.pathname};
|
return {origin: `${obj.protocol}//${obj.host}`, path: obj.pathname};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user