diff --git a/oauth-1.0a.d.ts b/oauth-1.0a.d.ts index f40cb3d..4a04d5d 100644 --- a/oauth-1.0a.d.ts +++ b/oauth-1.0a.d.ts @@ -17,6 +17,16 @@ declare class OAuth { signature_metho: string; version: string; + static urlPattern: RegExp; + + /** + * Parse an URL into its various component. + * + * Does no normalisation but throw if it encounters non-ascii char or if the + * URL does represent a http(s) request. + */ + static parseUrl(url: string): OAuth.URL; + constructor(opts?: OAuth.Options); /** @@ -189,4 +199,17 @@ declare namespace OAuth { secret: string; } + /** + * URL components + */ + export interface URL { + auth: string; + hash: string; + hostname: string; + pathname: string; + port: string; + protocol: 'http' | 'https'; + search: string; + } + } diff --git a/oauth-1.0a.js b/oauth-1.0a.js index 5c15dd4..8e89a87 100644 --- a/oauth-1.0a.js +++ b/oauth-1.0a.js @@ -48,6 +48,37 @@ function OAuth(opts) { this.body_hash_function = opts.body_hash_function || this.hash_function; } +// Static methods and properties. +Object.defineProperties(OAuth, { + + // cache the url regexp. + urlPattern: { + value: /^(https?):\/\/([^:]+:[^@]+@)?([^:/?#]+)(\:\d+)?(\/[^?#]*)?(\?[^#]*)?(#.*)?$/ + }, + + // Parse url into components. + parseUrl: { + value: function(url) { + var match = OAuth.urlPattern.exec(url); + var components; + + if (match == null || match.len < 8) { + throw new Error('Invalid URL: "' + url + '".'); + } + + return { + protocol: match[1], + auth: match[2] || '', + hostname: match[3], + port: match[4] || '', + pathname: match[5] || '', + search: match[6] || '', + hash: match[7] || '' + }; + } + } +}); + /** * OAuth request authorize * @param {Object} request data @@ -194,7 +225,18 @@ OAuth.prototype.getSigningKey = function(token_secret) { * @return {String} */ OAuth.prototype.getBaseUrl = function(url) { - return url.split('?')[0]; + var parsed = OAuth.parseUrl(url); + var port = parsed.port; + var protocol = parsed.protocol.toLowerCase(); + + if ( + (port === ':80' && protocol === 'http') || + (port === ':443' && protocol === 'https') + ) { + parsed.port = ''; + } + + return parsed.protocol + '://' + parsed.auth + parsed.hostname + parsed.port + parsed.pathname; }; /** diff --git a/test/getBaseUrl.js b/test/getBaseUrl.js new file mode 100644 index 0000000..3ff9fa8 --- /dev/null +++ b/test/getBaseUrl.js @@ -0,0 +1,29 @@ +var expect = require('chai').expect; +var OAuth = require('../oauth-1.0a'); + +describe('#getBaseUrl', function() { + var oauth = new OAuth({consumer: {}}); + + beforeEach(function () { + oauth = new OAuth({consumer: {}}); + }); + + it('should return base url', function () { + expect(oauth.getBaseUrl('http://example.com/path/')).to.equal('http://example.com/path/'); + expect(oauth.getBaseUrl('http://example.com/path/?foo=bar')).to.equal('http://example.com/path/'); + }); + + it('should exclude default port number', function () { + expect(oauth.getBaseUrl('http://example.com/')).to.equal('http://example.com/'); + expect(oauth.getBaseUrl('http://example.com:80/')).to.equal('http://example.com/'); + expect(oauth.getBaseUrl('https://example.com/')).to.equal('https://example.com/'); + expect(oauth.getBaseUrl('https://example.com:443/')).to.equal('https://example.com/'); + }); + + it('should include non-default port number', function () { + expect(oauth.getBaseUrl('http://example.com:8080/')).to.equal('http://example.com:8080/'); + expect(oauth.getBaseUrl('http://example.com:443/')).to.equal('http://example.com:443/'); + expect(oauth.getBaseUrl('https://example.com:8080/')).to.equal('https://example.com:8080/'); + expect(oauth.getBaseUrl('https://example.com:80/')).to.equal('https://example.com:80/'); + }); +}); diff --git a/test/parseUrl.js b/test/parseUrl.js new file mode 100644 index 0000000..afe89fb --- /dev/null +++ b/test/parseUrl.js @@ -0,0 +1,46 @@ +var expect = require('chai').expect; +var OAuth = require('../oauth-1.0a'); + +describe('OAuth.parseUrl', function() { + + it('should parse protocol', function () { + expect(OAuth.parseUrl('http://example.com/').protocol).to.equal('http'); + expect(OAuth.parseUrl('https://example.com/').protocol).to.equal('https'); + }); + + it('should parse auth component', function () { + expect(OAuth.parseUrl('http://example.com/').auth).to.equal(''); + expect(OAuth.parseUrl('http://foo:bar@example.com/').auth).to.equal('foo:bar@'); + }); + + it('should parse hostname component', function () { + expect(OAuth.parseUrl('http://example.com/').hostname).to.equal('example.com'); + expect(OAuth.parseUrl('http://example.com:8080/').hostname).to.equal('example.com'); + expect(OAuth.parseUrl('http://foo:bar@example.com/').hostname).to.equal('example.com'); + }); + + it('should parse port component', function () { + expect(OAuth.parseUrl('http://example.com/').port).to.equal(''); + expect(OAuth.parseUrl('http://example.com:80/').port).to.equal(':80'); + expect(OAuth.parseUrl('http://foo:bar@example.com:80/').port).to.equal(':80'); + }); + + it('should parse pathname component', function () { + expect(OAuth.parseUrl('http://example.com').pathname).to.equal(''); + expect(OAuth.parseUrl('http://example.com/').pathname).to.equal('/'); + expect(OAuth.parseUrl('http://example.com/foo/bar').pathname).to.equal('/foo/bar'); + }); + + it('should parse search component', function () { + expect(OAuth.parseUrl('http://example.com').search).to.equal(''); + expect(OAuth.parseUrl('http://example.com/?foo').search).to.equal('?foo'); + expect(OAuth.parseUrl('http://example.com/?foo#bar').search).to.equal('?foo'); + expect(OAuth.parseUrl('http://example.com/?foo?bar').search).to.equal('?foo?bar'); + }); + + it('should parse hash component', function () { + expect(OAuth.parseUrl('http://example.com').hash).to.equal(''); + expect(OAuth.parseUrl('http://example.com/?foo').hash).to.equal(''); + expect(OAuth.parseUrl('http://example.com/?foo#bar').hash).to.equal('#bar'); + }); +}); diff --git a/test/services/twitter.js b/test/services/twitter.js index dba976f..0d40b82 100644 --- a/test/services/twitter.js +++ b/test/services/twitter.js @@ -180,4 +180,4 @@ describe("Twitter Personal Consumer", function() { }); }); }); -}); \ No newline at end of file +});