Skip to content
This repository has been archived by the owner on Apr 3, 2024. It is now read-only.

Commit

Permalink
feat: add rel=nofollow to links if the nofollow option is set (#426)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: links now default to having rel=nofollow set
  • Loading branch information
ceejbot authored and bcoe committed Jan 17, 2018
1 parent cf7ca92 commit bb36977
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 28 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -68,6 +68,7 @@ The default options are as follows:
```js
{
sanitize: true, // remove script tags and stuff
nofollow: true, // add rel=nofollow to all links
linkify: true, // turn orphan URLs into hyperlinks
highlightSyntax: true, // run highlights on fenced code blocks
prefixHeadingIds: true, // prevent DOM id collisions
Expand Down
5 changes: 3 additions & 2 deletions index.js
Expand Up @@ -6,6 +6,7 @@ var markyInfo = require('./marky.json')

var defaultOptions = {
sanitize: true,
nofollow: true,
linkify: true,
highlightSyntax: true,
prefixHeadingIds: true,
Expand Down Expand Up @@ -62,7 +63,7 @@ var marky = module.exports = function (markdown, options) {
}

marky.parsePackageDescription = function (description) {
return sanitize(render.renderPackageDescription(description))
return sanitize(render.renderPackageDescription(description), defaultOptions)
}

marky.getParser = function (options) {
Expand All @@ -73,7 +74,7 @@ marky.getParser = function (options) {
if (options.sanitize) {
var originalRender = parser.render
parser.render = function (markdown) {
return sanitize(originalRender.call(parser, markdown))
return sanitize(originalRender.call(parser, markdown), options)
}
}
return parser
Expand Down
12 changes: 12 additions & 0 deletions lib/plugin/nofollow.js
@@ -0,0 +1,12 @@
// Set rel=nofollow on all links if we have set `nofollow` in the options.

module.exports = function (md, options) {
var defaultRender = md.renderer.rules.link_open || function (tokens, idx, options, env, self) {
return self.renderToken(tokens, idx, options)
}

md.renderer.rules.link_open = function handleLink (tokens, idx, options, env, self) {
tokens[idx].attrPush(['rel', 'nofollow'])
return defaultRender(tokens, idx, options, env, self)
}
}
5 changes: 5 additions & 0 deletions lib/render.js
Expand Up @@ -22,6 +22,7 @@ var githubHeadings = require('./gfm/indented-headings')
var overrideLinkDestinationParser = require('./gfm/override-link-destination-parser')
var looseLinkParsing = require('./gfm/link')
var looseImageParsing = require('./gfm/image')
var relNoFollow = require('./plugin/nofollow')

if (typeof process.browser === 'undefined') {
var Highlights = require('highlights')
Expand Down Expand Up @@ -88,6 +89,10 @@ render.getParser = function (options) {
.use(looseLinkParsing)
.use(looseImageParsing)

if (options.nofollow) {
parser.use(relNoFollow)
}

if (options.highlightSyntax) {
parser.use(codeWrap)
.use(fenceLanguageAliasing)
Expand Down
26 changes: 23 additions & 3 deletions lib/sanitize.js
Expand Up @@ -6,7 +6,8 @@ module.exports = function (html, options) {
transformTags: {
'*': prefixHTMLids,
'td': sanitizeCellStyle,
'th': sanitizeCellStyle
'th': sanitizeCellStyle,
'a': getNofollowSanitize(options)
}
})
} else {
Expand Down Expand Up @@ -66,7 +67,7 @@ function getSanitizerConfig (options) {
h4: ['id', 'align'],
h5: ['id', 'align'],
h6: ['id', 'align'],
a: ['href', 'id', 'name', 'target', 'title', 'aria-hidden'],
a: ['href', 'id', 'name', 'target', 'title', 'aria-hidden', 'rel'],
img: ['alt', 'id', 'src', 'width', 'height', 'align', 'valign', 'title', 'style'],
p: ['align'],
meta: ['name', 'content'],
Expand Down Expand Up @@ -97,11 +98,30 @@ function getSanitizerConfig (options) {
},
transformTags: {
'td': sanitizeCellStyle,
'th': sanitizeCellStyle
'th': sanitizeCellStyle,
'a': getNofollowSanitize(options)
}
}
}

function getNofollowSanitize (options) {
return options.nofollow ? sanitizeAnchorNofollow : sanitizeIdentity
}

function sanitizeIdentity (tagName, attribs) {
return {
tagName: tagName,
attribs: attribs
}
}

function sanitizeAnchorNofollow (tagName, attribs) {
if (attribs.href) {
attribs.rel = 'nofollow'
}
return sanitizeIdentity(tagName, attribs)
}

// Allow table cell alignment
function sanitizeCellStyle (tagName, attribs) {
// if we don't add the 'style' to the allowedAttributes above, it will be
Expand Down
152 changes: 139 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -67,7 +67,7 @@
"markdown-it-lazy-headers": "^0.1.3",
"markdown-it-task-lists": "^2.0.1",
"property-ttl": "^1.0.0",
"sanitize-html": "^1.14.1",
"sanitize-html": "^1.17.0",
"similarity": "^1.0.1"
},
"devDependencies": {
Expand Down
28 changes: 28 additions & 0 deletions test/nofollow.js
@@ -0,0 +1,28 @@

/* globals describe, it */

var assert = require('assert')
var marky = require('..')
var cheerio = require('cheerio')

describe('nofollow plugin', function () {
it('adds rel=nofollow attributes to links', function () {
var rendered = cheerio.load(marky('[link text](https://example.com/spam)'))
assert.equal(rendered('a').attr('rel'), 'nofollow')
})

it('respects the option to turn off nofollow', function () {
var rendered = cheerio.load(marky('[link text](https://example.com/spam)', { nofollow: false }))
assert.equal(rendered('a').attr('rel'), undefined)
})

it('adds rel=nofollow attributes to html links', function () {
var rendered = cheerio.load(marky('<a href=https://example.com/spam>link text</a>'))
assert.equal(rendered('a').attr('rel'), 'nofollow')
})

it('respects the option to turn off nofollow for html', function () {
var rendered = cheerio.load(marky('<a href=https://example.com/spam>link text</a>', { nofollow: false }))
assert.equal(rendered('a').attr('rel'), undefined)
})
})
2 changes: 1 addition & 1 deletion test/packagize.js
Expand Up @@ -74,7 +74,7 @@ describe('packagize', function () {

it('parses description as markdown and removes script tags', function () {
var description = marky.parsePackageDescription('bad <script>/xss</script> [hax](http://hax.com)')
assert.equal(description, 'bad <a href="http://hax.com">hax</a>')
assert.equal(description, 'bad <a href="http://hax.com" rel="nofollow">hax</a>')
})

it('safely handles inline code blocks', function () {
Expand Down

0 comments on commit bb36977

Please sign in to comment.