Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(service-worker): ignore passive mixed content requests #25994

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 15 additions & 4 deletions packages/service-worker/worker/src/driver.ts
Expand Up @@ -176,11 +176,12 @@ export class Driver implements Debuggable, UpdateSource {
*/
private onFetch(event: FetchEvent): void {
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.
if (this.adapter.parseUrl(req.url, this.scope.registration.scope).path === '/ngsw/state') {
// Allow the debugger to handle the request, but don't affect SW state in any
// other way.
if (requestUrlObj.path === '/ngsw/state') {
// Allow the debugger to handle the request, but don't affect SW state in any other way.
event.respondWith(this.debugger.handleFetch(req));
return;
}
Expand All @@ -197,14 +198,24 @@ export class Driver implements Debuggable, UpdateSource {
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
// 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
// `mode: 'same-origin'`.
// This is likely a bug in Chrome DevTools. Avoid handling such requests.
// (See also https://github.com/angular/angular/issues/22362.)
// 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.
if (!this.loggedInvalidOnlyIfCachedRequest) {
this.loggedInvalidOnlyIfCachedRequest = true;
Expand Down
34 changes: 32 additions & 2 deletions packages/service-worker/worker/test/happy_spec.ts
Expand Up @@ -204,8 +204,6 @@ const brokenServer =

const server404 = new MockServerStateBuilder().withStaticFiles(dist).build();

const scope = new SwTestHarnessBuilder().withServerState(server).build();

const manifestHash = sha1(JSON.stringify(manifest));
const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate));

Expand Down Expand Up @@ -1008,6 +1006,38 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate));
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', () => {
beforeEach(() => {
const serverV5 = new MockServerStateBuilder()
Expand Down
2 changes: 1 addition & 1 deletion packages/service-worker/worker/testing/scope.ts
Expand Up @@ -181,7 +181,7 @@ export class SwTestHarness implements ServiceWorkerGlobalScope, Adapter, Context
return {origin: obj.origin, path: obj.pathname};
} else {
const obj = require('url').parse(url);
return {origin: obj.origin, path: obj.pathname};
return {origin: `${obj.protocol}//${obj.host}`, path: obj.pathname};
}
}

Expand Down