Skip to content

Commit

Permalink
fix: avoid ERR_TLS_CERT_ALTNAME_INVALID when redirected url (#126)
Browse files Browse the repository at this point in the history
* chore(deps): update textlint-script@3

* fix: avoid ERR_TLS_CERT_ALTNAME_INVALID when redirected url

Always set valid HOST for `fetch`'s url.

fix #125

* chore: remove log

* test: --no-timeout

* test: remove --no-timeout
  • Loading branch information
azu committed Nov 28, 2019
1 parent f8eec2d commit 45a053d
Show file tree
Hide file tree
Showing 4 changed files with 427 additions and 486 deletions.
18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,22 @@
"minimatch": "^3.0.4",
"node-fetch": "^2.6.0",
"p-memoize": "^3.1.0",
"p-queue": "^6.1.1",
"p-queue": "^6.2.0",
"textlint-rule-helper": "^2.1.1"
},
"devDependencies": {
"eslint": "^6.1.0",
"eslint-config-airbnb-base": "^13.2.0",
"eslint-config-prettier": "^6.0.0",
"eslint": "^6.5.1",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^6.4.0",
"eslint-plugin-immutable": "^1.0.0",
"eslint-plugin-import": "^2.18.2",
"husky": "^3.0.2",
"lint-staged": "^9.2.1",
"husky": "^3.0.9",
"lint-staged": "^9.4.2",
"npm-run-all": "^4.1.5",
"prettier": "^1.18.2",
"textlint": "^11.3.1",
"textlint-scripts": "^3.0.0-beta.1",
"textlint-tester": "^5.1.9"
"textlint": "^11.4.0",
"textlint-scripts": "^3.0.0",
"textlint-tester": "^5.1.10"
},
"engines": {
"node": ">=4"
Expand Down
104 changes: 57 additions & 47 deletions src/no-dead-link.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,31 +86,59 @@ function waitTimeMs(ms) {
});
}

/**
* Create isAliveURI function with options
* @param {object} options
* @returns {isAliveURI}
*/
const createCheckAliveURL = (options) => {
const keepAliveAgents = {
http: new http.Agent({ keepAlive: true }),
https: new https.Agent({ keepAlive: true }),
};
const keepAliveAgents = {
http: new http.Agent({ keepAlive: true }),
https: new https.Agent({ keepAlive: true }),
};

const createFetchWithRuleDefaults = (ruleOptions) => {
/**
* Use library agent, avoid to use global.http(s)Agent
* Want to avoid Socket hang up
* @param parsedURL
* @returns {module:http.Agent|null|module:https.Agent}
*/
const getAgent = (parsedURL) => {
if (!options.keepAlive) {
if (!ruleOptions.keepAlive) {
return null;
}
if (parsedURL.protocol === 'http:') {
return keepAliveAgents.http;
}
return keepAliveAgents.https;
};

return (uri, fetchOptions) => {
const { host } = URL.parse(uri);
return fetch(uri, {
...fetchOptions,
// Disable gzip compression in Node.js
// to avoid the zlib's "unexpected end of file" error
// https://github.com/request/request/issues/2045
compress: false,
// Some website require UserAgent and Accept header
// to avoid ECONNRESET error
// https://github.com/textlint-rule/textlint-rule-no-dead-link/issues/111
headers: {
'User-Agent': 'textlint-rule-no-dead-link/1.0',
'Accept': '*/*',
// Same host for target url
// https://github.com/textlint-rule/textlint-rule-no-dead-link/issues/111
'Host': host,
},
// custom http(s).agent
agent: getAgent,
});
};
};
/**
* Create isAliveURI function with ruleOptions
* @param {object} ruleOptions
* @returns {isAliveURI}
*/
const createCheckAliveURL = (ruleOptions) => {
// Create fetch function for this rule
const fetchWithDefaults = createFetchWithRuleDefaults(ruleOptions);
/**
* Checks if a given URI is alive or not.
*
Expand All @@ -127,39 +155,21 @@ const createCheckAliveURL = (options) => {
* @return {{ ok: boolean, redirect?: string, message: string }}
*/
return async function isAliveURI(uri, method = 'HEAD', maxRetryCount = 3, currentRetryCount = 0) {
const { host } = URL.parse(uri);

const opts = {
method,
// Disable gzip compression in Node.js
// to avoid the zlib's "unexpected end of file" error
// https://github.com/request/request/issues/2045
compress: false,
// Some website require UserAgent and Accept header
// to avoid ECONNRESET error
// https://github.com/textlint-rule/textlint-rule-no-dead-link/issues/111
headers: {
'User-Agent': 'textlint-rule-no-dead-link/1.0',
'Accept': '*/*',
// Same host for target url
// https://github.com/textlint-rule/textlint-rule-no-dead-link/issues/111
'Host': host,
},
// Use `manual` redirect behaviour to get HTTP redirect status code
// and see what kind of redirect is occurring
redirect: 'manual',
// custom http(s).agent
agent: getAgent,
};
try {
const res = await fetch(uri, opts);

const res = await fetchWithDefaults(uri, opts);
// redirected
if (isRedirect(res.status)) {
const finalRes = await fetch(
uri,
Object.assign({}, opts, { redirect: 'follow' }),
const redirectedUrl = res.headers.get('Location');
const finalRes = await fetchWithDefaults(
redirectedUrl,
{ ...opts, redirect: 'follow' },
);

const { hash } = URL.parse(uri);
return {
ok: finalRes.ok,
Expand All @@ -168,7 +178,7 @@ const createCheckAliveURL = (options) => {
message: `${res.status} ${res.statusText}`,
};
}

// retry if it is not ok when use head request
if (!res.ok && method === 'HEAD' && currentRetryCount < maxRetryCount) {
return isAliveURI(uri, 'GET', maxRetryCount, currentRetryCount + 1);
}
Expand Down Expand Up @@ -222,8 +232,8 @@ async function isAliveLocalFile(filePath) {
function reporter(context, options = {}) {
const { Syntax, getSource, report, RuleError, fixer, getFilePath } = context;
const helper = new RuleHelper(context);
const opts = Object.assign({}, DEFAULT_OPTIONS, options);
const isAliveURI = createCheckAliveURL(opts);
const ruleOptions = { ...DEFAULT_OPTIONS, ...options };
const isAliveURI = createCheckAliveURL(ruleOptions);
// 30sec memorized
const memorizedIsAliveURI = pMemoize(isAliveURI, {
maxAge: 30 * 1000,
Expand All @@ -236,17 +246,17 @@ function reporter(context, options = {}) {
* @param {number} maxRetryCount retry count of linting
*/
const lint = async ({ node, uri, index }, maxRetryCount) => {
if (isIgnored(uri, opts.ignore)) {
if (isIgnored(uri, ruleOptions.ignore)) {
return;
}

if (isRelative(uri)) {
if (!opts.checkRelative) {
if (!ruleOptions.checkRelative) {
return;
}

const filePath = getFilePath();
const base = opts.baseURI || filePath;
const base = ruleOptions.baseURI || filePath;
if (!base) {
const message =
'Unable to resolve the relative URI. Please check if the base URI is correctly specified.';
Expand All @@ -266,7 +276,7 @@ function reporter(context, options = {}) {
}

const method =
opts.preferGET.filter(
ruleOptions.preferGET.filter(
(origin) => getURLOrigin(uri) === getURLOrigin(origin),
).length > 0
? 'GET'
Expand All @@ -280,7 +290,7 @@ function reporter(context, options = {}) {
if (!ok) {
const lintMessage = `${uri} is dead. (${message})`;
report(node, new RuleError(lintMessage, { index }));
} else if (redirected && !opts.ignoreRedirects) {
} else if (redirected && !ruleOptions.ignoreRedirects) {
const lintMessage = `${uri} is redirected to ${redirectTo}. (${message})`;
const fix = fixer.replaceTextRange(
[index, index + uri.length],
Expand Down Expand Up @@ -341,11 +351,11 @@ function reporter(context, options = {}) {

[`${context.Syntax.Document}:exit`]() {
const queue = new PQueue({
concurrency: opts.concurrency,
intervalCap: opts.intervalCap,
interval: opts.interval
concurrency: ruleOptions.concurrency,
intervalCap: ruleOptions.intervalCap,
interval: ruleOptions.interval,
});
const linkTasks = URIs.map((item) => () => lint(item, opts.retry));
const linkTasks = URIs.map((item) => () => lint(item, ruleOptions.retry));
return queue.addAll(linkTasks);
},
};
Expand Down
9 changes: 7 additions & 2 deletions test/no-dead-link.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,13 @@ tester.run('no-dead-link', rule, {
{
text:
'should preserve hash while ignoring redirect: [BDD](http://mochajs.org/#bdd)',
output:
'should preserve hash while ignoring redirect: [BDD](http://mochajs.org/#bdd)',
options: {
ignoreRedirects: true,
},
},
// https://github.com/textlint-rule/textlint-rule-no-dead-link/issues/125
{
text: 'ignore redirect https://www.consul.io/intro/getting-started/kv.html',
options: {
ignoreRedirects: true,
},
Expand Down

0 comments on commit 45a053d

Please sign in to comment.