Desktop: Resolves #164: Add support for proxy (#6537)

pull/6658/head
Jason Williams 2022-07-10 14:54:31 +01:00 committed by GitHub
parent 2c4cf9fbdb
commit 8bb5b4a557
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 108 additions and 6 deletions

View File

@ -325,6 +325,7 @@
"homenote", "homenote",
"hotfolder", "hotfolder",
"Howver", "Howver",
"hpagent",
"Hrvatska", "Hrvatska",
"htmlentities", "htmlentities",
"htmlfile", "htmlfile",
@ -950,4 +951,4 @@
"မြန်မာ", "မြန်မာ",
"កម្ពុជា" "កម្ពុជា"
] ]
} }

View File

@ -1,6 +1,7 @@
import Setting, { Env } from './models/Setting'; import Setting, { Env } from './models/Setting';
import Logger, { TargetType, LoggerWrapper } from './Logger'; import Logger, { TargetType, LoggerWrapper } from './Logger';
import shim from './shim'; import shim from './shim';
const { setupProxySettings } = require('./shim-init-node');
import BaseService from './services/BaseService'; import BaseService from './services/BaseService';
import reducer, { setStore } from './reducer'; import reducer, { setStore } from './reducer';
import KeychainServiceDriver from './services/keychain/KeychainServiceDriver.node'; import KeychainServiceDriver from './services/keychain/KeychainServiceDriver.node';
@ -456,6 +457,14 @@ export default class BaseApplication {
syswidecas.addCAs(f); syswidecas.addCAs(f);
} }
}, },
'net.proxyEnabled': async () => {
setupProxySettings({
maxConcurrentConnections: Setting.value('sync.maxConcurrentConnections'),
proxyTimeout: Setting.value('net.proxyTimeout'),
proxyEnabled: Setting.value('net.proxyEnabled'),
proxyUrl: Setting.value('net.proxyUrl'),
});
},
// Note: this used to run when "encryption.enabled" was changed, but // Note: this used to run when "encryption.enabled" was changed, but
// now we run it anytime any property of the sync target info is // now we run it anytime any property of the sync target info is
@ -491,6 +500,9 @@ export default class BaseApplication {
sideEffects['locale'] = sideEffects['dateFormat']; sideEffects['locale'] = sideEffects['dateFormat'];
sideEffects['encryption.passwordCache'] = sideEffects['syncInfoCache']; sideEffects['encryption.passwordCache'] = sideEffects['syncInfoCache'];
sideEffects['encryption.masterPassword'] = sideEffects['syncInfoCache']; sideEffects['encryption.masterPassword'] = sideEffects['syncInfoCache'];
sideEffects['sync.maxConcurrentConnections'] = sideEffects['net.proxyEnabled'];
sideEffects['sync.proxyTimeout'] = sideEffects['net.proxyEnabled'];
sideEffects['sync.proxyUrl'] = sideEffects['net.proxyEnabled'];
if (action) { if (action) {
const effect = sideEffects[action.key]; const effect = sideEffects[action.key];

View File

@ -1384,7 +1384,37 @@ class Setting extends BaseModel {
label: () => _('Ignore TLS certificate errors'), label: () => _('Ignore TLS certificate errors'),
storage: SettingStorage.File, storage: SettingStorage.File,
}, },
'net.proxyEnabled': {
value: false,
type: SettingItemType.Bool,
advanced: true,
section: 'sync',
isGlobal: true,
public: true,
label: () => _('Proxy enabled (beta)'),
storage: SettingStorage.File,
},
'net.proxyUrl': {
value: '',
type: SettingItemType.String,
advanced: true,
section: 'sync',
isGlobal: true,
public: true,
label: () => _('Proxy URL (beta)'),
description: () => _('e.g "http://my.proxy.com:80". You can also set via environment variables'),
storage: SettingStorage.File,
},
'net.proxyTimeout': {
value: 1,
type: SettingItemType.Int,
advanced: true,
section: 'sync',
isGlobal: true,
public: true,
label: () => _('proxy timeout (seconds) (beta)'),
storage: SettingStorage.File,
},
'sync.wipeOutFailSafe': { 'sync.wipeOutFailSafe': {
value: true, value: true,
type: SettingItemType.Bool, type: SettingItemType.Bool,

View File

@ -53,6 +53,7 @@
"follow-redirects": "^1.2.4", "follow-redirects": "^1.2.4",
"form-data": "^2.1.4", "form-data": "^2.1.4",
"fs-extra": "^5.0.0", "fs-extra": "^5.0.0",
"hpagent": "^1.0.0",
"html-entities": "^1.2.1", "html-entities": "^1.2.1",
"html-minifier": "^3.5.15", "html-minifier": "^3.5.15",
"image-data-uri": "^2.0.0", "image-data-uri": "^2.0.0",

View File

@ -13,12 +13,15 @@ const urlValidator = require('valid-url');
const { _ } = require('./locale'); const { _ } = require('./locale');
const http = require('http'); const http = require('http');
const https = require('https'); const https = require('https');
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
const toRelative = require('relative'); const toRelative = require('relative');
const timers = require('timers'); const timers = require('timers');
const zlib = require('zlib'); const zlib = require('zlib');
const dgram = require('dgram'); const dgram = require('dgram');
const { basename, fileExtension, safeFileExtension } = require('./path-utils'); const { basename, fileExtension, safeFileExtension } = require('./path-utils');
const proxySettings = {};
function fileExists(filePath) { function fileExists(filePath) {
try { try {
return fs.statSync(filePath).isFile(); return fs.statSync(filePath).isFile();
@ -27,6 +30,20 @@ function fileExists(filePath) {
} }
} }
function isUrlHttps(url) {
return url.startsWith('https');
}
function resolveProxyUrl(proxyUrl) {
return (
proxyUrl ||
process.env['http_proxy'] ||
process.env['https_proxy'] ||
process.env['HTTP_PROXY'] ||
process.env['HTTPS_PROXY']
);
}
// https://github.com/sindresorhus/callsites/blob/main/index.js // https://github.com/sindresorhus/callsites/blob/main/index.js
function callsites() { function callsites() {
const _prepareStackTrace = Error.prepareStackTrace; const _prepareStackTrace = Error.prepareStackTrace;
@ -64,6 +81,13 @@ const gunzipFile = function(source, destination) {
}); });
}; };
function setupProxySettings(options) {
proxySettings.maxConcurrentConnections = options.maxConcurrentConnections;
proxySettings.proxyTimeout = options.proxyTimeout;
proxySettings.proxyEnabled = options.proxyEnabled;
proxySettings.proxyUrl = options.proxyUrl;
}
function shimInit(options = null) { function shimInit(options = null) {
options = { options = {
sharp: null, sharp: null,
@ -79,6 +103,7 @@ function shimInit(options = null) {
const keytar = (shim.isWindows() || shim.isMac()) && !shim.isPortable() ? options.keytar : null; const keytar = (shim.isWindows() || shim.isMac()) && !shim.isPortable() ? options.keytar : null;
const appVersion = options.appVersion; const appVersion = options.appVersion;
shim.setNodeSqlite(options.nodeSqlite); shim.setNodeSqlite(options.nodeSqlite);
shim.fsDriver = () => { shim.fsDriver = () => {
@ -420,10 +445,11 @@ function shimInit(options = null) {
return new Buffer(data).toString('base64'); return new Buffer(data).toString('base64');
}; };
shim.fetch = async function(url, options = null) { shim.fetch = async function(url, options = {}) {
const validatedUrl = urlValidator.isUri(url); const validatedUrl = urlValidator.isUri(url);
if (!validatedUrl) throw new Error(`Not a valid URL: ${url}`); if (!validatedUrl) throw new Error(`Not a valid URL: ${url}`);
const resolvedProxyUrl = resolveProxyUrl(proxySettings.proxyUrl);
options.agent = (resolvedProxyUrl && proxySettings.proxyEnabled) ? shim.proxyAgent(url, resolvedProxyUrl) : null;
return shim.fetchWithRetry(() => { return shim.fetchWithRetry(() => {
return nodeFetch(url, options); return nodeFetch(url, options);
}, options); }, options);
@ -466,6 +492,9 @@ function shimInit(options = null) {
headers: headers, headers: headers,
}; };
const resolvedProxyUrl = resolveProxyUrl(proxySettings.proxyUrl);
requestOptions.agent = (resolvedProxyUrl && proxySettings.proxyEnabled) ? shim.proxyAgent(url, resolvedProxyUrl) : null;
const doFetchOperation = async () => { const doFetchOperation = async () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let file = null; let file = null;
@ -572,6 +601,27 @@ function shimInit(options = null) {
return url.startsWith('https') ? shim.httpAgent_.https : shim.httpAgent_.http; return url.startsWith('https') ? shim.httpAgent_.https : shim.httpAgent_.http;
}; };
shim.proxyAgent = (serverUrl, proxyUrl) => {
const proxyAgentConfig = {
keepAlive: true,
maxSockets: proxySettings.maxConcurrentConnections,
keepAliveMsecs: 5000,
proxy: proxyUrl,
timeout: proxySettings.proxyTimeout * 1000,
};
// Based on https://github.com/delvedor/hpagent#usage
if (!isUrlHttps(proxyUrl) && !isUrlHttps(serverUrl)) {
return new HttpProxyAgent(proxyAgentConfig);
} else if (isUrlHttps(proxyUrl) && !isUrlHttps(serverUrl)) {
return new HttpProxyAgent(proxyAgentConfig);
} else if (!isUrlHttps(proxyUrl) && isUrlHttps(serverUrl)) {
return new HttpsProxyAgent(proxyAgentConfig);
} else {
return new HttpsProxyAgent(proxyAgentConfig);
}
};
shim.openOrCreateFile = (filepath, defaultContents) => { shim.openOrCreateFile = (filepath, defaultContents) => {
// If the file doesn't exist, create it // If the file doesn't exist, create it
if (!fs.existsSync(filepath)) { if (!fs.existsSync(filepath)) {
@ -634,4 +684,4 @@ function shimInit(options = null) {
}; };
} }
module.exports = { shimInit }; module.exports = { shimInit, setupProxySettings };

View File

@ -16,7 +16,7 @@ let isTestingEnv_ = false;
// (app-desktop, app-mobile, etc.) since we are sure they won't be dependency to // (app-desktop, app-mobile, etc.) since we are sure they won't be dependency to
// other packages (unlike the lib which can be included anywhere). // other packages (unlike the lib which can be included anywhere).
// //
// Regarding the type - althought we import React, we only use it as a type // Regarding the type - although we import React, we only use it as a type
// using `typeof React`. This is just to get types in hooks. // using `typeof React`. This is just to get types in hooks.
// //
// https://stackoverflow.com/a/42816077/561309 // https://stackoverflow.com/a/42816077/561309

View File

@ -3436,6 +3436,7 @@ __metadata:
follow-redirects: ^1.2.4 follow-redirects: ^1.2.4
form-data: ^2.1.4 form-data: ^2.1.4
fs-extra: ^5.0.0 fs-extra: ^5.0.0
hpagent: ^1.0.0
html-entities: ^1.2.1 html-entities: ^1.2.1
html-minifier: ^3.5.15 html-minifier: ^3.5.15
image-data-uri: ^2.0.0 image-data-uri: ^2.0.0
@ -16672,6 +16673,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"hpagent@npm:^1.0.0":
version: 1.0.0
resolution: "hpagent@npm:1.0.0"
checksum: 911a9ba612747f5c9403fb60c1abd0c47a27fa634826caad9aecad41b899533489da36c92758b5cd83576e2bf9e84e23a4374a40aa513106d5450f62856c4b2d
languageName: node
linkType: hard
"html-encoding-sniffer@npm:^1.0.2": "html-encoding-sniffer@npm:^1.0.2":
version: 1.0.2 version: 1.0.2
resolution: "html-encoding-sniffer@npm:1.0.2" resolution: "html-encoding-sniffer@npm:1.0.2"