From e90091d148a5979761d3e88a0123ed557f8f33b4 Mon Sep 17 00:00:00 2001 From: Vartan Benohanian Date: Mon, 16 Mar 2020 16:10:11 -0400 Subject: [PATCH 1/3] Add method to get host, port, and secure flag from URL Signed-off-by: Vartan Benohanian --- lib/url.js | 33 +++++++++++++++++++++++++++++++++ test/lib/url.js | 24 ++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 lib/url.js create mode 100644 test/lib/url.js diff --git a/lib/url.js b/lib/url.js new file mode 100644 index 0000000..817f4a1 --- /dev/null +++ b/lib/url.js @@ -0,0 +1,33 @@ +const { URL } = require('url'); + +/** + * Parse the input (URL string) to get its hostname, port, and protocol. + * In the case that it's not parsable and it throws an error, return the an empty object. + * We do this to preserve backward compatibility. + * Note: this method assumes you are using either HTTP or HTTPS protocols. + * @param {String} input The URL. + * @param {Object} output An object containing host, port, and secure properties. + * @returns {Object} The object containing valid host, port, and secure keys based on the input. + */ +const parseURL = (input) => { + const output = {}; + + let url; + try { + url = new URL(input); + } catch (e) { + return output; + } + + const { hostname, port, protocol } = url; + + output.host = hostname; + output.port = port; + output.secure = protocol === 'https:'; + + return output; +}; + +module.exports = { + parseURL +}; diff --git a/test/lib/url.js b/test/lib/url.js new file mode 100644 index 0000000..999ca48 --- /dev/null +++ b/test/lib/url.js @@ -0,0 +1,24 @@ +/* eslint no-unused-expressions: 0 */ + +const url = require('../../lib/url'); + +describe('validation', () => { + describe('#parseURL', function() { + it('should return an object with host, port, and secure keys', function() { + let result = url.parseURL('http://localhost:3000'); + result.host.should.equal('localhost'); + result.port.should.equal('3000'); + result.secure.should.be.false; + + result = url.parseURL('https://localhost'); + result.host.should.equal('localhost'); + result.port.should.equal(''); + result.secure.should.be.true; + + result = url.parseURL('https://example.com:12345'); + result.host.should.equal('example.com'); + result.port.should.equal('12345'); + result.secure.should.be.true; + }); + }); +}); From 846628c902adbfffa98ddf12d49f8f4e301a98d9 Mon Sep 17 00:00:00 2001 From: Vartan Benohanian Date: Mon, 16 Mar 2020 16:16:24 -0400 Subject: [PATCH 2/3] parseURL for every API Signed-off-by: Vartan Benohanian --- lib/asset.js | 8 ++++++++ lib/campaign.js | 8 ++++++++ lib/claim.js | 8 ++++++++ lib/content.js | 8 ++++++++ lib/device.js | 8 ++++++++ lib/key.js | 8 ++++++++ lib/location.js | 8 ++++++++ lib/music.js | 8 ++++++++ lib/playback.js | 8 ++++++++ lib/player.js | 8 ++++++++ lib/provision.js | 8 ++++++++ lib/relationship.js | 8 ++++++++ lib/settings.js | 8 ++++++++ 13 files changed, 104 insertions(+) diff --git a/lib/asset.js b/lib/asset.js index f778f10..79b6c62 100644 --- a/lib/asset.js +++ b/lib/asset.js @@ -4,6 +4,7 @@ var co = require('co'), request = require('./request'), + url = require('./url'), validation = require('./validation'); const @@ -41,6 +42,13 @@ module.exports = function (assetOptions, ensureAuthHeaders, self) { // apply additional optional settings if supplied settings = validation.applyOptionalParameters(assetOptions, settings); + const result = url.parseURL(settings.host); + if (!validation.isEmpty(result)) { + settings.host = result.host; + settings.port = result.port; + settings.secure = result.secure; + } + // ensure request is setup req = new request.Request(settings); req.on(EVENT_REQUEST, (data) => (self.emit(EVENT_REQUEST, data))); diff --git a/lib/campaign.js b/lib/campaign.js index 74ebb29..b5718a7 100644 --- a/lib/campaign.js +++ b/lib/campaign.js @@ -4,6 +4,7 @@ var co = require('co'), fs = require('fs'), request = require('./request'), + url = require('./url'), validation = require('./validation'); const @@ -40,6 +41,13 @@ module.exports = function (campaignOptions, ensureAuthHeaders, self) { // apply additional optional settings if supplied settings = validation.applyOptionalParameters(campaignOptions, settings); + const result = url.parseURL(settings.host); + if (!validation.isEmpty(result)) { + settings.host = result.host; + settings.port = result.port; + settings.secure = result.secure; + } + // ensure request is setup req = new request.Request(settings); req.on(EVENT_REQUEST, (data) => (self.emit(EVENT_REQUEST, data))); diff --git a/lib/claim.js b/lib/claim.js index ad18303..966bbc5 100644 --- a/lib/claim.js +++ b/lib/claim.js @@ -4,6 +4,7 @@ var co = require('co'), request = require('./request'), + url = require('./url'), validation = require('./validation'); const @@ -39,6 +40,13 @@ module.exports = function (claimOptions, ensureAuthHeaders, self) { // apply additional optional settings if supplied settings = validation.applyOptionalParameters(claimOptions, settings); + const result = url.parseURL(settings.host); + if (!validation.isEmpty(result)) { + settings.host = result.host; + settings.port = result.port; + settings.secure = result.secure; + } + // ensure request is setup req = new request.Request(settings); req.on(EVENT_REQUEST, (data) => (self.emit(EVENT_REQUEST, data))); diff --git a/lib/content.js b/lib/content.js index 303f8d5..ce5e8a3 100644 --- a/lib/content.js +++ b/lib/content.js @@ -5,6 +5,7 @@ var co = require('co'), request = require('./request'), + url = require('./url'), validation = require('./validation'); const @@ -45,6 +46,13 @@ module.exports = function (contentOptions, ensureAuthHeaders, self) { // apply additional optional settings if supplied settings = validation.applyOptionalParameters(contentOptions, settings); + const result = url.parseURL(settings.host); + if (!validation.isEmpty(result)) { + settings.host = result.host; + settings.port = result.port; + settings.secure = result.secure; + } + // ensure request is setup req = new request.Request(settings); req.on(EVENT_REDIRECT, (data) => (self.emit(EVENT_REDIRECT, data))); diff --git a/lib/device.js b/lib/device.js index f65e60c..9ba9d47 100644 --- a/lib/device.js +++ b/lib/device.js @@ -4,6 +4,7 @@ var co = require('co'), request = require('./request'), + url = require('./url'), validation = require('./validation'); const @@ -41,6 +42,13 @@ module.exports = function (deviceOptions, ensureAuthHeaders, self) { // apply additional optional settings if supplied settings = validation.applyOptionalParameters(deviceOptions, settings); + const result = url.parseURL(settings.host); + if (!validation.isEmpty(result)) { + settings.host = result.host; + settings.port = result.port; + settings.secure = result.secure; + } + // ensure request is setup req = new request.Request(settings); req.on(EVENT_REQUEST, (data) => (self.emit(EVENT_REQUEST, data))); diff --git a/lib/key.js b/lib/key.js index e74a49a..83277b3 100644 --- a/lib/key.js +++ b/lib/key.js @@ -6,6 +6,7 @@ var co = require('co'), request = require('./request'), + url = require('./url'), validation = require('./validation'); const @@ -58,6 +59,13 @@ module.exports = function (keyOptions, clientId, secret, self) { // apply additional optional settings if supplied settings = validation.applyOptionalParameters(keyOptions, settings); + const result = url.parseURL(settings.host); + if (!validation.isEmpty(result)) { + settings.host = result.host; + settings.port = result.port; + settings.secure = result.secure; + } + // ensure request is setup req = new request.Request(settings); req.on(EVENT_REQUEST, (data) => (self.emit(EVENT_REQUEST, data))); diff --git a/lib/location.js b/lib/location.js index 1754f49..99ee613 100644 --- a/lib/location.js +++ b/lib/location.js @@ -4,6 +4,7 @@ var co = require('co'), request = require('./request'), + url = require('./url'), validation = require('./validation'); const @@ -39,6 +40,13 @@ module.exports = function (locationOptions, ensureAuthHeaders, self) { // apply additional optional settings if supplied settings = validation.applyOptionalParameters(locationOptions, settings); + const result = url.parseURL(settings.host); + if (!validation.isEmpty(result)) { + settings.host = result.host; + settings.port = result.port; + settings.secure = result.secure; + } + // ensure request is setup req = new request.Request(settings); req.on(EVENT_REQUEST, (data) => (self.emit(EVENT_REQUEST, data))); diff --git a/lib/music.js b/lib/music.js index f16b456..8231534 100644 --- a/lib/music.js +++ b/lib/music.js @@ -4,6 +4,7 @@ var co = require('co'), request = require('./request'), + url = require('./url'), validation = require('./validation'); const @@ -43,6 +44,13 @@ module.exports = function (musicOptions, ensureAuthHeaders, self) { // apply additional optional settings if supplied settings = validation.applyOptionalParameters(musicOptions, settings); + const result = url.parseURL(settings.host); + if (!validation.isEmpty(result)) { + settings.host = result.host; + settings.port = result.port; + settings.secure = result.secure; + } + // ensure request is setup req = new request.Request(settings); req.on(EVENT_REQUEST, (data) => (self.emit(EVENT_REQUEST, data))); diff --git a/lib/playback.js b/lib/playback.js index 483397f..4adfdff 100644 --- a/lib/playback.js +++ b/lib/playback.js @@ -4,6 +4,7 @@ var co = require('co'), request = require('./request'), + url = require('./url'), validation = require('./validation'); const @@ -39,6 +40,13 @@ module.exports = function (playbackOptions, ensureAuthHeaders, self) { // apply additional optional settings if supplied settings = validation.applyOptionalParameters(playbackOptions, settings); + const result = url.parseURL(settings.host); + if (!validation.isEmpty(result)) { + settings.host = result.host; + settings.port = result.port; + settings.secure = result.secure; + } + // ensure request is setup req = new request.Request(settings); req.on(EVENT_REQUEST, (data) => (self.emit(EVENT_REQUEST, data))); diff --git a/lib/player.js b/lib/player.js index b06e0c6..1164f13 100644 --- a/lib/player.js +++ b/lib/player.js @@ -2,6 +2,7 @@ var co = require('co'), events = require('events'), io = require('socket.io-client'), + url = require('./url'), validation = require('./validation'); let @@ -46,6 +47,13 @@ module.exports = function (playerOptions, ensureAuthHeaders, self) { // apply additional optional settings if supplied settings = validation.applyOptionalParameters(playerOptions, settings); + const result = url.parseURL(settings.host); + if (!validation.isEmpty(result)) { + settings.host = result.host; + settings.port = result.port; + settings.secure = result.secure; + } + self.settings = () => (settings); self.connect = (options, socketEventSubscriber) => co(function *() { diff --git a/lib/provision.js b/lib/provision.js index e351224..4a30597 100644 --- a/lib/provision.js +++ b/lib/provision.js @@ -4,6 +4,7 @@ var co = require('co'), request = require('./request'), + url = require('./url'), validation = require('./validation'); const @@ -42,6 +43,13 @@ module.exports = function (provisionOptions, ensureAuthHeaders, self) { // apply additional optional settings if supplied settings = validation.applyOptionalParameters(provisionOptions, settings); + const result = url.parseURL(settings.host); + if (!validation.isEmpty(result)) { + settings.host = result.host; + settings.port = result.port; + settings.secure = result.secure; + } + // ensure request is setup req = new request.Request(settings); req.on(EVENT_REQUEST, (data) => (self.emit(EVENT_REQUEST, data))); diff --git a/lib/relationship.js b/lib/relationship.js index 752d458..1979f7c 100644 --- a/lib/relationship.js +++ b/lib/relationship.js @@ -4,6 +4,7 @@ var co = require('co'), request = require('./request'), + url = require('./url'), validation = require('./validation'); const @@ -39,6 +40,13 @@ module.exports = function (relationshipsOptions, ensureAuthHeaders, self) { // apply additional optional settings if supplied settings = validation.applyOptionalParameters(relationshipsOptions, settings); + const result = url.parseURL(settings.host); + if (!validation.isEmpty(result)) { + settings.host = result.host; + settings.port = result.port; + settings.secure = result.secure; + } + // ensure request is setup req = new request.Request(settings); req.on(EVENT_REQUEST, (data) => (self.emit(EVENT_REQUEST, data))); diff --git a/lib/settings.js b/lib/settings.js index 2358177..79bf110 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -4,6 +4,7 @@ var co = require('co'), request = require('./request'), + url = require('./url'), validation = require('./validation'); const @@ -39,6 +40,13 @@ module.exports = function (settingsOptions, ensureAuthHeaders, self) { // apply additional optional settings if supplied settings = validation.applyOptionalParameters(settingsOptions, settings); + const result = url.parseURL(settings.host); + if (!validation.isEmpty(result)) { + settings.host = result.host; + settings.port = result.port; + settings.secure = result.secure; + } + // ensure request is setup req = new request.Request(settings); req.on(EVENT_REQUEST, (data) => (self.emit(EVENT_REQUEST, data))); From f85ccc0e3502b107864e4b43b3e25c26b9d02f77 Mon Sep 17 00:00:00 2001 From: Vartan Benohanian Date: Mon, 16 Mar 2020 22:04:21 -0400 Subject: [PATCH 3/3] Assume https by default, add test cases Signed-off-by: Vartan Benohanian --- lib/url.js | 22 ++++++++++++-- test/lib/url.js | 76 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 77 insertions(+), 21 deletions(-) diff --git a/lib/url.js b/lib/url.js index 817f4a1..5c608e1 100644 --- a/lib/url.js +++ b/lib/url.js @@ -1,10 +1,13 @@ const { URL } = require('url'); +const PROTOCOL_HTTP = 'http:'; +const PROTOCOL_HTTPS = 'https:'; + /** * Parse the input (URL string) to get its hostname, port, and protocol. - * In the case that it's not parsable and it throws an error, return the an empty object. + * In the case that it's not parsable and it throws an error, return an empty object. * We do this to preserve backward compatibility. - * Note: this method assumes you are using either HTTP or HTTPS protocols. + * Note: this method assumes you are using either HTTP or HTTPS protocols. It assumes HTTPS if protocol isn't provided. * @param {String} input The URL. * @param {Object} output An object containing host, port, and secure properties. * @returns {Object} The object containing valid host, port, and secure keys based on the input. @@ -12,6 +15,19 @@ const { URL } = require('url'); const parseURL = (input) => { const output = {}; + if (typeof(input) !== 'string') { + return output; + } + + input = input.trim(); + if (input === '') { + return output; + } + + if (!input.startsWith(PROTOCOL_HTTP) && !input.startsWith(PROTOCOL_HTTPS)) { + input = PROTOCOL_HTTPS + input; + } + let url; try { url = new URL(input); @@ -23,7 +39,7 @@ const parseURL = (input) => { output.host = hostname; output.port = port; - output.secure = protocol === 'https:'; + output.secure = protocol === PROTOCOL_HTTPS; return output; }; diff --git a/test/lib/url.js b/test/lib/url.js index 999ca48..91ceefb 100644 --- a/test/lib/url.js +++ b/test/lib/url.js @@ -3,22 +3,62 @@ const url = require('../../lib/url'); describe('validation', () => { - describe('#parseURL', function() { - it('should return an object with host, port, and secure keys', function() { - let result = url.parseURL('http://localhost:3000'); - result.host.should.equal('localhost'); - result.port.should.equal('3000'); - result.secure.should.be.false; - - result = url.parseURL('https://localhost'); - result.host.should.equal('localhost'); - result.port.should.equal(''); - result.secure.should.be.true; - - result = url.parseURL('https://example.com:12345'); - result.host.should.equal('example.com'); - result.port.should.equal('12345'); - result.secure.should.be.true; - }); - }); + describe('#parseURL', function() { + it('should return an object with host, port, and secure keys', function() { + let result = url.parseURL('http://localhost:3000'); + result.host.should.equal('localhost'); + result.port.should.equal('3000'); + result.secure.should.be.false; + + result = url.parseURL('https://localhost'); + result.host.should.equal('localhost'); + result.port.should.equal(''); + result.secure.should.be.true; + + result = url.parseURL('https://example.com:12345'); + result.host.should.equal('example.com'); + result.port.should.equal('12345'); + result.secure.should.be.true; + + result = url.parseURL(''); + result.should.deep.equal({}); + + result = url.parseURL(null); + result.should.deep.equal({}); + }); + + it('should return an empty object on invalid input', function() { + let result = url.parseURL(''); + result.should.deep.equal({}); + + result = url.parseURL(' '); + result.should.deep.equal({}); + + result = url.parseURL('this is not a url'); + result.should.deep.equal({}); + + result = url.parseURL(':8080'); + result.should.deep.equal({}); + + result = url.parseURL(null); + result.should.deep.equal({}); + }); + + it('assumes secure protocol if it is not provided in the url', function() { + let result = url.parseURL('example.com:12345'); + result.host.should.equal('example.com'); + result.port.should.equal('12345'); + result.secure.should.be.true; + + result = url.parseURL('localhost'); + result.host.should.equal('localhost'); + result.port.should.equal(''); + result.secure.should.be.true; + + result = url.parseURL('127.0.0.1'); + result.host.should.equal('127.0.0.1'); + result.port.should.equal(''); + result.secure.should.be.true; + }); + }); });