Merge pull request #3283 from BPplays/ipv6_auto adds checking for if localhost resolves + JSDoc additions
Signed| @@ -70,6 +70,7 @@ import { | |||
| 70 | getSeparator, | 70 | getSeparator, |
| 71 | stringToBool, | 71 | stringToBool, |
| 72 | urlHostnameToIPv6, | 72 | urlHostnameToIPv6, |
| 73 | canResolve, | ||
| 73 | } from './src/util.js'; | 74 | } from './src/util.js'; |
| 74 | import { UPLOADS_DIRECTORY } from './src/constants.js'; | 75 | import { UPLOADS_DIRECTORY } from './src/constants.js'; |
| 75 | import { ensureThumbnailCache } from './src/endpoints/thumbnails.js'; | 76 | import { ensureThumbnailCache } from './src/endpoints/thumbnails.js'; |
| @@ -247,28 +248,41 @@ app.use(compression()); | |||
| 247 | app.use(responseTime()); | 248 | app.use(responseTime()); |
| 248 | 249 | ||
| 249 | 250 | ||
| 251 | /** @type {number} */ | ||
| 250 | const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getConfigValue('port', DEFAULT_PORT); | 252 | const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getConfigValue('port', DEFAULT_PORT); |
| 253 | /** @type {boolean} */ | ||
| 251 | const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl; | 254 | const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl; |
| 255 | /** @type {boolean} */ | ||
| 252 | const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN); | 256 | const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN); |
| 257 | /** @type {boolean} */ | ||
| 253 | const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY); | 258 | const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY); |
| 254 | const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST); | 259 | const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST); |
| 260 | /** @type {string} */ | ||
| 255 | const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data'); | 261 | const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data'); |
| 262 | /** @type {boolean} */ | ||
| 256 | const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED); | 263 | const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED); |
| 257 | const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH); | 264 | const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH); |
| 258 | const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH); | 265 | const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH); |
| 266 | /** @type {boolean} */ | ||
| 259 | const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS); | 267 | const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS); |
| 260 | 268 | ||
| 261 | const uploadsPath = path.join(dataRoot, UPLOADS_DIRECTORY); | 269 | const uploadsPath = path.join(dataRoot, UPLOADS_DIRECTORY); |
| 262 | 270 | ||
| 263 | 271 | ||
| 272 | /** @type {boolean | "auto"} */ | ||
| 264 | let enableIPv6 = stringToBool(cliArguments.enableIPv6) ?? getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6); | 273 | let enableIPv6 = stringToBool(cliArguments.enableIPv6) ?? getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6); |
| 274 | /** @type {boolean | "auto"} */ | ||
| 265 | let enableIPv4 = stringToBool(cliArguments.enableIPv4) ?? getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4); | 275 | let enableIPv4 = stringToBool(cliArguments.enableIPv4) ?? getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4); |
| 266 | 276 | ||
| 277 | /** @type {string} */ | ||
| 267 | const autorunHostname = cliArguments.autorunHostname ?? getConfigValue('autorunHostname', DEFAULT_AUTORUN_HOSTNAME); | 278 | const autorunHostname = cliArguments.autorunHostname ?? getConfigValue('autorunHostname', DEFAULT_AUTORUN_HOSTNAME); |
| 279 | /** @type {number} */ | ||
| 268 | const autorunPortOverride = cliArguments.autorunPortOverride ?? getConfigValue('autorunPortOverride', DEFAULT_AUTORUN_PORT); | 280 | const autorunPortOverride = cliArguments.autorunPortOverride ?? getConfigValue('autorunPortOverride', DEFAULT_AUTORUN_PORT); |
| 269 | 281 | ||
| 282 | /** @type {boolean} */ | ||
| 270 | const dnsPreferIPv6 = cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', DEFAULT_PREFER_IPV6); | 283 | const dnsPreferIPv6 = cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', DEFAULT_PREFER_IPV6); |
| 271 | 284 | ||
| 285 | /** @type {boolean} */ | ||
| 272 | const avoidLocalhost = cliArguments.avoidLocalhost ?? getConfigValue('avoidLocalhost', DEFAULT_AVOID_LOCALHOST); | 286 | const avoidLocalhost = cliArguments.avoidLocalhost ?? getConfigValue('avoidLocalhost', DEFAULT_AVOID_LOCALHOST); |
| 273 | 287 | ||
| 274 | const proxyEnabled = cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', DEFAULT_PROXY_ENABLED); | 288 | const proxyEnabled = cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', DEFAULT_PROXY_ENABLED); |
| @@ -382,6 +396,16 @@ function getSessionCookieAge() { | |||
| 382 | return undefined; | 396 | return undefined; |
| 383 | } | 397 | } |
| 384 | 398 | ||
| 399 | |||
| 400 | /** | ||
| 401 | * Checks the network interfaces to determine the presence of IPv6 and IPv4 addresses. | ||
| 402 | * | ||
| 403 | * @returns {Promise<[boolean, boolean, boolean, boolean]>} A promise that resolves to an array containing: | ||
| 404 | * - [0]: `hasIPv6` (boolean) - Whether the computer has any IPv6 address, including (`::1`). | ||
| 405 | * - [1]: `hasIPv4` (boolean) - Whether the computer has any IPv4 address, including (`127.0.0.1`). | ||
| 406 | * - [2]: `hasIPv6Local` (boolean) - Whether the computer has local IPv6 address (`::1`). | ||
| 407 | * - [3]: `hasIPv4Local` (boolean) - Whether the computer has local IPv4 address (`127.0.0.1`). | ||
| 408 | */ | ||
| 385 | async function getHasIP() { | 409 | async function getHasIP() { |
| 386 | let hasIPv6 = false; | 410 | let hasIPv6 = false; |
| 387 | let hasIPv6Local = false; | 411 | let hasIPv6Local = false; |
| @@ -395,6 +419,7 @@ async function getHasIP() { | |||
| 395 | if (iface === undefined) { | 419 | if (iface === undefined) { |
| 396 | continue; | 420 | continue; |
| 397 | } | 421 | } |
| 422 | |||
| 398 | for (const info of iface) { | 423 | for (const info of iface) { |
| 399 | if (info.family === 'IPv6') { | 424 | if (info.family === 'IPv6') { |
| 400 | hasIPv6 = true; | 425 | hasIPv6 = true; |
| @@ -413,7 +438,12 @@ async function getHasIP() { | |||
| 413 | } | 438 | } |
| 414 | if (hasIPv6 && hasIPv4 && hasIPv6Local && hasIPv4Local) break; | 439 | if (hasIPv6 && hasIPv4 && hasIPv6Local && hasIPv4Local) break; |
| 415 | } | 440 | } |
| 416 | return [hasIPv6, hasIPv4, hasIPv6Local, hasIPv4Local]; | 441 | return [ |
| 442 | hasIPv6, | ||
| 443 | hasIPv4, | ||
| 444 | hasIPv6Local, | ||
| 445 | hasIPv4Local, | ||
| 446 | ]; | ||
| 417 | } | 447 | } |
| 418 | 448 | ||
| 419 | app.use(cookieSession({ | 449 | app.use(cookieSession({ |
| @@ -734,12 +764,16 @@ const preSetupTasks = async function () { | |||
| 734 | 764 | ||
| 735 | /** | 765 | /** |
| 736 | * Gets the hostname to use for autorun in the browser. | 766 | * Gets the hostname to use for autorun in the browser. |
| 737 | * @returns {string} The hostname to use for autorun | 767 | * @param {boolean} useIPv6 If use IPv6 |
| 768 | * @param {boolean} useIPv4 If use IPv4 | ||
| 769 | * @returns Promise<string> The hostname to use for autorun | ||
| 738 | */ | 770 | */ |
| 739 | function getAutorunHostname(useIPv6, useIPv4) { | 771 | async function getAutorunHostname(useIPv6, useIPv4) { |
| 740 | if (autorunHostname === 'auto') { | 772 | if (autorunHostname === 'auto') { |
| 773 | let localhostResolve = await canResolve('localhost', useIPv6, useIPv4); | ||
| 774 | |||
| 741 | if (useIPv6 && useIPv4) { | 775 | if (useIPv6 && useIPv4) { |
| 742 | if (avoidLocalhost) return '[::1]'; | 776 | if (avoidLocalhost || !localhostResolve) return '[::1]'; |
| 743 | return 'localhost'; | 777 | return 'localhost'; |
| 744 | } | 778 | } |
| 745 | 779 | ||
| @@ -752,6 +786,7 @@ function getAutorunHostname(useIPv6, useIPv4) { | |||
| 752 | } | 786 | } |
| 753 | } | 787 | } |
| 754 | 788 | ||
| 789 | |||
| 755 | return autorunHostname; | 790 | return autorunHostname; |
| 756 | } | 791 | } |
| 757 | 792 | ||
| @@ -759,11 +794,13 @@ function getAutorunHostname(useIPv6, useIPv4) { | |||
| 759 | * Tasks that need to be run after the server starts listening. | 794 | * Tasks that need to be run after the server starts listening. |
| 760 | * @param {boolean} v6Failed If the server failed to start on IPv6 | 795 | * @param {boolean} v6Failed If the server failed to start on IPv6 |
| 761 | * @param {boolean} v4Failed If the server failed to start on IPv4 | 796 | * @param {boolean} v4Failed If the server failed to start on IPv4 |
| 797 | * @param {boolean} useIPv6 If the server is using IPv6 | ||
| 798 | * @param {boolean} useIPv4 If the server is using IPv4 | ||
| 762 | */ | 799 | */ |
| 763 | const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) { | 800 | const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) { |
| 764 | const autorunUrl = new URL( | 801 | const autorunUrl = new URL( |
| 765 | (cliArguments.ssl ? 'https://' : 'http://') + | 802 | (cliArguments.ssl ? 'https://' : 'http://') + |
| 766 | (getAutorunHostname(useIPv6, useIPv4)) + | 803 | (await getAutorunHostname(useIPv6, useIPv4)) + |
| 767 | (':') + | 804 | (':') + |
| 768 | ((autorunPortOverride >= 0) ? autorunPortOverride : server_port), | 805 | ((autorunPortOverride >= 0) ? autorunPortOverride : server_port), |
| 769 | ); | 806 | ); |
| @@ -777,11 +814,15 @@ const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) { | |||
| 777 | let logListen = 'SillyTavern is listening on'; | 814 | let logListen = 'SillyTavern is listening on'; |
| 778 | 815 | ||
| 779 | if (useIPv6 && !v6Failed) { | 816 | if (useIPv6 && !v6Failed) { |
| 780 | logListen += color.green(' IPv6: ' + tavernUrlV6.host); | 817 | logListen += color.green( |
| 818 | ' IPv6: ' + tavernUrlV6.host | ||
| 819 | ); | ||
| 781 | } | 820 | } |
| 782 | 821 | ||
| 783 | if (useIPv4 && !v4Failed) { | 822 | if (useIPv4 && !v4Failed) { |
| 784 | logListen += color.green(' IPv4: ' + tavernUrl.host); | 823 | logListen += color.green( |
| 824 | ' IPv4: ' + tavernUrl.host | ||
| 825 | ); | ||
| 785 | } | 826 | } |
| 786 | 827 | ||
| 787 | const goToLog = 'Go to: ' + color.blue(autorunUrl) + ' to open SillyTavern'; | 828 | const goToLog = 'Go to: ' + color.blue(autorunUrl) + ' to open SillyTavern'; |
| @@ -793,16 +834,22 @@ const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) { | |||
| 793 | console.log('\n' + getSeparator(plainGoToLog.length) + '\n'); | 834 | console.log('\n' + getSeparator(plainGoToLog.length) + '\n'); |
| 794 | 835 | ||
| 795 | if (listen) { | 836 | if (listen) { |
| 796 | console.log('[::] or 0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n'); | 837 | console.log( |
| 838 | '[::] or 0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n' | ||
| 839 | ); | ||
| 797 | } | 840 | } |
| 798 | 841 | ||
| 799 | if (basicAuthMode) { | 842 | if (basicAuthMode) { |
| 800 | if (perUserBasicAuth && !enableAccounts) { | 843 | if (perUserBasicAuth && !enableAccounts) { |
| 801 | console.error(color.red('Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.')); | 844 | console.error(color.red( |
| 845 | 'Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.' | ||
| 846 | )); | ||
| 802 | } else if (!perUserBasicAuth) { | 847 | } else if (!perUserBasicAuth) { |
| 803 | const basicAuthUser = getConfigValue('basicAuthUser', {}); | 848 | const basicAuthUser = getConfigValue('basicAuthUser', {}); |
| 804 | if (!basicAuthUser?.username || !basicAuthUser?.password) { | 849 | if (!basicAuthUser?.username || !basicAuthUser?.password) { |
| 805 | console.warn(color.yellow('Basic Authentication is enabled, but username or password is not set or empty!')); | 850 | console.warn(color.yellow( |
| 851 | 'Basic Authentication is enabled, but username or password is not set or empty!' | ||
| 852 | )); | ||
| 806 | } | 853 | } |
| 807 | } | 854 | } |
| 808 | } | 855 | } |
| @@ -855,6 +902,8 @@ function logSecurityAlert(message) { | |||
| 855 | * Handles the case where the server failed to start on one or both protocols. | 902 | * Handles the case where the server failed to start on one or both protocols. |
| 856 | * @param {boolean} v6Failed If the server failed to start on IPv6 | 903 | * @param {boolean} v6Failed If the server failed to start on IPv6 |
| 857 | * @param {boolean} v4Failed If the server failed to start on IPv4 | 904 | * @param {boolean} v4Failed If the server failed to start on IPv4 |
| 905 | * @param {boolean} useIPv6 If use IPv6 | ||
| 906 | * @param {boolean} useIPv4 If use IPv4 | ||
| 858 | */ | 907 | */ |
| 859 | function handleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4) { | 908 | function handleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4) { |
| 860 | if (v6Failed && !useIPv4) { | 909 | if (v6Failed && !useIPv4) { |
| @@ -876,6 +925,7 @@ function handleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4) { | |||
| 876 | /** | 925 | /** |
| 877 | * Creates an HTTPS server. | 926 | * Creates an HTTPS server. |
| 878 | * @param {URL} url The URL to listen on | 927 | * @param {URL} url The URL to listen on |
| 928 | * @param {number} ipVersion the ip version to use | ||
| 879 | * @returns {Promise<void>} A promise that resolves when the server is listening | 929 | * @returns {Promise<void>} A promise that resolves when the server is listening |
| 880 | * @throws {Error} If the server fails to start | 930 | * @throws {Error} If the server fails to start |
| 881 | */ | 931 | */ |
| @@ -903,6 +953,7 @@ function createHttpsServer(url, ipVersion) { | |||
| 903 | /** | 953 | /** |
| 904 | * Creates an HTTP server. | 954 | * Creates an HTTP server. |
| 905 | * @param {URL} url The URL to listen on | 955 | * @param {URL} url The URL to listen on |
| 956 | * @param {number} ipVersion the ip version to use | ||
| 906 | * @returns {Promise<void>} A promise that resolves when the server is listening | 957 | * @returns {Promise<void>} A promise that resolves when the server is listening |
| 907 | * @throws {Error} If the server fails to start | 958 | * @throws {Error} If the server fails to start |
| 908 | */ | 959 | */ |
| @@ -923,6 +974,12 @@ function createHttpServer(url, ipVersion) { | |||
| 923 | }); | 974 | }); |
| 924 | } | 975 | } |
| 925 | 976 | ||
| 977 | |||
| 978 | /** | ||
| 979 | * Starts the server using http or https depending on config | ||
| 980 | * @param {boolean} useIPv6 If use IPv6 | ||
| 981 | * @param {boolean} useIPv4 If use IPv4 | ||
| 982 | */ | ||
| 926 | async function startHTTPorHTTPS(useIPv6, useIPv4) { | 983 | async function startHTTPorHTTPS(useIPv6, useIPv4) { |
| 927 | let v6Failed = false; | 984 | let v6Failed = false; |
| 928 | let v4Failed = false; | 985 | let v4Failed = false; |
| @@ -957,12 +1014,19 @@ async function startHTTPorHTTPS(useIPv6, useIPv4) { | |||
| 957 | async function startServer() { | 1014 | async function startServer() { |
| 958 | let useIPv6 = (enableIPv6 === true); | 1015 | let useIPv6 = (enableIPv6 === true); |
| 959 | let useIPv4 = (enableIPv4 === true); | 1016 | let useIPv4 = (enableIPv4 === true); |
| 960 | let hasIPv6, hasIPv4, hasIPv6Local, hasIPv4Local, hasIPv6Any, hasIPv4Any; | 1017 | |
| 1018 | let hasIPv6 = false, | ||
| 1019 | hasIPv4 = false, | ||
| 1020 | hasIPv6Local = false, | ||
| 1021 | hasIPv4Local = false, | ||
| 1022 | hasIPv6Any = false, | ||
| 1023 | hasIPv4Any = false; | ||
| 961 | 1024 | ||
| 962 | 1025 | ||
| 963 | if (enableIPv6 === 'auto' || enableIPv4 === 'auto') { | 1026 | if (enableIPv6 === 'auto' || enableIPv4 === 'auto') { |
| 964 | [hasIPv6Any, hasIPv4Any, hasIPv6Local, hasIPv4Local] = await getHasIP(); | 1027 | [hasIPv6Any, hasIPv4Any, hasIPv6Local, hasIPv4Local] = await getHasIP(); |
| 965 | 1028 | ||
| 1029 | |||
| 966 | hasIPv6 = listen ? hasIPv6Any : hasIPv6Local; | 1030 | hasIPv6 = listen ? hasIPv6Any : hasIPv6Local; |
| 967 | if (enableIPv6 === 'auto') { | 1031 | if (enableIPv6 === 'auto') { |
| 968 | useIPv6 = hasIPv6; | 1032 | useIPv6 = hasIPv6; |
| @@ -987,10 +1051,6 @@ async function startServer() { | |||
| 987 | console.log('IPv4 support detected (but disabled)'); | 1051 | console.log('IPv4 support detected (but disabled)'); |
| 988 | } | 1052 | } |
| 989 | } | 1053 | } |
| 990 | } else { | ||
| 991 | console.log('Neither protocol: ipv6, nor ipv4 are set to auto, skipping detection'); | ||
| 992 | } | ||
| 993 | |||
| 994 | 1054 | ||
| 995 | 1055 | ||
| 996 | if (enableIPv6 === 'auto' && enableIPv4 === 'auto') { | 1056 | if (enableIPv6 === 'auto' && enableIPv4 === 'auto') { |
| @@ -1000,14 +1060,23 @@ async function startServer() { | |||
| 1000 | } | 1060 | } |
| 1001 | } | 1061 | } |
| 1002 | 1062 | ||
| 1063 | } else { | ||
| 1064 | console.log('Neither protocol: ipv6, nor ipv4 are set to auto, skipping detection'); | ||
| 1065 | } | ||
| 1066 | |||
| 1067 | |||
| 1068 | |||
| 1069 | |||
| 1003 | if (!useIPv6 && !useIPv4) { | 1070 | if (!useIPv6 && !useIPv4) { |
| 1004 | console.error('Both IPv6 and IPv4 are disabled,\nP.S. you should never see this error, at least at one point it was checked for before this, with the rest of the config options'); | 1071 | console.error('Both IPv6 and IPv4 are disabled,\nP.S. you should never see this error, at least at one point it was checked for before this, with the rest of the config options'); |
| 1005 | process.exit(1); | 1072 | process.exit(1); |
| 1006 | } | 1073 | } |
| 1007 | 1074 | ||
| 1075 | |||
| 1008 | const [v6Failed, v4Failed] = await startHTTPorHTTPS(useIPv6, useIPv4); | 1076 | const [v6Failed, v4Failed] = await startHTTPorHTTPS(useIPv6, useIPv4); |
| 1009 | 1077 | ||
| 1010 | handleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4); | 1078 | handleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4); |
| 1079 | |||
| 1011 | postSetupTasks(v6Failed, v4Failed, useIPv6, useIPv4); | 1080 | postSetupTasks(v6Failed, v4Failed, useIPv6, useIPv4); |
| 1012 | } | 1081 | } |
| 1013 | 1082 | ||
| @@ -5,6 +5,7 @@ import process from 'node:process'; | |||
| 5 | import { Readable } from 'node:stream'; | 5 | import { Readable } from 'node:stream'; |
| 6 | import { createRequire } from 'node:module'; | 6 | import { createRequire } from 'node:module'; |
| 7 | import { Buffer } from 'node:buffer'; | 7 | import { Buffer } from 'node:buffer'; |
| 8 | import { promises as dnsPromise } from 'node:dns'; | ||
| 8 | 9 | ||
| 9 | import yaml from 'yaml'; | 10 | import yaml from 'yaml'; |
| 10 | import { sync as commandExistsSync } from 'command-exists'; | 11 | import { sync as commandExistsSync } from 'command-exists'; |
| @@ -692,6 +693,11 @@ export function isValidUrl(url) { | |||
| 692 | } | 693 | } |
| 693 | } | 694 | } |
| 694 | 695 | ||
| 696 | /** | ||
| 697 | * removes starting `[` or ending `]` from hostname. | ||
| 698 | * @param {string} hostname hostname to use | ||
| 699 | * @returns {string} hostname plus the modifications | ||
| 700 | */ | ||
| 695 | export function urlHostnameToIPv6(hostname) { | 701 | export function urlHostnameToIPv6(hostname) { |
| 696 | if (hostname.startsWith('[')) { | 702 | if (hostname.startsWith('[')) { |
| 697 | hostname = hostname.slice(1); | 703 | hostname = hostname.slice(1); |
| @@ -702,6 +708,49 @@ export function urlHostnameToIPv6(hostname) { | |||
| 702 | return hostname; | 708 | return hostname; |
| 703 | } | 709 | } |
| 704 | 710 | ||
| 711 | /** | ||
| 712 | * Test if can resolve a dns name. | ||
| 713 | * @param {string} name Domain name to use | ||
| 714 | * @param {boolean} useIPv6 If use IPv6 | ||
| 715 | * @param {boolean} useIPv4 If use IPv4 | ||
| 716 | * @returns Promise<boolean> If the URL is valid | ||
| 717 | */ | ||
| 718 | export async function canResolve(name, useIPv6 = true, useIPv4 = true) { | ||
| 719 | try { | ||
| 720 | let v6Resolved = false; | ||
| 721 | let v4Resolved = false; | ||
| 722 | |||
| 723 | if (useIPv6) { | ||
| 724 | try { | ||
| 725 | await dnsPromise.resolve6(name); | ||
| 726 | v6Resolved = true; | ||
| 727 | } catch (error) { | ||
| 728 | v6Resolved = false; | ||
| 729 | } | ||
| 730 | } | ||
| 731 | |||
| 732 | if (useIPv4) { | ||
| 733 | try { | ||
| 734 | await dnsPromise.resolve(name); | ||
| 735 | v4Resolved = true; | ||
| 736 | } catch (error) { | ||
| 737 | v4Resolved = false; | ||
| 738 | } | ||
| 739 | } | ||
| 740 | |||
| 741 | return v6Resolved || v4Resolved; | ||
| 742 | |||
| 743 | } catch (error) { | ||
| 744 | return false; | ||
| 745 | } | ||
| 746 | } | ||
| 747 | |||
| 748 | |||
| 749 | /** | ||
| 750 | * converts string to boolean accepts 'true' or 'false' else it returns the string put in | ||
| 751 | * @param {string|null} str Input string or null | ||
| 752 | * @returns {boolean|string|null} boolean else original input string or null if input is | ||
| 753 | */ | ||
| 705 | export function stringToBool(str) { | 754 | export function stringToBool(str) { |
| 706 | if (str === 'true') return true; | 755 | if (str === 'true') return true; |
| 707 | if (str === 'false') return false; | 756 | if (str === 'false') return false; |