diff --git a/package-lock.json b/package-lock.json index 8960962850..78f464ff59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "dashjs", - "version": "4.0.1", + "version": "4.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/core/Utils.js b/src/core/Utils.js index b5bda4f2ff..aa7002e1dc 100644 --- a/src/core/Utils.js +++ b/src/core/Utils.js @@ -34,6 +34,8 @@ * @ignore */ +import path from 'path-browserify' + class Utils { static mixin(dest, source, copy) { let s; @@ -96,7 +98,7 @@ class Utils { } } - static parseHttpHeaders (headerStr) { + static parseHttpHeaders(headerStr) { let headers = {}; if (!headerStr) { return headers; @@ -139,6 +141,40 @@ class Utils { } return hash; } + + /** + * Compares both urls and returns a relative url (target relative to original) + * @param {string} original + * @param {string} target + * @return {string|*} + */ + static getRelativeUrl(originalUrl, targetUrl) { + try { + const original = new URL(originalUrl); + const target = new URL(targetUrl); + + // Unify the protocol to compare the origins + original.protocol = target.protocol; + if (original.origin !== target.origin) { + return targetUrl; + } + + // Use the relative path implementation of the path library. We need to cut off the actual filename in the end to get the relative path + let relativePath = path.relative(original.pathname.substr(0, original.pathname.lastIndexOf('/')), target.pathname.substr(0, target.pathname.lastIndexOf('/'))); + + // In case the relative path is empty (both path are equal) return the filename only. Otherwise add a slash in front of the filename + const startIndexOffset = relativePath.length === 0 ? 1 : 0; + relativePath += target.pathname.substr(target.pathname.lastIndexOf('/') + startIndexOffset, target.pathname.length - 1); + + // Build the other candidate, e.g. the 'host relative' path that starts with "/", and return the shortest of the two candidates. + if (target.pathname.length < relativePath.length) { + return target.pathname; + } + return relativePath; + } catch (e) { + return targetUrl + } + } } export default Utils; diff --git a/src/streaming/models/CmcdModel.js b/src/streaming/models/CmcdModel.js index b56684207e..e03b79984b 100644 --- a/src/streaming/models/CmcdModel.js +++ b/src/streaming/models/CmcdModel.js @@ -270,8 +270,7 @@ function CmcdModel() { if (nextRequest) { if (request.url !== nextRequest.url) { - let url = new URL(nextRequest.url); - data.nor = url.pathname; + data.nor = encodeURIComponent(Utils.getRelativeUrl(request.url, nextRequest.url)); } else if (nextRequest.range) { data.nrr = nextRequest.range; } diff --git a/test/unit/core.Utils.js b/test/unit/core.Utils.js new file mode 100644 index 0000000000..8c598c0b7a --- /dev/null +++ b/test/unit/core.Utils.js @@ -0,0 +1,90 @@ +import Utils from '../../src/core/Utils' +import {expect} from 'chai'; + +describe('Utils', () => { + describe('getRelativeUrl', () => { + + it('Should return complete url if no original url is given', () => { + const b = 'https://localhost:3000/d/e/f.mp4'; + + expect(Utils.getRelativeUrl(undefined,b)).to.be.equal('https://localhost:3000/d/e/f.mp4'); + }) + + it('Should return relative url if strings are different after server name', () => { + const a = 'https://localhost:3000/a/b/c.mp4'; + const b = 'https://localhost:3000/d/e/f.mp4'; + + expect(Utils.getRelativeUrl(a,b)).to.be.equal('/d/e/f.mp4'); + }) + + it('Should return relative url if strings are similar up to one element before filename', () => { + const a = 'https://localhost:3000/a/b/c.mp4'; + const b = 'https://localhost:3000/a/c/f.mp4'; + + expect(Utils.getRelativeUrl(a,b)).to.be.equal('../c/f.mp4'); + }) + + it('Should return relative url if strings are similar up to filename', () => { + const a = 'https://localhost:3000/a/b/c.mp4'; + const b = 'https://localhost:3000/a/b/f.mp4'; + + expect(Utils.getRelativeUrl(a,b)).to.be.equal('f.mp4'); + }) + + it('Should return complete url if origin is different', () => { + const a = 'https://localhost:3000/a/b/c.mp4'; + const b = 'https://loca:3000/a/b/f.mp4'; + + expect(Utils.getRelativeUrl(a,b)).to.be.equal('https://loca:3000/a/b/f.mp4'); + }) + + it('Should return filename if origin differs in terms of SSL', () => { + const a = 'https://localhost:3000/a/b/c.mp4'; + const b = 'http://localhost:3000/a/b/e.mp4'; + + expect(Utils.getRelativeUrl(a,b)).to.be.equal('e.mp4'); + }) + + it('Should return relative url if part of the pathnames are not equal', () => { + const a = 'https://localhost:3000/a/b/c.mp4'; + const b = 'https://localhost:3000/ab/b/f.mp4'; + + expect(Utils.getRelativeUrl(a,b)).to.be.equal('/ab/b/f.mp4'); + }) + + it('Should return relative url if target pathname is longer than source pathname ', () => { + const a = 'https://localhost:3000/a/b/f.mp4'; + const b = 'https://localhost:3000/a/b/f/e/c.mp4'; + + expect(Utils.getRelativeUrl(a,b)).to.be.equal('f/e/c.mp4'); + }) + + it('Should return relative url if source pathname is longer than target pathname ', () => { + const a = 'https://localhost:3000/a/b/f/e/c.mp4'; + const b = 'https://localhost:3000/a/b/f.mp4'; + + expect(Utils.getRelativeUrl(a,b)).to.be.equal('/a/b/f.mp4'); + }) + + it('Should return relative url if source contains slash in the end ', () => { + const a = 'https://localhost:3000/a/'; + const b = 'https://localhost:3000/a/b.mp4'; + + expect(Utils.getRelativeUrl(a,b)).to.be.equal('b.mp4'); + }) + + it('Should return relative url if source pathnames are exceptionally long ', () => { + const a = 'https://localhost:3000/a/b/c/d/e/f/g/h/1.mp4'; + const b = 'https://localhost:3000/a/b/c/d/e/change/i/j/k/l/m/n/2.mp4'; + + expect(Utils.getRelativeUrl(a,b)).to.be.equal('../../../change/i/j/k/l/m/n/2.mp4'); + }) + + it('Should return relative url if source contains slash in the end and multiple elements in the path', () => { + const a = 'https://localhost:3000/a/b/c/'; + const b = 'https://localhost:3000/a/b/x/c.mp4'; + + expect(Utils.getRelativeUrl(a,b)).to.be.equal('../x/c.mp4'); + }) + }) +}) diff --git a/test/unit/streaming.models.CmcdModel.js b/test/unit/streaming.models.CmcdModel.js index dfc1dd02e1..5b15760513 100644 --- a/test/unit/streaming.models.CmcdModel.js +++ b/test/unit/streaming.models.CmcdModel.js @@ -121,7 +121,7 @@ describe('CmcdModel', function () { const MEASURED_THROUGHPUT = 8327641; const BUFFER_LEVEL = parseInt(dashMetricsMock.getCurrentBufferLevel() * 10) * 100; const VIDEO_OBJECT_TYPE = 'v'; - const NEXT_OBJECT_URL = '/next_object'; + const NEXT_OBJECT_URL = 'next_object'; const NEXT_OBJECT_RANGE = '100-500'; abrControllerMock.setTopBitrateInfo({bitrate: TOP_BITRATE}); @@ -135,7 +135,8 @@ describe('CmcdModel', function () { mediaType: MEDIA_TYPE, quality: 0, mediaInfo: {bitrateList: [{bandwidth: BITRATE}]}, - duration: DURATION + duration: DURATION, + url: 'http://test.url/firstRequest' }; let headers = cmcdModel.getHeaderParameters(request); @@ -430,7 +431,7 @@ describe('CmcdModel', function () { const MEASURED_THROUGHPUT = 8327641; const BUFFER_LEVEL = parseInt(dashMetricsMock.getCurrentBufferLevel() * 10) * 100; const VIDEO_OBJECT_TYPE = 'v'; - const NEXT_OBJECT_URL = '/next_object'; + const NEXT_OBJECT_URL = 'next_object'; const NEXT_OBJECT_RANGE = '100-500'; abrControllerMock.setTopBitrateInfo({bitrate: TOP_BITRATE}); @@ -444,7 +445,8 @@ describe('CmcdModel', function () { mediaType: MEDIA_TYPE, quality: 0, mediaInfo: {bitrateList: [{bandwidth: BITRATE}]}, - duration: DURATION + duration: DURATION, + url: 'http://test.url/firstRequest' }; let parameters = cmcdModel.getQueryParameter(request);