From 7995b89e9552a9dff7ad93d7aa9688081ed1384a Mon Sep 17 00:00:00 2001 From: Julius Knorr Date: Thu, 18 Jun 2026 15:02:07 +0200 Subject: [PATCH] feat: add internalUrl option to rewrite self-referential HTTP fetches When services.CoAuthoring.server.internalUrl is set alongside storage.externalHost, any URL passed to downloadUrlPromise whose origin matches externalHost is rewritten to use internalUrl instead. This avoids hairpin-NAT / split-horizon DNS failures when DocService fetches its own signed cache URLs (e.g. /downloadfile proxy, converter source downloads). Signed-off-by: Julius Knorr Assisted-by: ClaudeCode:claude-sonnet-4-6 --- Common/config/default.json | 3 ++- Common/sources/utils.js | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Common/config/default.json b/Common/config/default.json index e86d4d3e9..284f76c23 100644 --- a/Common/config/default.json +++ b/Common/config/default.json @@ -346,7 +346,8 @@ "documentFormatsFile": "../../document-formats/onlyoffice-docs-formats.json", "downloadFileAllowExt": ["pdf", "xlsx"], "tokenRequiredParams": true, - "forceSaveUsingButtonWithoutChanges": false + "forceSaveUsingButtonWithoutChanges": false, + "internalUrl": "" }, "requestDefaults": { "headers": { diff --git a/Common/sources/utils.js b/Common/sources/utils.js index fedc5e7df..d0edee7b4 100644 --- a/Common/sources/utils.js +++ b/Common/sources/utils.js @@ -78,6 +78,7 @@ const cfgSecret = config.get('aesEncrypt.secret'); const cfgAESConfig = config.util.cloneDeep(config.get('aesEncrypt.config')); const cfgRequesFilteringAgent = config.get('services.CoAuthoring.request-filtering-agent'); const cfgStorageExternalHost = config.get('storage.externalHost'); +const cfgInternalUrl = config.get('services.CoAuthoring.server.internalUrl'); const cfgExternalRequestDirectIfIn = config.get('externalRequest.directIfIn'); const cfgExternalRequestAction = config.get('externalRequest.action'); const cfgWinCa = config.util.cloneDeep(config.get('win-ca')); @@ -346,8 +347,16 @@ async function downloadUrlPromise(ctx, uri, optTimeout, optLimit, opt_Authorizat const tenTenantRequestDefaults = ctx.getCfg('services.CoAuthoring.requestDefaults', cfgRequestDefaults); const tenTokenOutboxHeader = ctx.getCfg('services.CoAuthoring.token.outbox.header', cfgTokenOutboxHeader); const tenTokenOutboxPrefix = ctx.getCfg('services.CoAuthoring.token.outbox.prefix', cfgTokenOutboxPrefix); + const tenInternalUrl = ctx.getCfg('services.CoAuthoring.server.internalUrl', cfgInternalUrl); + const tenStorageExternalHost = ctx.getCfg('storage.externalHost', cfgStorageExternalHost); const sizeLimit = optLimit || Number.MAX_VALUE; uri = URI.serialize(URI.parse(uri)); + if (tenInternalUrl && tenStorageExternalHost) { + const externalOrigin = tenStorageExternalHost.replace(/\/$/, ''); + if (uri.startsWith(externalOrigin + '/') || uri === externalOrigin) { + uri = tenInternalUrl.replace(/\/$/, '') + uri.slice(externalOrigin.length); + } + } const options = config.util.cloneDeep(tenTenantRequestDefaults); //baseRequest creates new agent(win-ca injects in globalAgent)