diff --git a/index.js b/index.js index 20ab9de..1afc342 100644 --- a/index.js +++ b/index.js @@ -6,8 +6,8 @@ const testParameter = (name, filters) => { return filters.some(filter => filter instanceof RegExp ? filter.test(name) : filter === name); }; -const normalizeDataURL = urlString => { - const parts = urlString.trim().match(/^data:(.*?),(.*)$/); +const normalizeDataURL = (urlString, {stripHash}) => { + const parts = urlString.match(/^data:(.*?),(.*?)(?:#(.*))?$/); if (!parts) { throw new Error(`Invalid URL: ${urlString}`); @@ -15,6 +15,7 @@ const normalizeDataURL = urlString => { const mediaType = parts[1].split(';'); const body = parts[2]; + const hash = stripHash ? '' : parts[3]; let base64 = false; @@ -50,7 +51,7 @@ const normalizeDataURL = urlString => { normalizedMediaType.unshift(mimeType); } - return `data:${normalizedMediaType.join(';')},${base64 ? body.trim() : body}`; + return `data:${normalizedMediaType.join(';')},${base64 ? body.trim() : body}${hash ? `#${hash}` : ''}`; }; const normalizeUrl = (urlString, options) => { @@ -84,11 +85,16 @@ const normalizeUrl = (urlString, options) => { urlString = urlString.trim(); + // Data URL + if (/^data:/i.test(urlString)) { + return normalizeDataURL(urlString, options); + } + const hasRelativeProtocol = urlString.startsWith('//'); const isRelativeUrl = !hasRelativeProtocol && /^\.*\//.test(urlString); // Prepend protocol - if (!isRelativeUrl && !/^data:/i.test(urlString)) { + if (!isRelativeUrl) { urlString = urlString.replace(/^(?!(?:\w+:)?\/\/)|^\/\//, options.defaultProtocol); } @@ -177,12 +183,6 @@ const normalizeUrl = (urlString, options) => { urlObj.searchParams.sort(); } - // Data URL - if (urlObj.protocol === 'data:') { - const url = normalizeDataURL(`${urlObj.protocol}${urlObj.pathname}`); - return `${url}${urlObj.search}${urlObj.hash}`; - } - if (options.removeTrailingSlash) { urlObj.pathname = urlObj.pathname.replace(/\/$/, ''); } diff --git a/test.js b/test.js index b65347f..64ed300 100644 --- a/test.js +++ b/test.js @@ -221,29 +221,49 @@ test('data URL', t => { // Lowercase the MIME type. t.is(normalizeUrl('data:TEXT/plain,foo'), 'data:text/plain,foo'); + // Strip empty hash. + t.is(normalizeUrl('data:,foo# '), 'data:,foo'); + // Lowercase the charset. t.is(normalizeUrl('data:text/plain;charset=UTF-8,foo'), 'data:text/plain;charset=utf-8,foo'); // Remove spaces after the comma when it's base64. - t.is(normalizeUrl('data:image/gif;base64, R0lGODlhAQABAAAAACw= ?foo=bar'), 'data:image/gif;base64,R0lGODlhAQABAAAAACw=?foo=bar'); + t.is(normalizeUrl('data:image/gif;base64, R0lGODlhAQABAAAAACw= #foo #bar'), 'data:image/gif;base64,R0lGODlhAQABAAAAACw=#foo #bar'); // Keep spaces when it's not base64. - t.is(normalizeUrl('data:text/plain;charset=utf-8, foo ?foo=bar'), 'data:text/plain;charset=utf-8, foo?foo=bar'); - - // Data URL with query and hash. - t.is(normalizeUrl('data:image/gif;base64,R0lGODlhAQABAAAAACw=?foo=bar#baz'), 'data:image/gif;base64,R0lGODlhAQABAAAAACw=?foo=bar#baz'); + t.is(normalizeUrl('data:text/plain;charset=utf-8, foo #bar'), 'data:text/plain;charset=utf-8, foo #bar'); - // Options. - t.is(normalizeUrl('data:text/plain;charset=utf-8,www.foo/index.html?foo=bar&a=a&utm_medium=test#baz', { + // Protocol should not be changed. + t.is(normalizeUrl('data:,', { defaultProtocol: 'http:', normalizeProtocol: true, forceHttp: true, - stripHash: true, - stripWWW: true, - stripProtocol: true, + stripProtocol: true + }), 'data:,'); + + // Option: removeTrailingSlash. + t.is(normalizeUrl('data:,foo/', { + removeTrailingSlash: true + }), 'data:,foo/'); + + // Option: removeDirectoryIndex. + t.is(normalizeUrl('data:,foo/index.html', { + removeTrailingSlash: true + }), 'data:,foo/index.html'); + + // Option: removeQueryParameters & sortQueryParameters. + t.is(normalizeUrl('data:,foo?foo=bar&a=a&utm_medium=test', { removeQueryParameters: [/^utm_\w+/i, 'ref'], - sortQueryParameters: true, - removeTrailingSlash: true, - removeDirectoryIndex: true - }), 'data:text/plain;charset=utf-8,www.foo/index.html?a=a&foo=bar'); + sortQueryParameters: true + }), 'data:,foo?foo=bar&a=a&utm_medium=test'); + + // Option: stripHash. + t.is(normalizeUrl('data:,foo#bar', { + stripHash: true + }), 'data:,foo'); + + // Option: stripWWW. + t.is(normalizeUrl('data:,www.foo.com', { + stripWWW: true + }), 'data:,www.foo.com'); });