Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ Users and the development team are usually in the [Discord server](https://disco

_Vercel_ has given us a Pro account.

<a href="https://vercel.com/?utm_source=docsifyjsdocs" target="_blank"><img src="_media/vercel_logo.svg" width="100px"></a>
<a href="https://vercel.com/?utm_source=docsifyjsdocs" target="_blank"><img src="https://cdn.jsdelivr.net/gh/docsifyjs/docsify/docs/_media/vercel_logo.svg" width="100px"></a>
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ Set the route alias. You can freely manage routing rules. Supports RegExp.
```js
window.$docsify = {
alias: {
'/foo/(+*)': '/bar/$1', // supports regexp
'/foo/(.*)': '/bar/$1', // supports regexp
'/zh-cn/changelog': '/changelog',
'/changelog':
'https://raw.githubusercontent.com/docsifyjs/docsify/master/CHANGELOG',
Expand Down
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@
}
return (
html +
'<br/> <i>Vercel</i> has given us a Pro account <br/> <a href="https://vercel.com/?utm_source=docsifyjsdocs" target="_blank"><img src="_media/vercel_logo.svg" alt="Vercel" width="100" height="64"></a>'
'<br/> <i>Vercel</i> has given us a Pro account <br/> <a href="https://vercel.com/?utm_source=docsifyjsdocs" target="_blank"><img src="https://cdn.jsdelivr.net/gh/docsifyjs/docsify/docs/_media/vercel_logo.svg" alt="Vercel" width="100" height="64"></a>'
);
});
},
Expand Down
24 changes: 12 additions & 12 deletions packages/docsify-server-renderer/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 packages/docsify-server-renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"dependencies": {
"debug": "^4.3.2",
"docsify": "^4.11.6",
"docsify": "^4.12.0",
"dompurify": "^2.2.6",
"node-fetch": "^2.6.0",
"resolve-pathname": "^3.0.0"
Expand Down
28 changes: 1 addition & 27 deletions src/core/fetch/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable no-unused-vars */
import { callHook } from '../init/lifecycle';
import { getParentPath, stringifyQuery } from '../router/util';
import { noop } from '../util/core';
import { noop, isExternal } from '../util/core';
import { getAndActive } from '../event/sidebar';
import { get } from './ajax';

Expand All @@ -20,32 +20,6 @@ function loadNested(path, qs, file, next, vm, first) {
).then(next, _ => loadNested(path, qs, file, next, vm));
}

function isExternal(url) {
let match = url.match(
/^([^:/?#]+:)?(?:\/\/([^/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/
);
if (
typeof match[1] === 'string' &&
match[1].length > 0 &&
match[1].toLowerCase() !== location.protocol
) {
return true;
}
if (
typeof match[2] === 'string' &&
match[2].length > 0 &&
match[2].replace(
new RegExp(
':(' + { 'http:': 80, 'https:': 443 }[location.protocol] + ')?$'
),
''
) !== location.host
) {
return true;
}
return false;
}

export function fetchMixin(proto) {
let last;

Expand Down
32 changes: 32 additions & 0 deletions src/core/util/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,35 @@ export function noop() {}
export function isFn(obj) {
return typeof obj === 'function';
}

/**
* Check if url is external
* @param {String} string url
* @returns {Boolean} True if the passed-in url is external
*/
export function isExternal(url) {
let match = url.match(
/^([^:/?#]+:)?(?:\/{2,}([^/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/
);

if (
typeof match[1] === 'string' &&
match[1].length > 0 &&
match[1].toLowerCase() !== location.protocol
) {
return true;
}
if (
typeof match[2] === 'string' &&
match[2].length > 0 &&
match[2].replace(
new RegExp(
':(' + { 'http:': 80, 'https:': 443 }[location.protocol] + ')?$'
),
''
) !== location.host
) {
return true;
}
return false;
}
32 changes: 32 additions & 0 deletions test/e2e/security.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const docsifyInit = require('../helpers/docsify-init');

describe(`Security`, function() {
const sharedOptions = {
markdown: {
homepage: '# Hello World',
},
routes: {
'test.md': '# Test Page',
},
};

describe(`Cross Site Scripting (XSS)`, function() {
const slashStrings = ['//', '///'];

for (const slashString of slashStrings) {
const hash = `#${slashString}domain.com/file.md`;

test(`should not load remote content from hash (${hash})`, async () => {
await docsifyInit(sharedOptions);
await expect(page).toHaveText('#main', 'Hello World');
await page.evaluate(() => (location.hash = '#/test'));
await expect(page).toHaveText('#main', 'Test Page');
await page.evaluate(newHash => {
location.hash = newHash;
}, hash);
await expect(page).toHaveText('#main', 'Hello World');
expect(page.url()).toMatch(/#\/$/);
});
}
});
});
63 changes: 63 additions & 0 deletions test/unit/core-util.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const { isExternal } = require('../../src/core/util');

// Core util
// -----------------------------------------------------------------------------
describe('core/util', () => {
// isExternal()
// ---------------------------------------------------------------------------
describe('isExternal()', () => {
// cases non external
test('non external local url with one /', () => {
const result = isExternal(`/${location.host}/docsify/demo.md`);

expect(result).toBeFalsy();
});

test('non external local url with two //', () => {
const result = isExternal(`//${location.host}/docsify/demo.md`);

expect(result).toBeFalsy();
});

test('non external local url with three ///', () => {
const result = isExternal(`///${location.host}/docsify/demo.md`);

expect(result).toBeFalsy();
});

test('non external local url with more /', () => {
const result = isExternal(
`//////////////////${location.host}/docsify/demo.md`
);

expect(result).toBeFalsy();
});

test('non external url with one /', () => {
const result = isExternal('/example.github.io/docsify/demo.md');

expect(result).toBeFalsy();
});

// cases is external
test('external url with two //', () => {
const result = isExternal('/docsify/demo.md');

expect(result).toBeFalsy();
});

test('external url with three ///', () => {
const result = isExternal('///example.github.io/docsify/demo.md');

expect(result).toBeTruthy();
});

test('external url with more /', () => {
const result = isExternal(
'//////////////////example.github.io/docsify/demo.md'
);

expect(result).toBeTruthy();
});
});
});