Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions packages/lib/shim-init-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import BaseItem from './models/BaseItem';
import { Size } from '@joplin/utils/types';
import { cpus } from 'os';
import { pathToFileURL } from 'url';
import * as tls from 'tls';
import type PdfJs from './utils/types/pdfJs';
const { _ } = require('./locale');
const http = require('http');
Expand Down Expand Up @@ -529,7 +530,7 @@ function shimInit(options: ShimInitOptions = null) {
throw new Error(`Not a valid URL: ${url}`);
}
const resolvedProxyUrl = resolveProxyUrl(proxySettings.proxyUrl);
options.agent = (resolvedProxyUrl && proxySettings.proxyEnabled) ? shim.proxyAgent(url, resolvedProxyUrl) : null;
options.agent = (resolvedProxyUrl && proxySettings.proxyEnabled) ? shim.proxyAgent(url, resolvedProxyUrl) : shim.httpAgent(url);
return shim.fetchWithRetry(() => {
Comment on lines 532 to 534
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n packages/lib/shim-init-node.ts | head -750 | tail -250

Repository: laurent22/joplin

Length of output: 10652


🏁 Script executed:

# Check the shim functions and agent creation
rg -A 10 "httpAgent|proxyAgent" packages/lib/shim-init-node.ts

Repository: laurent22/joplin

Length of output: 3126


🏁 Script executed:

# Check package.json for node-fetch version
cat packages/lib/package.json | grep -A 2 -B 2 "node-fetch"

Repository: laurent22/joplin

Length of output: 195


🌐 Web query:

node-fetch agent option callback function support documentation

💡 Result:

node-fetch does document support for an agent “callback” (i.e., a function) in fetch(url, options):

  • In the official node-fetch README, the agent option is documented as either an http(s).Agent instance or a function that returns an Agent. The README further explains that this function is called with the current URL (useful for redirects that switch between http: and https:). [1]
  • The TypeScript typings for node-fetch (DefinitelyTyped, as rendered by jsDocs) define it as:
    agent?: RequestOptions['agent'] | ((parsedUrl: URL) => RequestOptions['agent']); [2]

Docs: see the node-fetch README → API → Options → Custom Agent. [1]

Sources
[1] node-fetch README (GitHub) (github.com)
[2] @types/node-fetch RequestInit typings (jsDocs) (jsdocs.io)

Citations:


Use an agent callback to support cross-protocol redirects.

node-fetch supports passing a function for the agent option, which is called with each redirect URL to allow protocol-specific agent selection. The current fixed agent on Line 532 means an http: URL that redirects to https: keeps the agent selected for the original scheme, causing the request to potentially fail.

Suggested change
-		options.agent = (resolvedProxyUrl && proxySettings.proxyEnabled) ? shim.proxyAgent(url, resolvedProxyUrl) : shim.httpAgent(url);
+		options.agent = currentUrl => {
+			const currentUrlString = currentUrl.href ?? currentUrl.toString();
+			return (resolvedProxyUrl && proxySettings.proxyEnabled)
+				? shim.proxyAgent(currentUrlString, resolvedProxyUrl)
+				: shim.httpAgent(currentUrlString);
+		};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const resolvedProxyUrl = resolveProxyUrl(proxySettings.proxyUrl);
options.agent = (resolvedProxyUrl && proxySettings.proxyEnabled) ? shim.proxyAgent(url, resolvedProxyUrl) : null;
options.agent = (resolvedProxyUrl && proxySettings.proxyEnabled) ? shim.proxyAgent(url, resolvedProxyUrl) : shim.httpAgent(url);
return shim.fetchWithRetry(() => {
const resolvedProxyUrl = resolveProxyUrl(proxySettings.proxyUrl);
options.agent = currentUrl => {
const currentUrlString = currentUrl.href ?? currentUrl.toString();
return (resolvedProxyUrl && proxySettings.proxyEnabled)
? shim.proxyAgent(currentUrlString, resolvedProxyUrl)
: shim.httpAgent(currentUrlString);
};
return shim.fetchWithRetry(() => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/lib/shim-init-node.ts` around lines 531 - 533, The current
assignment of options.agent uses a fixed agent and breaks cross-protocol
redirects; change options.agent to a function callback that receives the request
URL and returns the correct agent per-redirect: if proxySettings.proxyEnabled
and resolvedProxyUrl are set, call shim.proxyAgent(url, resolvedProxyUrl) for
that redirect URL, otherwise call shim.httpAgent(url); update the code around
resolvedProxyUrl, options.agent, and the shim.fetchWithRetry call so
fetchWithRetry receives this agent callback instead of a single static agent.

return nodeFetch(url, options);
}, options);
Expand Down Expand Up @@ -584,7 +585,7 @@ function shimInit(options: ShimInitOptions = null) {
};

const resolvedProxyUrl = resolveProxyUrl(proxySettings.proxyUrl);
requestOptions.agent = (resolvedProxyUrl && proxySettings.proxyEnabled) ? shim.proxyAgent(url.href, resolvedProxyUrl) : null;
requestOptions.agent = (resolvedProxyUrl && proxySettings.proxyEnabled) ? shim.proxyAgent(url.href, resolvedProxyUrl) : shim.httpAgent(url.href);
Comment on lines 587 to +588
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n packages/lib/shim-init-node.ts | sed -n '580,595p'

Repository: laurent22/joplin

Length of output: 907


🏁 Script executed:

rg -A 10 "const resolvedProxyUrl = resolveProxyUrl" packages/lib/shim-init-node.ts

Repository: laurent22/joplin

Length of output: 1324


🏁 Script executed:

rg "fetchBlob" packages/lib/shim-init-node.ts -B 5 -A 15

Repository: laurent22/joplin

Length of output: 1086


🏁 Script executed:

rg "requestOptions" packages/lib/shim-init-node.ts -A 5 -B 2

Repository: laurent22/joplin

Length of output: 1675


🏁 Script executed:

rg "http\.request\|https\.request" packages/lib/shim-init-node.ts -A 3 -B 2

Repository: laurent22/joplin

Length of output: 42


🌐 Web query:

follow-redirects library agents vs agent parameter options API

💡 Result:

In follow-redirects you can use either the standard Node.js agent option or the library-specific agents option:

  • agent (standard http/https option): a single agent instance used for the request (works fine if you know you’ll stay on one protocol). follow-redirects supports the normal http/https request options in addition to its own. [1]

  • agents (follow-redirects-specific): lets you specify the agent per protocol because HTTP and HTTPS use different agent classes, and redirects can switch protocols (e.g., http → https). Shape is:

    agents: { http?: http.Agent, https?: https.Agent }

    [1][2]

Example:

const { http, https } = require('follow-redirects');

https.request({
  url: 'http://example.com',
  agents: {
    http: new (require('http')).Agent({ keepAlive: true }),
    https: new (require('https')).Agent({ keepAlive: true }),
  },
});

Sources: [1] follow-redirects README options docs, [2] Type definitions for agents shape.


🏁 Script executed:

rg "shim\.httpAgent\s*=|shim\.proxyAgent\s*=" packages/lib/shim-init-node.ts -A 5 -B 1

Repository: laurent22/joplin

Length of output: 421


🏁 Script executed:

rg "httpAgent|proxyAgent" packages/lib/shim-init-node.ts | head -20

Repository: laurent22/joplin

Length of output: 805


🏁 Script executed:

rg "shim\.httpAgent\s*=" packages/lib/shim-init-node.ts -A 15

Repository: laurent22/joplin

Length of output: 443


🏁 Script executed:

rg "shim\.proxyAgent\s*=" packages/lib/shim-init-node.ts -A 25

Repository: laurent22/joplin

Length of output: 1003


Use agents for follow-redirects downloads.

On line 587, a single agent is assigned to requestOptions.agent based on the initial URL scheme. However, follow-redirects requires an agents map to handle cross-protocol redirects correctly—when an HTTP download redirects to HTTPS (or vice versa), the current code reuses the wrong agent type. The library expects:

requestOptions.agents = { http: httpAgent, https: httpsAgent }

This ensures each protocol gets its appropriate agent instance during the redirect chain.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/lib/shim-init-node.ts` around lines 586 - 587, The code assigns a
single agent to requestOptions.agent using resolvedProxyUrl/resolveProxyUrl and
shim.proxyAgent/shim.httpAgent which breaks cross-protocol redirects; replace
this with an agents map on requestOptions (requestOptions.agents = { http:
<httpAgent>, https: <httpsAgent> }) and populate each entry using the same proxy
logic (use shim.proxyAgent(url.href, resolvedProxyUrl) and
shim.httpAgent(url.href) to build the appropriate http and https agents) so
follow-redirects can pick the correct agent per protocol instead of reusing
requestOptions.agent.


const doFetchOperation = async () => {
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -701,12 +702,24 @@ function shimInit(options: ShimInitOptions = null) {

shim.httpAgent_ = null;

// X25519MLKEM768 is a post-quantum cryptography key exchange, details:
// https://developers.cloudflare.com/ssl/post-quantum-cryptography/
// Not supported on by all SSL stacks and versions, detect support at runtime.
let tlsEcdhCurve: string;
try {
tls.createSecureContext({ ecdhCurve: 'X25519MLKEM768:X25519:P-256:P-384' });
tlsEcdhCurve = 'X25519MLKEM768:X25519:P-256:P-384';
} catch {
tlsEcdhCurve = 'auto';
}

shim.httpAgent = url => {
if (!shim.httpAgent_) {
const AgentSettings = {
keepAlive: true,
maxSockets: 1,
keepAliveMsecs: 5000,
ecdhCurve: tlsEcdhCurve,
};
shim.httpAgent_ = {
http: new http.Agent(AgentSettings),
Expand All @@ -723,6 +736,7 @@ function shimInit(options: ShimInitOptions = null) {
keepAliveMsecs: 5000,
proxy: proxyUrl,
timeout: proxySettings.proxyTimeout * 1000,
ecdhCurve: tlsEcdhCurve,
};

// Based on https://github.com/delvedor/hpagent#usage
Expand Down
1 change: 1 addition & 0 deletions packages/tools/cspell/dictionary2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ mkdir
mkdirp
mknote
mktodo
MLKEM
MMYY
mmproj
mnop
Expand Down
Loading