mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-07-19 07:44:02 +02:00
Fix DoH URL path validation, more error handling for connections
This commit is contained in:
parent
47cf7f9d13
commit
a670386281
3 changed files with 34 additions and 22 deletions
|
@ -480,6 +480,7 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
|
||||||
|
|
||||||
let client;
|
let client;
|
||||||
let resolver;
|
let resolver;
|
||||||
|
const socketName = `${resolverServer}:${resolverPort}`;
|
||||||
// Transport method determines which client type to use
|
// Transport method determines which client type to use
|
||||||
switch (method) {
|
switch (method) {
|
||||||
|
|
||||||
|
@ -500,13 +501,13 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
|
||||||
options.servername = resolverServer;
|
options.servername = resolverServer;
|
||||||
}
|
}
|
||||||
client = tls.connect(options, () => {
|
client = tls.connect(options, () => {
|
||||||
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
log.debug("dns", `Connected to ${socketName}`);
|
||||||
client.write(buf);
|
client.write(buf);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
client = new Socket();
|
client = new Socket();
|
||||||
client.connect(resolverPort, resolverServer, () => {
|
client.connect(resolverPort, resolverServer, () => {
|
||||||
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
log.debug("dns", `Connected to ${socketName}`);
|
||||||
client.write(buf);
|
client.write(buf);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -518,11 +519,14 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
|
||||||
let expectedLength = 0;
|
let expectedLength = 0;
|
||||||
let isValidLength = false;
|
let isValidLength = false;
|
||||||
client.on("error", (err) => {
|
client.on("error", (err) => {
|
||||||
|
if (err.code === "ETIMEDOUT") {
|
||||||
|
err.message = `Connection to ${socketName} timed out`;
|
||||||
|
}
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
client.setTimeout(timeout, () => {
|
client.setTimeout(timeout, () => {
|
||||||
client.destroy();
|
client.destroy();
|
||||||
reject({ message: "Connection timed out" });
|
reject({ message: `Request to ${socketName} timed out` });
|
||||||
});
|
});
|
||||||
client.on("data", (chunk) => {
|
client.on("data", (chunk) => {
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
|
@ -542,7 +546,7 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
client.on("close", () => {
|
client.on("close", () => {
|
||||||
log.debug("dns", "Connection closed");
|
log.debug("dns", `Connection to ${socketName} closed`);
|
||||||
if (!isValidLength) {
|
if (!isValidLength) {
|
||||||
reject({ message: lenErrMsg });
|
reject({ message: lenErrMsg });
|
||||||
}
|
}
|
||||||
|
@ -553,7 +557,7 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
|
||||||
|
|
||||||
case "DOH": {
|
case "DOH": {
|
||||||
const queryPath = dohUsePost ? dohQuery : `${dohQuery}?dns=${buf.toString("base64url")}`;
|
const queryPath = dohUsePost ? dohQuery : `${dohQuery}?dns=${buf.toString("base64url")}`;
|
||||||
const requestURL = url.parse(`https://${resolverServer}:${resolverPort}/${queryPath}`, true);
|
const requestURL = url.parse(`https://${socketName}/${queryPath}`, true);
|
||||||
const mimeType = "application/dns-message";
|
const mimeType = "application/dns-message";
|
||||||
const options = {
|
const options = {
|
||||||
hostname: requestURL.hostname,
|
hostname: requestURL.hostname,
|
||||||
|
@ -596,10 +600,10 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
|
||||||
}
|
}
|
||||||
// Validate response from resolver
|
// Validate response from resolver
|
||||||
if (statusCode !== 200) {
|
if (statusCode !== 200) {
|
||||||
reject({ message: `Request failed with status code ${statusCode}` });
|
reject({ message: `Request to ${socketName} failed with status code ${statusCode}` });
|
||||||
return;
|
return;
|
||||||
} else if (contentType !== mimeType) {
|
} else if (contentType !== mimeType) {
|
||||||
reject({ message: `Content-Type was "${contentType}", expected ${mimeType}` });
|
reject({ message: `Response from ${socketName} Content-Type was "${contentType}", expected ${mimeType}` });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Read the response body into a buffer
|
// Read the response body into a buffer
|
||||||
|
@ -627,10 +631,10 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
|
||||||
});
|
});
|
||||||
client.setTimeout(timeout, () => {
|
client.setTimeout(timeout, () => {
|
||||||
client.destroy();
|
client.destroy();
|
||||||
reject({ message: "Request timed out" });
|
reject({ message: `Request to ${socketName} timed out` });
|
||||||
});
|
});
|
||||||
client.on("connect", () => {
|
client.on("connect", () => {
|
||||||
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
log.debug("dns", `Connected to ${socketName}`);
|
||||||
});
|
});
|
||||||
const req = client.request(headers);
|
const req = client.request(headers);
|
||||||
req.on("error", (err) => {
|
req.on("error", (err) => {
|
||||||
|
@ -653,11 +657,11 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
|
||||||
});
|
});
|
||||||
client.setTimeout(timeout, () => {
|
client.setTimeout(timeout, () => {
|
||||||
client.destroy();
|
client.destroy();
|
||||||
reject({ message: "Request timed out" });
|
reject({ message: `Request to ${socketName} timed out` });
|
||||||
});
|
});
|
||||||
client.on("socket", (socket) => {
|
client.on("socket", (socket) => {
|
||||||
socket.on("secureConnect", () => {
|
socket.on("secureConnect", () => {
|
||||||
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
log.debug("dns", `Connected to ${socketName}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (dohUsePost) {
|
if (dohUsePost) {
|
||||||
|
@ -669,7 +673,7 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
client.on("close", () => {
|
client.on("close", () => {
|
||||||
log.debug("dns", "Connection closed");
|
log.debug("dns", `Connection to ${socketName} closed`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
@ -695,15 +699,15 @@ exports.dnsResolve = function (opts, resolverServer, resolverPort, transport) {
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
client.on("listening", () => {
|
client.on("listening", () => {
|
||||||
log.debug("dns", `Connected to ${resolverServer}:${resolverPort}`);
|
log.debug("dns", `Connected to ${socketName}`);
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
reject({ message: "Query timed out" });
|
reject({ message: `Query to ${socketName} timed out` });
|
||||||
client.close();
|
client.close();
|
||||||
}, timeout);
|
}, timeout);
|
||||||
});
|
});
|
||||||
client.on("close", () => {
|
client.on("close", () => {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
log.debug("dns", "Connection closed");
|
log.debug("dns", `Connection to ${socketName} closed`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
client.send(buf, 0, buf.length, resolverPort, resolverServer);
|
client.send(buf, 0, buf.length, resolverPort, resolverServer);
|
||||||
|
|
|
@ -1279,6 +1279,7 @@ export default {
|
||||||
case "DoH":
|
case "DoH":
|
||||||
return this.hostnameRegexPattern.source;
|
return this.hostnameRegexPattern.source;
|
||||||
case "DoT":
|
case "DoT":
|
||||||
|
case "DoQ":
|
||||||
return this.ipOrHostnameRegexPattern.source;
|
return this.ipOrHostnameRegexPattern.source;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,20 +162,27 @@ export function dnsNameRegexPattern() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regex patterns for validating URL paths
|
* Regex patterns for validating URL paths
|
||||||
* @param {boolean} qstr whether or not the url follows query string format
|
* @param {boolean} qstr True if the url should contain a query string
|
||||||
|
* @param {boolean} tmpl True if the url should contain templating, `{query}`
|
||||||
|
* Takes precedence over qstr if both are true.
|
||||||
* @returns {RegExp} The requested regex
|
* @returns {RegExp} The requested regex
|
||||||
*/
|
*/
|
||||||
export function urlPathRegexPattern(qstr = true) {
|
export function urlPathRegexPattern(qstr = false, tmpl = false) {
|
||||||
|
// Matches any URL path, including empty string
|
||||||
|
const pathRegexPattern = /^\/?(([a-zA-Z0-9\-_%])+\/)*[a-zA-Z0-9\-_%]*(\?([a-zA-Z0-9\-_%]+=[a-zA-Z0-9\-_%]*&?)+)?$/;
|
||||||
// Ensures a URL path follows query string format
|
// Ensures a URL path follows query string format
|
||||||
const queryStringRegexPattern = /^\/?(([a-zA-Z0-9\-_%])+\/)*[a-zA-Z0-9\-_%]*\?([a-zA-Z0-9\-_%]+=[a-zA-Z0-9\-_%]*&?)+$/;
|
const queryRegexPattern = /^\/?(([a-zA-Z0-9\-_%])+\/)*[a-zA-Z0-9\-_%]*\?([a-zA-Z0-9\-_%]+=[a-zA-Z0-9\-_%]*&?)+$/;
|
||||||
// Only checks for valid URL path containing "{query}"
|
// Only checks for valid URL path containing "{query}"
|
||||||
/* eslint-disable-next-line no-useless-escape */
|
/* eslint-disable-next-line no-useless-escape */
|
||||||
const queryRegexPattern = /^[a-zA-Z0-9\-._~:\/?#\[\]@!$&'\(\)*+,;=]*\{query\}[a-zA-Z0-9\-._~:\/?#\[\]@!$&'\(\)*+,;=]*$/;
|
const queryTemplateRegexPattern = /^[a-zA-Z0-9\-._~:\/?#\[\]@!$&'\(\)*+,;=]*\{query\}[a-zA-Z0-9\-._~:\/?#\[\]@!$&'\(\)*+,;=]*$/;
|
||||||
|
|
||||||
if (qstr) {
|
if (tmpl) {
|
||||||
return queryStringRegexPattern;
|
return queryTemplateRegexPattern;
|
||||||
}
|
}
|
||||||
return queryRegexPattern;
|
if (qstr) {
|
||||||
|
return queryRegexPattern;
|
||||||
|
}
|
||||||
|
return pathRegexPattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Reference in a new issue