diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js
index cef91ba9a9e2..537913b500f5 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js
@@ -1,12 +1,12 @@
import React from 'react';
import htmlRendererPropTypes from './htmlRendererPropTypes';
-import Config from '../../../CONFIG';
import AttachmentModal from '../../AttachmentModal';
import styles from '../../../styles/styles';
import ThumbnailImage from '../../ThumbnailImage';
import PressableWithoutFocus from '../../PressableWithoutFocus';
import CONST from '../../../CONST';
import {ShowContextMenuContext, showContextMenuForReport} from '../../ShowContextMenuContext';
+import tryResolveUrlFromApiRoot from '../../../libs/tryResolveUrlFromApiRoot';
const ImageRenderer = (props) => {
const htmlAttribs = props.tnode.attributes;
@@ -30,20 +30,12 @@ const ImageRenderer = (props) => {
//
const isAttachment = Boolean(htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]);
const originalFileName = htmlAttribs['data-name'];
- let previewSource = htmlAttribs.src;
- let source = isAttachment
- ? htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]
- : htmlAttribs.src;
- // Update the image URL so the images can be accessed depending on the config environment
- previewSource = previewSource.replace(
- Config.EXPENSIFY.EXPENSIFY_URL,
- Config.EXPENSIFY.URL_API_ROOT,
- );
- source = source.replace(
- Config.EXPENSIFY.EXPENSIFY_URL,
- Config.EXPENSIFY.URL_API_ROOT,
- );
+ // Files created/uploaded/hosted by App should resolve from API ROOT. Other URLs aren't modified
+ const previewSource = tryResolveUrlFromApiRoot(htmlAttribs.src);
+ const source = tryResolveUrlFromApiRoot(isAttachment
+ ? htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]
+ : htmlAttribs.src);
const imageWidth = htmlAttribs['data-expensify-width'] ? parseInt(htmlAttribs['data-expensify-width'], 10) : undefined;
const imageHeight = htmlAttribs['data-expensify-height'] ? parseInt(htmlAttribs['data-expensify-height'], 10) : undefined;
diff --git a/src/libs/fileDownload/getAttachmentDetails.js b/src/libs/fileDownload/getAttachmentDetails.js
index 8a465403f8a8..8105a78c4799 100644
--- a/src/libs/fileDownload/getAttachmentDetails.js
+++ b/src/libs/fileDownload/getAttachmentDetails.js
@@ -1,5 +1,5 @@
import CONST from '../../CONST';
-import Config from '../../CONFIG';
+import tryResolveUrlFromApiRoot from '../tryResolveUrlFromApiRoot';
/**
* Extract the thumbnail URL, source URL and the original filename from the HTML.
@@ -19,14 +19,11 @@ export default function getAttachmentName(html) {
originalFileName: null,
};
}
- const sourceURL = html.match(SOURCE_REGEX)[1].replace(
- Config.EXPENSIFY.EXPENSIFY_URL,
- Config.EXPENSIFY.URL_API_ROOT,
- );
- const previewSourceURL = (IS_IMAGE_TAG ? html.match(PREVIEW_SOURCE_REGEX)[1] : sourceURL).replace(
- Config.EXPENSIFY.EXPENSIFY_URL,
- Config.EXPENSIFY.URL_API_ROOT,
- );
+
+ // Files created/uploaded/hosted by App should resolve from API ROOT. Other URLs aren't modified
+ const sourceURL = tryResolveUrlFromApiRoot(html.match(SOURCE_REGEX)[1]);
+ const imageURL = IS_IMAGE_TAG && tryResolveUrlFromApiRoot(html.match(PREVIEW_SOURCE_REGEX)[1]);
+ const previewSourceURL = IS_IMAGE_TAG ? imageURL : sourceURL;
const originalFileName = html.match(ORIGINAL_FILENAME_REGEX)[1];
// Update the image URL so the images can be accessed depending on the config environment
diff --git a/src/libs/tryResolveUrlFromApiRoot.js b/src/libs/tryResolveUrlFromApiRoot.js
new file mode 100644
index 000000000000..7797d3446459
--- /dev/null
+++ b/src/libs/tryResolveUrlFromApiRoot.js
@@ -0,0 +1,24 @@
+import Config from '../CONFIG';
+
+// Absolute URLs (`/` or `//`) should be resolved from API ROOT
+// Legacy attachments can come from either staging or prod, depending on the env they were uploaded by
+// Both should be replaced and loaded from API ROOT of the current environment
+const ORIGINS_TO_REPLACE = ['/+', Config.EXPENSIFY.EXPENSIFY_URL, Config.EXPENSIFY.STAGING_EXPENSIFY_URL];
+
+// Anything starting with a match from ORIGINS_TO_REPLACE
+const ORIGIN_PATTERN = new RegExp(`^(${ORIGINS_TO_REPLACE.join('|')})`);
+
+/**
+ * When possible resolve sources relative to API ROOT
+ * Updates applicable URLs, so they are accessed relative to URL_API_ROOT
+ * - Absolute URLs like `/{path}`, become: `https://{API_ROOT}/{path}`
+ * - Similarly for prod or staging URLs we replace the `https://www.expensify`
+ * or `https://staging.expensify` part, with `https://{API_ROOT}`
+ * - Unmatched URLs are returned with no modifications
+ *
+ * @param {String} url
+ * @returns {String}
+ */
+export default function tryResolveUrlFromApiRoot(url) {
+ return url.replace(ORIGIN_PATTERN, Config.EXPENSIFY.URL_API_ROOT);
+}