diff options
| author | Elliott Sprehn | 2010-10-27 19:06:40 -0700 | 
|---|---|---|
| committer | Elliott Sprehn | 2010-10-29 11:40:56 -0700 | 
| commit | 5524d2b0fb9b87ef0d1beec092337f836a1043a8 (patch) | |
| tree | 0aa57ceb1fa474dc4c75e06a22c114dc0d0b8256 | |
| parent | d4839bac3288bbf97116bd0adf9d59637889dd9e (diff) | |
| download | angular.js-5524d2b0fb9b87ef0d1beec092337f836a1043a8.tar.bz2 | |
Check if file exists (not a 404) and that document is accessible and not using file:// URLs in Application
| -rw-r--r-- | lib/nodeserver/server.js | 34 | ||||
| -rw-r--r-- | src/scenario/Application.js | 66 | ||||
| -rw-r--r-- | src/scenario/Scenario.js | 1 | ||||
| -rw-r--r-- | src/scenario/dsl.js | 4 | ||||
| -rw-r--r-- | test/scenario/ApplicationSpec.js | 106 | 
5 files changed, 181 insertions, 30 deletions
diff --git a/lib/nodeserver/server.js b/lib/nodeserver/server.js index 08f13a99..f91f6afa 100644 --- a/lib/nodeserver/server.js +++ b/lib/nodeserver/server.js @@ -8,10 +8,8 @@ var DEFAULT_PORT = 8000;  function main(argv) {    new HttpServer({ -    'GET': (function() {  -      var servlet = new StaticServlet(); -      return servlet.handleRequest.bind(servlet) -    })() +    'GET': createServlet(StaticServlet), +    'HEAD': createServlet(StaticServlet)    }).start(Number(argv[2]) || DEFAULT_PORT);  } @@ -22,6 +20,11 @@ function escapeHtml(value) {      replace('"', '"');  } +function createServlet(Class) { +  var servlet = new Class(); +  return servlet.handleRequest.bind(servlet); +} +  /**   * An Http server implementation that uses a map of methods to decide   * action routing. @@ -61,11 +64,10 @@ HttpServer.prototype.handleRequest_ = function(req, res) {    }  }; -  /**   * Handles static content.   */ -function  StaticServlet() {} +function StaticServlet() {}  StaticServlet.MimeMap = {    'txt': 'text/plain', @@ -164,13 +166,17 @@ StaticServlet.prototype.sendFile_ = function(req, res, path) {      'Content-Type': StaticServlet.        MimeMap[path.split('.').pop()] || 'text/plain'    }); -  file.on('data', res.write.bind(res)); -  file.on('close', function() { +  if (req.method === 'HEAD') {      res.end(); -  }); -  file.on('error', function(error) { -    self.sendError_(req, res, error); -  }); +  } else { +    file.on('data', res.write.bind(res)); +    file.on('close', function() { +      res.end(); +    }); +    file.on('error', function(error) { +      self.sendError_(req, res, error); +    }); +  }  };  StaticServlet.prototype.sendDirectory_ = function(req, res, path) { @@ -207,6 +213,10 @@ StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {    res.writeHead(200, {      'Content-Type': 'text/html'    }); +  if (req.method === 'HEAD') { +    res.end(); +    return; +  }    res.write('<!doctype html>\n');    res.write('<title>' + escapeHtml(path) + '</title>\n');    res.write('<style>\n'); diff --git a/src/scenario/Application.js b/src/scenario/Application.js index e2d34551..eacf3c7b 100644 --- a/src/scenario/Application.js +++ b/src/scenario/Application.js @@ -38,27 +38,70 @@ angular.scenario.Application.prototype.getWindow_ = function() {  };  /** + * Checks that a URL would return a 2xx success status code. Callback is called + * with no arguments on success, or with an error on failure. + * + * Warning: This requires the server to be able to respond to HEAD requests  + * and not modify the state of your application. + * + * @param {string} url Url to check + * @param {Function} callback function(error) that is called with result. + */ +angular.scenario.Application.prototype.checkUrlStatus_ = function(url, callback) { +  var self = this; +  _jQuery.ajax({ +    url: url, +    type: 'HEAD', +    complete: function(request) { +      if (request.status < 200 || request.status >= 300) { +        if (!request.status) { +          callback.call(self, 'Sandbox Error: Cannot access ' + url); +        } else { +          callback.call(self, request.status + ' ' + request.statusText); +        } +      } else { +        callback.call(self); +      } +    } +  }); +}; + +/**   * Changes the location of the frame.   *   * @param {string} url The URL. If it begins with a # then only the    *   hash of the page is changed. - * @param {Function} onloadFn function($window, $document) + * @param {Function} loadFn function($window, $document) Called when frame loads. + * @param {Function} errorFn function(error) Called if any error when loading.   */ -angular.scenario.Application.prototype.navigateTo = function(url, onloadFn) { +angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorFn) {    var self = this;    var frame = this.getFrame_(); -  if (url.charAt(0) === '#') { +  //TODO(esprehn): Refactor to use rethrow() +  errorFn = errorFn || function(e) { throw e; }; +  if (/^file:\/\//.test(url)) { +    errorFn('Sandbox Error: Cannot load file:// URL.'); +  } else if (url.charAt(0) === '#') {      url = frame.attr('src').split('#')[0] + url;      frame.attr('src', url); -    this.executeAction(onloadFn); +    this.executeAction(loadFn);    } else {      frame.css('display', 'none').attr('src', 'about:blank'); -    this.context.find('#test-frames').append('<iframe>'); -    frame = this.getFrame_(); -    frame.load(function() { -      self.executeAction(onloadFn); -      frame.unbind(); -    }).attr('src', url); +    this.checkUrlStatus_(url, function(error) { +      if (error) { +        return errorFn(error); +      } +      self.context.find('#test-frames').append('<iframe>'); +      frame = this.getFrame_(); +      frame.load(function() { +        frame.unbind(); +        try { +          self.executeAction(loadFn); +        } catch (e) { +          errorFn(e); +        } +      }).attr('src', url); +    });    }    this.context.find('> h2 a').attr('href', url).text(url);  }; @@ -73,6 +116,9 @@ angular.scenario.Application.prototype.navigateTo = function(url, onloadFn) {  angular.scenario.Application.prototype.executeAction = function(action) {    var self = this;    var $window = this.getWindow_(); +  if (!$window.document) { +    throw 'Sandbox Error: Application document not accessible.'; +  }    if (!$window.angular) {      return action.call(this, $window, _jQuery($window.document));    } diff --git a/src/scenario/Scenario.js b/src/scenario/Scenario.js index 14d530ac..d141c42b 100644 --- a/src/scenario/Scenario.js +++ b/src/scenario/Scenario.js @@ -1,3 +1,4 @@ +  /**   * Setup file for the Scenario.   * Must be first in the compilation/bootstrap list. diff --git a/src/scenario/dsl.js b/src/scenario/dsl.js index fcc2e5b2..358f1fce 100644 --- a/src/scenario/dsl.js +++ b/src/scenario/dsl.js @@ -62,7 +62,7 @@ angular.scenario.dsl('navigateTo', function() {        }        application.navigateTo(url, function() {          done(null, url); -      }); +      }, done);      });    };  }); @@ -271,7 +271,7 @@ angular.scenario.dsl('element', function() {        if (href && elements[0].nodeName.toUpperCase() === 'A') {          this.application.navigateTo(href, function() {            done(); -        }); +        }, done);        } else {          done();        } diff --git a/test/scenario/ApplicationSpec.js b/test/scenario/ApplicationSpec.js index 3e1c862d..75a7e9c9 100644 --- a/test/scenario/ApplicationSpec.js +++ b/test/scenario/ApplicationSpec.js @@ -1,15 +1,24 @@  describe('angular.scenario.Application', function() {    var app, frames; +  function callLoadHandlers(app) { +    var handlers = app.getFrame_().data('events').load; +    expect(handlers).toBeDefined(); +    expect(handlers.length).toEqual(1); +    handlers[0].handler(); +  } +    beforeEach(function() {      frames = _jQuery("<div></div>");      app = new angular.scenario.Application(frames); +    app.checkUrlStatus_ = function(url, callback) { +      callback.call(this); +    };    });    it('should return new $window and $document after navigate', function() {      var called;      var testWindow, testDocument, counter = 0; -    app.navigateTo = noop;      app.getWindow_ = function() {        return {x:counter++, document:{x:counter++}};      }; @@ -50,6 +59,31 @@ describe('angular.scenario.Application', function() {      expect(app.getFrame_().attr('test')).toBeFalsy();    }); +  it('should call error handler if document not accessible', function() { +    app.getWindow_ = function() { +      return {}; +    }; +    app.navigateTo('about:blank', angular.noop, function(error) { +      expect(error).toMatch(/Sandbox Error/); +    }); +    callLoadHandlers(app); +  }); + +  it('should call error handler if using file:// URL', function() { +    app.navigateTo('file://foo/bar.txt', angular.noop, function(error) { +      expect(error).toMatch(/Sandbox Error/); +    }); +  }); + +  it('should call error handler if status check fails', function() { +    app.checkUrlStatus_ = function(url, callback) { +      callback.call(this, 'Example Error'); +    }; +    app.navigateTo('about:blank', angular.noop, function(error) { +      expect(error).toEqual('Example Error'); +    }); +  }); +    it('should hide old iframes and navigate to about:blank', function() {      app.navigateTo('about:blank#foo');      app.navigateTo('about:blank#bar'); @@ -70,15 +104,12 @@ describe('angular.scenario.Application', function() {    it('should call onload handler when frame loads', function() {      var called;      app.getWindow_ = function() { -      return {}; +      return {document: {}};      };      app.navigateTo('about:blank', function($window, $document) {        called = true;      }); -    var handlers = app.getFrame_().data('events').load; -    expect(handlers).toBeDefined(); -    expect(handlers.length).toEqual(1); -    handlers[0].handler(); +    callLoadHandlers(app);      expect(called).toBeTruthy();    }); @@ -113,4 +144,67 @@ describe('angular.scenario.Application', function() {      expect(handlers.length).toEqual(1);      handlers[0]();    }); + +  describe('jQuery ajax', function() { +    var options; +    var response; +    var jQueryAjax; + +    beforeEach(function() { +      response = { +        status: 200, +        statusText: 'OK' +      }; +      jQueryAjax = _jQuery.ajax; +      _jQuery.ajax = function(opts) { +        options = opts; +        opts.complete.call(this, response); +      }; +      app.checkUrlStatus_ = angular.scenario.Application. +        prototype.checkUrlStatus_; +    }); + +    afterEach(function() { +      _jQuery.ajax = jQueryAjax; +    }); + +    it('should perform a HEAD request to verify file existence', function() { +      app.navigateTo('http://www.google.com/', angular.noop, angular.noop); +      expect(options.type).toEqual('HEAD'); +      expect(options.url).toEqual('http://www.google.com/'); +    }); +     +    it('should call error handler if status code is less than 200', function() { +      var finished; +      response.status = 199; +      response.statusText = 'Error Message'; +      app.navigateTo('about:blank', angular.noop, function(error) { +        expect(error).toEqual('199 Error Message'); +        finished = true; +      }); +      expect(finished).toBeTruthy(); +    }); +     +    it('should call error handler if status code is greater than 299', function() { +      var finished; +      response.status = 300; +      response.statusText = 'Error'; +      app.navigateTo('about:blank', angular.noop, function(error) { +        expect(error).toEqual('300 Error'); +        finished = true; +      }); +      expect(finished).toBeTruthy(); +    }); +     +    it('should call error handler if status code is 0 for sandbox error', function() { +      var finished; +      response.status = 0; +      response.statusText = ''; +      app.navigateTo('about:blank', angular.noop, function(error) { +        expect(error).toEqual('Sandbox Error: Cannot access about:blank'); +        finished = true; +      }); +      expect(finished).toBeTruthy(); +    }); +  });  });  | 
