From 1a255f39186e0751b52e196418a143c24291e948 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Mon, 28 Oct 2019 17:22:03 +0800 Subject: [PATCH 1/4] feat: bring up isExternalLink --- README.md | 37 ++++++++++++++++++++++++++++++ lib/index.js | 1 + lib/isExternalLink.js | 43 +++++++++++++++++++++++++++++++++++ test/isExternalLink.spec.js | 45 +++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+) create mode 100644 lib/isExternalLink.js create mode 100644 test/isExternalLink.spec.js diff --git a/README.md b/README.md index 0da84a8b..713fcca8 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Utilities for [Hexo]. - [hash](#hashstr) - [highlight](#highlightstr-options) - [htmlTag](#htmltagtag-attrs-text-escape) +- [isExternalLink](#isexternallinkurl) - [Pattern](#patternrule) - [Permalink](#permalinkrule-options) - [relative_url](#relative_urlfrom-to) @@ -256,6 +257,42 @@ htmlTag('script', {src: '/foo.js', async: true}, '') // ``` +### isExternalLink(url) + +Returns if a given url is external link relative to `config.url` and `config.exclude`. + +``` yml +_config.yml +url: https://example.com # example +``` + +``` js +isExternalLink('https://example.com'); +// false +isExternalLink('/archives/foo.html'); +// false +isExternalLink('https://foo.com/'); +// true +``` + +``` yml +_config.yml +url: https://example.com # example +exclude: + - foo.com + - bar.com +``` + +``` js +isExternalLink('https://foo.com'); +// false +isExternalLink('https://bar.com'); +// false +isExternalLink('https://baz.com/'); +// true +``` + + ### Pattern(rule) Parses the string and tests if the string matches the rule. `rule` can be a string, a regular expression or a function. diff --git a/lib/index.js b/lib/index.js index 159d8a5d..91d7b132 100644 --- a/lib/index.js +++ b/lib/index.js @@ -17,6 +17,7 @@ exports.hash = hash.hash; exports.HashStream = hash.HashStream; exports.highlight = require('./highlight'); exports.htmlTag = require('./html_tag'); +exports.isExternalLink = require('./isExternalLink'); exports.Pattern = require('./pattern'); exports.Permalink = require('./permalink'); exports.relative_url = require('./relative_url'); diff --git a/lib/isExternalLink.js b/lib/isExternalLink.js new file mode 100644 index 00000000..5b517eed --- /dev/null +++ b/lib/isExternalLink.js @@ -0,0 +1,43 @@ +'use strict'; + +const { URL } = require('url'); + +const urlObj = (str) => { + try { + return new URL(str); + } catch (err) { + return str; + } +}; + +/** + * Check whether the link is external + * @param {String} url The url to check + * @returns {Boolean} True if the link doesn't have protocol or link has same host with config.url + */ + +function isExternalLink(url) { + const { config } = this; + const exclude = Array.isArray(config.external_link.exclude) ? config.external_link.exclude + : [config.external_link.exclude]; + const data = urlObj(url); + const host = data.hostname; + const sitehost = typeof urlObj(config.url) === 'object' ? urlObj(config.url).hostname : config.url; + + if (!sitehost || typeof data === 'string') return false; + + // handle mailto: javascript: vbscript: and so on + if (data.origin === 'null') return false; + + if (exclude && exclude.length) { + for (const i of exclude) { + if (host === i) return false; + } + } + + if (host !== sitehost) return true; + + return false; +} + +module.exports = isExternalLink; diff --git a/test/isExternalLink.spec.js b/test/isExternalLink.spec.js new file mode 100644 index 00000000..9b52b648 --- /dev/null +++ b/test/isExternalLink.spec.js @@ -0,0 +1,45 @@ +'use strict'; + +describe('isExternalLink', () => { + const ctx = { + config: { + url: 'https://example.com', + external_link: { + exclude: '' + } + } + }; + + const isExternalLink = require('../lib/isExternalLink').bind(ctx); + + it('external link', () => { + isExternalLink('https://hexo.io/').should.eql(true); + }); + + it('internal link', () => { + isExternalLink('https://example.com').should.eql(false); + isExternalLink('//example.com').should.eql(false); + isExternalLink('//example.com/archives/foo.html').should.eql(false); + isExternalLink('/archives/foo.html').should.eql(false); + }); + + it('hash, mailto, javascript', () => { + isExternalLink('#top').should.eql(false); + isExternalLink('mailto:hi@hexo.io').should.eql(false); + isExternalLink('javascript:alert(\'Hexo is Awesome\')').should.eql(false); + }); + + it('exclude - string', () => { + ctx.config.external_link.exclude = 'foo.com'; + isExternalLink('https://foo.com/').should.eql(false); + isExternalLink('https://bar.com/').should.eql(true); + isExternalLink('https://baz.com/').should.eql(true); + }); + + it('exclude - array', () => { + ctx.config.external_link.exclude = ['foo.com', 'bar.com']; + isExternalLink('https://foo.com/').should.eql(false); + isExternalLink('https://bar.com/').should.eql(false); + isExternalLink('https://baz.com/').should.eql(true); + }); +}); From c8d4629e1c84f8bc00f1dcc9cb54caabba12c848 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Mon, 28 Oct 2019 17:58:49 +0800 Subject: [PATCH 2/4] docs: mentions isExternalLink should use with bind(hexo) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 713fcca8..a09d6ffa 100644 --- a/README.md +++ b/README.md @@ -488,6 +488,7 @@ Following utilities require `bind(hexo)` / `bind(this)` / `call(hexo, input)` / - [`full_url_for()`](#full_url_forpath) - [`url_for()`](#url_forpath) - [`relative_url()`](#relative_urlfrom-to) +- [`isExternalLink()`](#isexternallinkurl) Below examples demonstrate different approaches to creating a [helper](https://hexo.io/api/helper) (each example is separated by `/******/`), From 42ca7295897de3e8b823249a7a02614470bc41ee Mon Sep 17 00:00:00 2001 From: SukkaW Date: Mon, 28 Oct 2019 18:31:38 +0800 Subject: [PATCH 3/4] test: change default config for isExternalLink --- test/isExternalLink.spec.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/isExternalLink.spec.js b/test/isExternalLink.spec.js index 9b52b648..903220ef 100644 --- a/test/isExternalLink.spec.js +++ b/test/isExternalLink.spec.js @@ -4,9 +4,7 @@ describe('isExternalLink', () => { const ctx = { config: { url: 'https://example.com', - external_link: { - exclude: '' - } + external_link: {} } }; @@ -29,6 +27,11 @@ describe('isExternalLink', () => { isExternalLink('javascript:alert(\'Hexo is Awesome\')').should.eql(false); }); + it('exclude - empty string', () => { + ctx.config.external_link.exclude = ''; + isExternalLink('https://hexo.io/').should.eql(true); + }); + it('exclude - string', () => { ctx.config.external_link.exclude = 'foo.com'; isExternalLink('https://foo.com/').should.eql(false); From a4a2873f5677d0c962ac933338157b35f078dc97 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Sat, 2 Nov 2019 09:48:06 +0800 Subject: [PATCH 4/4] refactor: rename file --- lib/{isExternalLink.js => is_external_link.js} | 0 test/{isExternalLink.spec.js => is_external_link.spec.js} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/{isExternalLink.js => is_external_link.js} (100%) rename test/{isExternalLink.spec.js => is_external_link.spec.js} (95%) diff --git a/lib/isExternalLink.js b/lib/is_external_link.js similarity index 100% rename from lib/isExternalLink.js rename to lib/is_external_link.js diff --git a/test/isExternalLink.spec.js b/test/is_external_link.spec.js similarity index 95% rename from test/isExternalLink.spec.js rename to test/is_external_link.spec.js index 903220ef..e2a11113 100644 --- a/test/isExternalLink.spec.js +++ b/test/is_external_link.spec.js @@ -8,7 +8,7 @@ describe('isExternalLink', () => { } }; - const isExternalLink = require('../lib/isExternalLink').bind(ctx); + const isExternalLink = require('../lib/is_external_link').bind(ctx); it('external link', () => { isExternalLink('https://hexo.io/').should.eql(true);