Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions lib/decode_url.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
// eslint-disable-next-line n/no-deprecated-api
import { parse, format } from 'url';
import { format } from 'url';
import { unescape } from 'querystring';

const decodeURL = (str: string) => {
const index = str.indexOf(':');
if (index < 0) {
if (!str.includes(':')) {
return unescape(str);
}
if (parse(str.slice(0, index + 1)).protocol) {
const parsed = new URL(str);

const parsed = URL.parse(str);
if (parsed) {

// Exit if input is a data url
if (parsed.origin === 'null') return str;
Expand Down
11 changes: 5 additions & 6 deletions lib/encode_url.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
// eslint-disable-next-line n/no-deprecated-api
import { parse, format } from 'url';
import { format } from 'url';
import { unescape } from 'querystring';

const encodeURL = (str: string) => {
const index = str.indexOf(':');
if (index < 0) {
if (!str.includes(':')) {
return encodeURI(unescape(str));
}
if (parse(str.slice(0, index + 1)).protocol) {
const parsed = new URL(str);

const parsed = URL.parse(str);
if (parsed) {

// Exit if input is a data url
if (parsed.origin === 'null') return str;
Expand Down
8 changes: 3 additions & 5 deletions lib/full_url_for.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// eslint-disable-next-line n/no-deprecated-api
import { parse } from 'url';
import encodeURL from './encode_url';
import prettyUrls from './pretty_urls';
import Cache from './cache';
Expand All @@ -16,11 +14,11 @@ function fullUrlForHelper(path = '/') {
return cache.apply(`${config.url}-${prettyUrlsOptions.trailing_index}-${prettyUrlsOptions.trailing_html}-${path}`, () => {
if (/^(\/\/|http(s)?:)/.test(path)) return path;

const sitehost = parse(config.url).hostname || config.url;
const data = new URL(path, `http://${sitehost}`);
const sitehost = URL.parse(config.url)?.hostname || config.url;
const data = URL.parse(path, `http://${sitehost}`);

// Exit if input is an external link or a data url
if (data.hostname !== sitehost || data.origin === 'null') return path;
if (!data || data.hostname !== sitehost || data.origin === 'null') return path;

path = encodeURL(config.url + `/${path}`.replace(/\/{2,}/g, '/'));
path = prettyUrls(path, prettyUrlsOptions);
Expand Down
13 changes: 4 additions & 9 deletions lib/is_external_link.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// eslint-disable-next-line n/no-deprecated-api
import { parse } from 'url';
import Cache from './cache';
const cache = new Cache<boolean>();

Expand All @@ -16,18 +14,15 @@ function isExternalLink(input: string, sitehost: string, exclude?: string | stri
// Return false early for internal link
if (!/^(\/\/|http(s)?:)/.test(input)) return false;

sitehost = parse(sitehost).hostname || sitehost;
sitehost = URL.parse(sitehost)?.hostname || sitehost;

if (!sitehost) return false;

// handle relative url and invalid url
let data;
try {
data = new URL(input, `http://${sitehost}`);
} catch { }
const data = URL.parse(input, `http://${sitehost}`);

// if input is invalid url, data should be undefined
if (typeof data !== 'object') return false;
// if input is invalid url, data should be null
if (!data) return false;

// handle mailto: javascript: vbscript: and so on
if (data.origin === 'null') return false;
Expand Down
8 changes: 3 additions & 5 deletions lib/url_for.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// eslint-disable-next-line n/no-deprecated-api
import { parse } from 'url';
import encodeURL from './encode_url';
import relative_url from './relative_url';
import prettyUrls from './pretty_urls';
Expand Down Expand Up @@ -57,11 +55,11 @@ function urlForHelper(path = '/', options: UrlForOptions | null = {}) {
return cache.apply(
`${config.url}-${root}-${prettyUrlsOptions.trailing_index}-${prettyUrlsOptions.trailing_html}-${path}`,
() => {
const sitehost = parse(config.url).hostname || config.url;
const data = new URL(path, `http://${sitehost}`);
const sitehost = URL.parse(config.url)?.hostname || config.url;
const data = URL.parse(path, `http://${sitehost}`);

// Exit if input is an external link or a data url
if (data.hostname !== sitehost || data.origin === 'null') {
if (!data || data.hostname !== sitehost || data.origin === 'null') {
return path;
}

Expand Down
10 changes: 10 additions & 0 deletions test/decode_url.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ describe('decodeURL', () => {
decodeURL(content).should.eql('/foo bar/baz/');
});

it('path with colon', () => {
const content = '/foo:bar%20baz/';
decodeURL(content).should.eql('/foo:bar baz/');
});

it('path with unicode', () => {
const content = '/foo/b%C3%A1r/';
decodeURL(content).should.eql('/foo/bár/');
Expand All @@ -87,4 +92,9 @@ describe('decodeURL', () => {
const content = 'data:image/png;base64';
decodeURL(content).should.eql('data:image/png;base64');
});

it('invalid absolute URL', () => {
decodeURL('https:').should.eql('https:');
decodeURL('http://[invalid').should.eql('http://[invalid');
});
});
11 changes: 11 additions & 0 deletions test/encode_url.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ describe('encodeURL', () => {
encodeURL(content).should.eql('/foo%20bar/baz/');
});

it('path with colon', () => {
const content = '/foo:bar baz/';
encodeURL(content).should.eql('/foo:bar%20baz/');
});

it('path with unicode', () => {
const content = '/foo/bár/';
encodeURL(content).should.eql('/foo/b%C3%A1r/');
Expand All @@ -108,6 +113,12 @@ describe('encodeURL', () => {
const content = 'data:,Hello%2C%20World!';
encodeURL(content).should.eql(content);
});

it('invalid absolute URL', () => {
encodeURL('https:').should.eql('https:');
encodeURL('http://[invalid').should.eql('http://%5Binvalid');
});

it('encode pathname', () => {
const content = 'https://fóo.com/páth%20[square]';
encodeURL(content).should.eql('https://fóo.com/p%C3%A1th%20%5Bsquare%5D');
Expand Down
9 changes: 9 additions & 0 deletions test/full_url_for.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ describe('full_url_for', () => {
fullUrlFor('/').should.eql(ctx.config.url + '/');
});

it('internal url - IPv6 host', () => {
ctx.config.url = 'http://[::1]:4000/blog';
fullUrlFor('index.html').should.eql(ctx.config.url + '/index.html');
});

it('internal url - no duplicate slash', () => {
ctx.config.url = 'https://example.com';
fullUrlFor('/index.html').should.eql('https://example.com/index.html');
Expand Down Expand Up @@ -89,4 +94,8 @@ describe('full_url_for', () => {
fullUrlFor(url).should.eql(url);
});
});

it('invalid URL', () => {
fullUrlFor('httpsx://[invalid').should.eql('httpsx://[invalid');
});
});
6 changes: 6 additions & 0 deletions test/is_external_link.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ describe('isExternalLink', () => {
isExternalLink('/archives//hexo.io', ctx.config.url).should.eql(false);
});

it('IPv6 host', () => {
const sitehost = 'http://[::1]:4000';
isExternalLink('http://[::1]:5000/foo', sitehost).should.eql(false);
isExternalLink('http://[::2]:4000/foo', sitehost).should.eql(true);
});

it('hash, mailto, javascript', () => {
isExternalLink('#top', ctx.config.url).should.eql(false);
isExternalLink('mailto:hi@hexo.io', ctx.config.url).should.eql(false);
Expand Down
10 changes: 10 additions & 0 deletions test/url_for.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ describe('url_for', () => {
urlFor('/index.html').should.eql('/blog/index.html');
});

it('internal url (relative off) - IPv6 host', () => {
ctx.config.url = 'http://[::1]:4000/blog';
ctx.config.root = '/blog/';
urlFor('index.html').should.eql('/blog/index.html');
});

it('internal url (relative on)', () => {
ctx.config.relative_link = true;
ctx.config.root = '/';
Expand Down Expand Up @@ -153,4 +159,8 @@ describe('url_for', () => {
urlFor(url).should.eql(url);
});
});

it('invalid URL', () => {
urlFor('httpsx://[invalid').should.eql('httpsx://[invalid');
});
});
Loading