diff --git a/.eslintignore b/.eslintignore index 7e298e2c8..af0a6c8bc 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1176,6 +1176,9 @@ packages/lib/models/utils/paginationToSql.js.map packages/lib/models/utils/types.d.ts packages/lib/models/utils/types.js packages/lib/models/utils/types.js.map +packages/lib/net-utils.d.ts +packages/lib/net-utils.js +packages/lib/net-utils.js.map packages/lib/ntp.d.ts packages/lib/ntp.js packages/lib/ntp.js.map diff --git a/.gitignore b/.gitignore index 25f7354ce..5e4f054e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1159,6 +1159,9 @@ packages/lib/models/utils/paginationToSql.js.map packages/lib/models/utils/types.d.ts packages/lib/models/utils/types.js packages/lib/models/utils/types.js.map +packages/lib/net-utils.d.ts +packages/lib/net-utils.js +packages/lib/net-utils.js.map packages/lib/ntp.d.ts packages/lib/ntp.js packages/lib/ntp.js.map diff --git a/packages/app-cli/app/ResourceServer.js b/packages/app-cli/app/ResourceServer.js index 90eb7ad11..ac6e2ed19 100644 --- a/packages/app-cli/app/ResourceServer.js +++ b/packages/app-cli/app/ResourceServer.js @@ -1,5 +1,5 @@ const Logger = require('@joplin/lib/Logger').default; -const { netUtils } = require('@joplin/lib/net-utils.js'); +const { findAvailablePort } = require('@joplin/lib/net-utils'); const http = require('http'); const urlParser = require('url'); @@ -36,7 +36,7 @@ class ResourceServer { } async start() { - this.port_ = await netUtils.findAvailablePort([9167, 9267, 8167, 8267]); + this.port_ = await findAvailablePort([9167, 9267, 8167, 8267]); if (!this.port_) { this.logger().error('Could not find available port to start resource server. Please report the error at https://github.com/laurent22/joplin'); return; diff --git a/packages/lib/JoplinServerApi.ts b/packages/lib/JoplinServerApi.ts index 18a5f7974..05cc50568 100644 --- a/packages/lib/JoplinServerApi.ts +++ b/packages/lib/JoplinServerApi.ts @@ -5,6 +5,7 @@ import JoplinError from './JoplinError'; import { Env } from './models/Setting'; import Logger from './Logger'; import personalizedUserContentBaseUrl from './services/joplinServer/personalizedUserContentBaseUrl'; +import { getHttpStatusMessage } from './net-utils'; const { stringify } = require('query-string'); const logger = Logger.create('JoplinServerApi'); @@ -245,7 +246,7 @@ export default class JoplinServerApi { //
nginx/1.18.0 (Ubuntu)
// // - throw newError(`Unknown error: ${shortResponseText()}`, response.status); + throw newError(`Error ${response.status} ${getHttpStatusMessage(response.status)}: ${shortResponseText()}`, response.status); } if (options.responseFormat === 'text') return responseText; diff --git a/packages/lib/net-utils.js b/packages/lib/net-utils.js deleted file mode 100644 index d36def19a..000000000 --- a/packages/lib/net-utils.js +++ /dev/null @@ -1,40 +0,0 @@ -const shim = require('./shim').default; - -const netUtils = {}; - -netUtils.ip = async () => { - const response = await shim.fetch('https://api.ipify.org/?format=json'); - if (!response.ok) { - throw new Error(`Could not retrieve IP: ${await response.text()}`); - } - - const ip = await response.json(); - return ip.ip; -}; - -netUtils.findAvailablePort = async (possiblePorts, extraRandomPortsToTry = 20) => { - const tcpPortUsed = require('tcp-port-used'); - - for (let i = 0; i < extraRandomPortsToTry; i++) { - possiblePorts.push(Math.floor(8000 + Math.random() * 2000)); - } - - let port = null; - for (let i = 0; i < possiblePorts.length; i++) { - const inUse = await tcpPortUsed.check(possiblePorts[i]); - if (!inUse) { - port = possiblePorts[i]; - break; - } - } - return port; -}; - -netUtils.mimeTypeFromHeaders = headers => { - if (!headers || !headers['content-type']) return null; - - const splitted = headers['content-type'].split(';'); - return splitted[0].trim().toLowerCase(); -}; - -module.exports = { netUtils }; diff --git a/packages/lib/net-utils.ts b/packages/lib/net-utils.ts new file mode 100644 index 000000000..4a32e1f47 --- /dev/null +++ b/packages/lib/net-utils.ts @@ -0,0 +1,112 @@ +import shim from './shim'; + +export async function ip() { + const response = await shim.fetch('https://api.ipify.org/?format=json'); + if (!response.ok) { + throw new Error(`Could not retrieve IP: ${await response.text()}`); + } + + const ip = await response.json(); + return ip.ip; +} + +export async function findAvailablePort(possiblePorts: number[], extraRandomPortsToTry = 20) { + const tcpPortUsed = require('tcp-port-used'); + + for (let i = 0; i < extraRandomPortsToTry; i++) { + possiblePorts.push(Math.floor(8000 + Math.random() * 2000)); + } + + let port = null; + for (let i = 0; i < possiblePorts.length; i++) { + const inUse = await tcpPortUsed.check(possiblePorts[i]); + if (!inUse) { + port = possiblePorts[i]; + break; + } + } + return port; +} + +export async function mimeTypeFromHeaders(headers: Record) { + if (!headers || !headers['content-type']) return null; + + const splitted = headers['content-type'].split(';'); + return splitted[0].trim().toLowerCase(); +} + + +const httpStatusCodes_: Record = { + 100: 'Continue', + 101: 'Switching Protocols', + 102: 'Processing', + 103: 'Early Hints', + 200: 'OK', + 201: 'Created', + 202: 'Accepted', + 203: 'Non-Authoritative Information', + 204: 'No Content', + 205: 'Reset Content', + 206: 'Partial Content', + 207: 'Multi-Status', + 208: 'Already Reported', + 226: 'IM Used', + 300: 'Multiple Choices', + 301: 'Moved Permanently', + 302: 'Found', + 303: 'See Other', + 304: 'Not Modified', + 305: 'Use Proxy', + 307: 'Temporary Redirect', + 308: 'Permanent Redirect', + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 406: 'Not Acceptable', + 407: 'Proxy Authentication Required', + 408: 'Request Timeout', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Failed', + 413: 'Payload Too Large', + 414: 'URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Range Not Satisfiable', + 417: 'Expectation Failed', + 418: 'I\'m a Teapot', + 421: 'Misdirected Request', + 422: 'Unprocessable Entity', + 423: 'Locked', + 424: 'Failed Dependency', + 425: 'Too Early', + 426: 'Upgrade Required', + 428: 'Precondition Required', + 429: 'Too Many Requests', + 431: 'Request Header Fields Too Large', + 451: 'Unavailable For Legal Reasons', + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported', + 506: 'Variant Also Negotiates', + 507: 'Insufficient Storage', + 508: 'Loop Detected', + 509: 'Bandwidth Limit Exceeded', + 510: 'Not Extended', + 511: 'Network Authentication Required', +}; + +export function getHttpStatusMessage(statusCode: number): string { + const msg = httpStatusCodes_[statusCode]; + + // We don't throw an exception since a server can send any arbitrary error code + if (!msg) return 'Unknown status code'; + + return msg; +} diff --git a/packages/lib/onedrive-api-node-utils.js b/packages/lib/onedrive-api-node-utils.js index c85f263fb..588c18218 100644 --- a/packages/lib/onedrive-api-node-utils.js +++ b/packages/lib/onedrive-api-node-utils.js @@ -1,5 +1,5 @@ const { _ } = require('./locale'); -const { netUtils } = require('./net-utils.js'); +const { findAvailablePort } = require('./net-utils'); const shim = require('./shim').default; const http = require('http'); @@ -42,7 +42,7 @@ class OneDriveApiNodeUtils { this.api().setAuth(null); - const port = await netUtils.findAvailablePort(this.possibleOAuthDancePorts(), 0); + const port = await findAvailablePort(this.possibleOAuthDancePorts(), 0); if (!port) throw new Error(_('All potential ports are in use - please report the issue at %s', 'https://github.com/laurent22/joplin')); const authCodeUrl = this.api().authCodeUrl(`http://localhost:${port}`); diff --git a/packages/lib/services/rest/routes/notes.ts b/packages/lib/services/rest/routes/notes.ts index 48d929ebe..506e6923e 100644 --- a/packages/lib/services/rest/routes/notes.ts +++ b/packages/lib/services/rest/routes/notes.ts @@ -23,7 +23,7 @@ const md5 = require('md5'); import HtmlToMd from '../../../HtmlToMd'; const urlUtils = require('../../../urlUtils.js'); const ArrayUtils = require('../../../ArrayUtils.js'); -const { netUtils } = require('../../../net-utils'); +const { mimeTypeFromHeaders } = require('../../../net-utils'); const { fileExtension, safeFileExtension, safeFilename, filename } = require('../../../path-utils'); const uri2path = require('file-uri-to-path'); const { MarkupToHtml } = require('@joplin/renderer'); @@ -144,7 +144,7 @@ async function buildNoteStyleSheet(stylesheets: any[]) { } async function tryToGuessImageExtFromMimeType(response: any, imagePath: string) { - const mimeType = netUtils.mimeTypeFromHeaders(response.headers); + const mimeType = mimeTypeFromHeaders(response.headers); if (!mimeType) return imagePath; const newExt = mimeUtils.toFileExtension(mimeType); diff --git a/packages/tools/fetchPatreonPosts.js b/packages/tools/fetchPatreonPosts.js index 3df05cbcc..dc2fba5b5 100644 --- a/packages/tools/fetchPatreonPosts.js +++ b/packages/tools/fetchPatreonPosts.js @@ -8,7 +8,7 @@ const HtmlToMd = require('@joplin/lib/HtmlToMd').default; const { dirname, filename, basename } = require('@joplin/lib/path-utils'); const markdownUtils = require('@joplin/lib/markdownUtils').default; const mimeUtils = require('@joplin/lib/mime-utils.js').mime; -const { netUtils } = require('@joplin/lib/net-utils'); +const { mimeTypeFromHeaders } = require('@joplin/lib/net-utils'); const shim = require('@joplin/lib/shim').default; const moment = require('moment'); const { pregQuote } = require('@joplin/lib/string-utils'); @@ -62,7 +62,7 @@ async function createPostFile(post, filePath) { const imagePath = `${tempDir}/${imageFilename}`; const response = await shim.fetchBlob(imageUrl, { path: imagePath, maxRetry: 1 }); - const mimeType = netUtils.mimeTypeFromHeaders(response.headers); + const mimeType = mimeTypeFromHeaders(response.headers); let ext = 'jpg'; if (mimeType) { const newExt = mimeUtils.toFileExtension(mimeType);