Angular.js: `$http` fails on LG webOS using the `file://` protocol as cookies aren't accessible

Created on 19 Dec 2016  路  4Comments  路  Source: angular/angular.js

Note: for support questions, please use one of these channels: https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#question. This repository's issues are reserved for feature requests and bug reports.

Do you want to request a feature or report a bug?

bug

What is the current behavior?

On LG webOS using the file:// protocol, $http fails because it calls $$cookieReader that throws SecurityError: DOM Exception 18.

$http calls $$cookieReader for CSRF checks: https://github.com/angular/angular.js/blob/v1.6.0/src/ng/http.js#L1287.

$$cookieReader throws the error when accessing rawDocument.cookie: https://github.com/angular/angular.js/blob/v1.6.0/src/ng/cookieReader.js#L27

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://plnkr.co or similar (template: http://plnkr.co/edit/tpl:yBpEi4).

Quite hard to reproduce, as you need an LG webOS device... Just make $$cookieReader throw an error?

What is the expected behavior?

Either $http or $$cookieReader could catch errors so that xsrfValue gets undefined if cookies aren't accessible?

What is the motivation / use case for changing the behavior?

To fix a bug :)

Which versions of Angular, and which browser / OS are affected by this issue? Did this work in previous versions of Angular? Please also test with the latest stable and snapshot (https://code.angularjs.org/snapshot/) versions.

Issue met with 1.6.0 on LG webOS.

Other information (e.g. stacktraces, related issues, suggestions how to fix)

Actual stack trace:

DOMException
  code: 18
  column: 42
  constructor: DOMExceptionConstructor
  line: 20114
  message: "SecurityError: DOM Exception 18"
  name: "SecurityError"
  sourceURL: "file:///.../libs/angular/angular.js"
  stack: "
    file:///.../libs/angular/angular.js:20114:42
    sendReq@file:///.../libs/angular/angular.js:12146:29
    serverRequest@file:///.../libs/angular/angular.js:11905:23
    processQueue@file:///.../libs/angular/angular.js:16643:39
    file:///.../libs/angular/angular.js:16683:39
    $eval@file:///.../libs/angular/angular.js:17958:28
    $digest@file:///.../libs/angular/angular.js:17772:36
    $apply@file:///.../libs/angular/angular.js:18066:31
    bootstrapApply@file:///.../libs/angular/angular.js:1841:21
    invoke@file:///.../libs/angular/angular.js:4839:24
    doBootstrap@file:///.../libs/angular/angular.js:1839:20
    bootstrap@file:///.../libs/angular/angular.js:1859:23
    angularInit@file:///.../libs/angular/angular.js:1744:14
    file:///.../libs/angular/angular.js:32890:16
    mightThrow@file:///.../libs/jquery/dist/jquery.js:3570:34
    file:///.../libs/jquery/dist/jquery.js:3638:22
  "
  __proto__: DOMExceptionPrototype

For now, I temporary decorate $$cookieReader to disable it:

.config(["$provide",
function ($provide) {
  $provide.decorator("$$cookieReader", [
  function () {
    return function () {
      return {};
    };
  }]);
}])
PRs plz! misc core low inconvenient bug

All 4 comments

This is a quite uncommon usecase (and has an easy - even if not ideal - workaround), but if you feel like it you can submit a PR fixing it. Basically, we could replace this line with something like:

-var currentCookieString = rawDocument.cookie || '';
+var currentCookieString = safeGetCookie(rawDocument);
+...
+function safeGetCookie(document) {
+  try {
+    return document.cookie || '';
+  } catch (err) {
+    return '';
+  }

Quite hard to test though... I've tried to play with a getter:

it('should return an empty string if cookie cannot be read', function() {
  // see #15523 - sometimes reading cookies throws an error
  document.cookie = 'cookie_name=cookie_value';
  Object.defineProperty(document, 'cookie', {
    configurable: true,
    get: function() { throw new Error('#15523'); }
  });
  expect($$cookieReader()['cookie_name']).toBeUndefined();
  Object.defineProperty(document, 'cookie', {
    writable: true
  });
});

But this breaks many other tests. Works as expected on this fiddle though: https://jsfiddle.net/31k61c3L/.

Any idea? Maybe I simply can't brutalise window.document like that...

Maybe I simply can't brutalise window.document like that...

No you can't (or rther you shouldn't). That's where DI comes into play :smiley:
$$cookieReader uses the injected $document to retrieve the rawDocument (as $document[0]). All you need to do, is provide a mock $document for your test, such that $document[0].cookie throws.

I haven't looked at the testsuite, but something along the following lines should work:

it('should return an empty string if cookie cannot be read', function() {
  var cookieSpy = jasmine.createSpy('cookie').and.throwError('Can\'t touch this!');
  var mockDocument = {};
  Object.defineProperty(mockDocument, 'cookie', {get: cookieSpy});

  module(function($provide) {
    $provide.value($document, [mockDocument]);
  });

  inject(function($$cookieReader) {
    expect($$cookieReader()).toEqual({});
    expect(cookieSpy).toHaveBeenCalled();
  });
});

EDIT:
I just saw how the $$cookieReader testsuite is set up. You need to have a separate describe block that the existing beforeEach/afterEach do not affect. (Let me know if you need help with that.)

Thanks @gkalpak, very educative!

Was this page helpful?
0 / 5 - 0 ratings