Skip to content

Commit 45a053d

Browse files
authored
fix: avoid ERR_TLS_CERT_ALTNAME_INVALID when redirected url (#126)
* 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
1 parent f8eec2d commit 45a053d

File tree

4 files changed

+427
-486
lines changed

4 files changed

+427
-486
lines changed

package.json

+9-9
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,22 @@
4646
"minimatch": "^3.0.4",
4747
"node-fetch": "^2.6.0",
4848
"p-memoize": "^3.1.0",
49-
"p-queue": "^6.1.1",
49+
"p-queue": "^6.2.0",
5050
"textlint-rule-helper": "^2.1.1"
5151
},
5252
"devDependencies": {
53-
"eslint": "^6.1.0",
54-
"eslint-config-airbnb-base": "^13.2.0",
55-
"eslint-config-prettier": "^6.0.0",
53+
"eslint": "^6.5.1",
54+
"eslint-config-airbnb-base": "^14.0.0",
55+
"eslint-config-prettier": "^6.4.0",
5656
"eslint-plugin-immutable": "^1.0.0",
5757
"eslint-plugin-import": "^2.18.2",
58-
"husky": "^3.0.2",
59-
"lint-staged": "^9.2.1",
58+
"husky": "^3.0.9",
59+
"lint-staged": "^9.4.2",
6060
"npm-run-all": "^4.1.5",
6161
"prettier": "^1.18.2",
62-
"textlint": "^11.3.1",
63-
"textlint-scripts": "^3.0.0-beta.1",
64-
"textlint-tester": "^5.1.9"
62+
"textlint": "^11.4.0",
63+
"textlint-scripts": "^3.0.0",
64+
"textlint-tester": "^5.1.10"
6565
},
6666
"engines": {
6767
"node": ">=4"

src/no-dead-link.js

+57-47
Original file line numberDiff line numberDiff line change
@@ -86,31 +86,59 @@ function waitTimeMs(ms) {
8686
});
8787
}
8888

89-
/**
90-
* Create isAliveURI function with options
91-
* @param {object} options
92-
* @returns {isAliveURI}
93-
*/
94-
const createCheckAliveURL = (options) => {
95-
const keepAliveAgents = {
96-
http: new http.Agent({ keepAlive: true }),
97-
https: new https.Agent({ keepAlive: true }),
98-
};
89+
const keepAliveAgents = {
90+
http: new http.Agent({ keepAlive: true }),
91+
https: new https.Agent({ keepAlive: true }),
92+
};
93+
94+
const createFetchWithRuleDefaults = (ruleOptions) => {
9995
/**
10096
* Use library agent, avoid to use global.http(s)Agent
10197
* Want to avoid Socket hang up
10298
* @param parsedURL
10399
* @returns {module:http.Agent|null|module:https.Agent}
104100
*/
105101
const getAgent = (parsedURL) => {
106-
if (!options.keepAlive) {
102+
if (!ruleOptions.keepAlive) {
107103
return null;
108104
}
109105
if (parsedURL.protocol === 'http:') {
110106
return keepAliveAgents.http;
111107
}
112108
return keepAliveAgents.https;
113109
};
110+
111+
return (uri, fetchOptions) => {
112+
const { host } = URL.parse(uri);
113+
return fetch(uri, {
114+
...fetchOptions,
115+
// Disable gzip compression in Node.js
116+
// to avoid the zlib's "unexpected end of file" error
117+
// https://github.com/request/request/issues/2045
118+
compress: false,
119+
// Some website require UserAgent and Accept header
120+
// to avoid ECONNRESET error
121+
// https://github.com/textlint-rule/textlint-rule-no-dead-link/issues/111
122+
headers: {
123+
'User-Agent': 'textlint-rule-no-dead-link/1.0',
124+
'Accept': '*/*',
125+
// Same host for target url
126+
// https://github.com/textlint-rule/textlint-rule-no-dead-link/issues/111
127+
'Host': host,
128+
},
129+
// custom http(s).agent
130+
agent: getAgent,
131+
});
132+
};
133+
};
134+
/**
135+
* Create isAliveURI function with ruleOptions
136+
* @param {object} ruleOptions
137+
* @returns {isAliveURI}
138+
*/
139+
const createCheckAliveURL = (ruleOptions) => {
140+
// Create fetch function for this rule
141+
const fetchWithDefaults = createFetchWithRuleDefaults(ruleOptions);
114142
/**
115143
* Checks if a given URI is alive or not.
116144
*
@@ -127,39 +155,21 @@ const createCheckAliveURL = (options) => {
127155
* @return {{ ok: boolean, redirect?: string, message: string }}
128156
*/
129157
return async function isAliveURI(uri, method = 'HEAD', maxRetryCount = 3, currentRetryCount = 0) {
130-
const { host } = URL.parse(uri);
131-
132158
const opts = {
133159
method,
134-
// Disable gzip compression in Node.js
135-
// to avoid the zlib's "unexpected end of file" error
136-
// https://github.com/request/request/issues/2045
137-
compress: false,
138-
// Some website require UserAgent and Accept header
139-
// to avoid ECONNRESET error
140-
// https://github.com/textlint-rule/textlint-rule-no-dead-link/issues/111
141-
headers: {
142-
'User-Agent': 'textlint-rule-no-dead-link/1.0',
143-
'Accept': '*/*',
144-
// Same host for target url
145-
// https://github.com/textlint-rule/textlint-rule-no-dead-link/issues/111
146-
'Host': host,
147-
},
148160
// Use `manual` redirect behaviour to get HTTP redirect status code
149161
// and see what kind of redirect is occurring
150162
redirect: 'manual',
151-
// custom http(s).agent
152-
agent: getAgent,
153163
};
154164
try {
155-
const res = await fetch(uri, opts);
156-
165+
const res = await fetchWithDefaults(uri, opts);
166+
// redirected
157167
if (isRedirect(res.status)) {
158-
const finalRes = await fetch(
159-
uri,
160-
Object.assign({}, opts, { redirect: 'follow' }),
168+
const redirectedUrl = res.headers.get('Location');
169+
const finalRes = await fetchWithDefaults(
170+
redirectedUrl,
171+
{ ...opts, redirect: 'follow' },
161172
);
162-
163173
const { hash } = URL.parse(uri);
164174
return {
165175
ok: finalRes.ok,
@@ -168,7 +178,7 @@ const createCheckAliveURL = (options) => {
168178
message: `${res.status} ${res.statusText}`,
169179
};
170180
}
171-
181+
// retry if it is not ok when use head request
172182
if (!res.ok && method === 'HEAD' && currentRetryCount < maxRetryCount) {
173183
return isAliveURI(uri, 'GET', maxRetryCount, currentRetryCount + 1);
174184
}
@@ -222,8 +232,8 @@ async function isAliveLocalFile(filePath) {
222232
function reporter(context, options = {}) {
223233
const { Syntax, getSource, report, RuleError, fixer, getFilePath } = context;
224234
const helper = new RuleHelper(context);
225-
const opts = Object.assign({}, DEFAULT_OPTIONS, options);
226-
const isAliveURI = createCheckAliveURL(opts);
235+
const ruleOptions = { ...DEFAULT_OPTIONS, ...options };
236+
const isAliveURI = createCheckAliveURL(ruleOptions);
227237
// 30sec memorized
228238
const memorizedIsAliveURI = pMemoize(isAliveURI, {
229239
maxAge: 30 * 1000,
@@ -236,17 +246,17 @@ function reporter(context, options = {}) {
236246
* @param {number} maxRetryCount retry count of linting
237247
*/
238248
const lint = async ({ node, uri, index }, maxRetryCount) => {
239-
if (isIgnored(uri, opts.ignore)) {
249+
if (isIgnored(uri, ruleOptions.ignore)) {
240250
return;
241251
}
242252

243253
if (isRelative(uri)) {
244-
if (!opts.checkRelative) {
254+
if (!ruleOptions.checkRelative) {
245255
return;
246256
}
247257

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

268278
const method =
269-
opts.preferGET.filter(
279+
ruleOptions.preferGET.filter(
270280
(origin) => getURLOrigin(uri) === getURLOrigin(origin),
271281
).length > 0
272282
? 'GET'
@@ -280,7 +290,7 @@ function reporter(context, options = {}) {
280290
if (!ok) {
281291
const lintMessage = `${uri} is dead. (${message})`;
282292
report(node, new RuleError(lintMessage, { index }));
283-
} else if (redirected && !opts.ignoreRedirects) {
293+
} else if (redirected && !ruleOptions.ignoreRedirects) {
284294
const lintMessage = `${uri} is redirected to ${redirectTo}. (${message})`;
285295
const fix = fixer.replaceTextRange(
286296
[index, index + uri.length],
@@ -341,11 +351,11 @@ function reporter(context, options = {}) {
341351

342352
[`${context.Syntax.Document}:exit`]() {
343353
const queue = new PQueue({
344-
concurrency: opts.concurrency,
345-
intervalCap: opts.intervalCap,
346-
interval: opts.interval
354+
concurrency: ruleOptions.concurrency,
355+
intervalCap: ruleOptions.intervalCap,
356+
interval: ruleOptions.interval,
347357
});
348-
const linkTasks = URIs.map((item) => () => lint(item, opts.retry));
358+
const linkTasks = URIs.map((item) => () => lint(item, ruleOptions.retry));
349359
return queue.addAll(linkTasks);
350360
},
351361
};

test/no-dead-link.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,13 @@ tester.run('no-dead-link', rule, {
9595
{
9696
text:
9797
'should preserve hash while ignoring redirect: [BDD](http://mochajs.org/#bdd)',
98-
output:
99-
'should preserve hash while ignoring redirect: [BDD](http://mochajs.org/#bdd)',
98+
options: {
99+
ignoreRedirects: true,
100+
},
101+
},
102+
// https://github.com/textlint-rule/textlint-rule-no-dead-link/issues/125
103+
{
104+
text: 'ignore redirect https://www.consul.io/intro/getting-started/kv.html',
100105
options: {
101106
ignoreRedirects: true,
102107
},

0 commit comments

Comments
 (0)