Skip to content

Commit

Permalink
Resolve relative URL
Browse files Browse the repository at this point in the history
  • Loading branch information
Dima Voytenko committed Jan 28, 2016
1 parent 730bda0 commit 38abef8
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 26 deletions.
94 changes: 81 additions & 13 deletions src/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,18 @@ export function parseQueryString(queryString) {
* Don't use this directly, only exported for testing. The value
* is available via the origin property of the object returned by
* parseUrl.
* @param {!Location} info
* @param {string|!Location} url
* @return {string}
* @visibleForTesting
*/
export function getOrigin(info) {
if (info.protocol == 'data:' || !info.host) {
return info.href;
export function getOrigin(url) {
if (typeof url == 'string') {
url = parseUrl(url);
}
if (url.protocol == 'data:' || !url.host) {
return url.href;
}
return info.protocol + '//' + info.host;
return url.protocol + '//' + url.host;
}


Expand Down Expand Up @@ -192,19 +195,19 @@ export function isProxyOrigin(url) {
}

/**
* Returns the source origin of an AMP document for documents served
* Returns the source URL of an AMP document for documents served
* on a proxy origin or directly.
* @param {string|!Location} url URL of an AMP document.
* @return {string} The source origin of the URL.
* @return {string}
*/
export function getSourceOrigin(url) {
export function getSourceUrl(url) {
if (typeof url == 'string') {
url = parseUrl(url);
}

// Not a proxy URL - return the URL's origin.
// Not a proxy URL - return the URL itself.
if (!isProxyOrigin(url)) {
return getOrigin(url);
return url.href;
}

// A proxy URL.
Expand All @@ -217,9 +220,74 @@ export function getSourceOrigin(url) {
'Unknown path prefix in url %s', url.href);
const domainOrHttpsSignal = path[2];
const origin = domainOrHttpsSignal == 's'
? 'https://' + path[3]
: 'http://' + domainOrHttpsSignal;
? 'https://' + decodeURIComponent(path[3])
: 'http://' + decodeURIComponent(domainOrHttpsSignal);
// Sanity test that what we found looks like a domain.
assert(origin.indexOf('.') > 0, 'Expected a . in origin %s', origin);
return origin;
path.splice(1, domainOrHttpsSignal == 's' ? 3 : 2);
return origin + path.join('/') + (url.search || '') + (url.hash || '');
}

/**
* Returns the source origin of an AMP document for documents served
* on a proxy origin or directly.
* @param {string|!Location} url URL of an AMP document.
* @return {string} The source origin of the URL.
*/
export function getSourceOrigin(url) {
return getOrigin(getSourceUrl(url));
}

/**
* Returns absolute URL resolved based on the relative URL and the base.
* @param {string} relativeUrlString
* @param {string|!Location} baseUrl
* @return {string}
*/
export function resolveRelativeUrl(relativeUrlString, baseUrl) {
if (typeof baseUrl == 'string') {
baseUrl = parseUrl(baseUrl);
}
if (typeof URL == 'function') {
return new URL(relativeUrlString, baseUrl.href).toString();
}
return resolveRelativeUrlFallback_(relativeUrlString, baseUrl);
}

/**
* Fallback for URL resolver when URL class is not available.
* @param {string} relativeUrlString
* @param {string|!Location} baseUrl
* @return {string}
* @private Visible for testing.
*/
export function resolveRelativeUrlFallback_(relativeUrlString, baseUrl) {
if (typeof baseUrl == 'string') {
baseUrl = parseUrl(baseUrl);
}
relativeUrlString = relativeUrlString.replace(/\\/g, '/');
const relativeUrl = parseUrl(relativeUrlString);

// Absolute URL.
if (relativeUrlString.toLowerCase().indexOf(relativeUrl.protocol) == 0) {
return relativeUrl.href;
}

// Protocol-relative URL.
if (relativeUrlString.indexOf('//') == 0) {
return baseUrl.protocol + relativeUrlString;
}

// Absolute path.
if (relativeUrlString.indexOf('/') == 0) {
return baseUrl.origin + relativeUrlString;
}

// Relative path.
const basePath = baseUrl.pathname.split('/');
return baseUrl.origin +
(basePath.length > 1 ?
basePath.slice(0, basePath.length - 1).join('/') :
'') +
'/' + relativeUrlString;
}
2 changes: 1 addition & 1 deletion test/functional/test-cid.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ describe('cid', () => {
'https://cdn.ampproject.org/v/www.DIFFERENT.com/foo/?f=0';
return compare(
'e2',
'sha384(sha384([1,2,3,0,0,0,0,0,0,0,0,0,0,0,0,15])http://www.DIFFERENT.come2)');
'sha384(sha384([1,2,3,0,0,0,0,0,0,0,0,0,0,0,0,15])http://www.different.come2)');
});

it('should fallback to cookie value on custom domain.', () => {
Expand Down
113 changes: 101 additions & 12 deletions test/functional/test-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ import {
assertHttpsUrl,
getOrigin,
getSourceOrigin,
getSourceUrl,
isProxyOrigin,
parseQueryString,
parseUrl,
removeFragment
removeFragment,
resolveRelativeUrl,
resolveRelativeUrlFallback_
} from '../../src/url';

describe('url', () => {
Expand Down Expand Up @@ -334,33 +337,119 @@ describe('isProxyOrigin', () => {
false);
});

describe('getSourceOrigin', () => {
describe('getSourceOrigin/Url', () => {

function testOrigin(href, origin) {
it('should return the origin from ' + href, () => {
expect(getSourceOrigin(parseUrl(href))).to.equal(origin);
function testOrigin(href, sourceHref) {
it('should return the source origin/url from ' + href, () => {
expect(getSourceUrl(href)).to.equal(sourceHref);
expect(getSourceOrigin(href)).to.equal(getOrigin(sourceHref));
});
}

// CDN.
testOrigin(
'https://cdn.ampproject.org/v/www.origin.com/foo/?f=0',
'http://www.origin.com');
'https://cdn.ampproject.org/v/www.origin.com/foo/?f=0#h',
'http://www.origin.com/foo/?f=0#h');
testOrigin(
'https://cdn.ampproject.org/v/s/www.origin.com/foo/?f=0',
'https://www.origin.com');
'https://cdn.ampproject.org/v/s/www.origin.com/foo/?f=0#h',
'https://www.origin.com/foo/?f=0#h');
testOrigin(
'https://cdn.ampproject.org/c/www.origin.com/foo/?f=0',
'http://www.origin.com');
'http://www.origin.com/foo/?f=0');
testOrigin(
'https://cdn.ampproject.org/c/s/www.origin.com/foo/?f=0',
'https://www.origin.com');
'https://www.origin.com/foo/?f=0');
testOrigin(
'https://cdn.ampproject.org/c/s/origin.com/foo/?f=0',
'https://origin.com');
'https://origin.com/foo/?f=0');
testOrigin(
'https://cdn.ampproject.org/c/s/origin.com%3A81/foo/?f=0',
'https://origin.com:81/foo/?f=0');

// Non-CDN.
testOrigin(
'https://origin.com/foo/?f=0',
'https://origin.com/foo/?f=0');

it('should fail on invalid source origin', () => {
expect(() => {
getSourceOrigin(parseUrl('https://cdn.ampproject.org/v/yyy/'));
}).to.throw(/Expected a \. in origin http:\/\/yyy/);
});
});

describe('resolveRelativeUrl', () => {

function testRelUrl(href, baseHref, resolvedHref) {
it('should return the resolved rel url from ' + href +
' with base ' + baseHref, () => {
expect(resolveRelativeUrl(href, baseHref))
.to.equal(resolvedHref, 'native or fallback');
expect(resolveRelativeUrlFallback_(href, baseHref))
.to.equal(resolvedHref, 'fallback');
});
}

// Absolute URL.
testRelUrl(
'https://acme.org/path/file?f=0#h',
'https://base.org/bpath/bfile?bf=0#bh',
'https://acme.org/path/file?f=0#h');
testRelUrl(
'data:12345',
'https://base.org/bpath/bfile?bf=0#bh',
'data:12345');

// Protocol-relative URL.
testRelUrl(
'//acme.org/path/file?f=0#h',
'https://base.org/bpath/bfile?bf=0#bh',
'https://acme.org/path/file?f=0#h');
testRelUrl(
'//acme.org/path/file?f=0#h',
'http://base.org/bpath/bfile?bf=0#bh',
'http://acme.org/path/file?f=0#h');
testRelUrl(
'\\\\acme.org/path/file?f=0#h',
'http://base.org/bpath/bfile?bf=0#bh',
'http://acme.org/path/file?f=0#h');

// Absolute path.
testRelUrl(
'/path/file?f=0#h',
'https://base.org/bpath/bfile?bf=0#bh',
'https://base.org/path/file?f=0#h');
testRelUrl(
'/path/file?f=0#h',
'http://base.org/bpath/bfile?bf=0#bh',
'http://base.org/path/file?f=0#h');
testRelUrl(
'\\path/file?f=0#h',
'http://base.org/bpath/bfile?bf=0#bh',
'http://base.org/path/file?f=0#h');

// Relative path.
testRelUrl(
'file?f=0#h',
'https://base.org/bpath/bfile?bf=0#bh',
'https://base.org/bpath/file?f=0#h');
testRelUrl(
'file?f=0#h',
'http://base.org/bpath/bfile?bf=0#bh',
'http://base.org/bpath/file?f=0#h');

testRelUrl(
'file?f=0#h',
'https://base.org/bfile?bf=0#bh',
'https://base.org/file?f=0#h');
testRelUrl(
'file?f=0#h',
'http://base.org/bfile?bf=0#bh',
'http://base.org/file?f=0#h');

// Accepts parsed URLs.
testRelUrl(
'file?f=0#h',
parseUrl('http://base.org/bfile?bf=0#bh'),
'http://base.org/file?f=0#h');
});

0 comments on commit 38abef8

Please sign in to comment.