From de376dfedf1069079153dd89b1ad467cfe9e8601 Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Mon, 16 Jan 2023 12:51:26 +0200 Subject: [PATCH 1/4] ImageRenderer - handle attachments url from staging being rendered on prod --- .../HTMLRenderers/ImageRenderer.js | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js index cef91ba9a9e2..2ef20db7cc6f 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -1,4 +1,5 @@ import React from 'react'; +import _ from 'underscore'; import htmlRendererPropTypes from './htmlRendererPropTypes'; import Config from '../../../CONFIG'; import AttachmentModal from '../../AttachmentModal'; @@ -8,6 +9,28 @@ import PressableWithoutFocus from '../../PressableWithoutFocus'; import CONST from '../../../CONST'; import {ShowContextMenuContext, showContextMenuForReport} from '../../ShowContextMenuContext'; +/** + * Update the image URL so images can be accessed depending on the config environment + * + * @param {String} urlString + * @returns {*|String} + */ +function mapSource(urlString) { + // 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 originsWeShouldReplace = [Config.EXPENSIFY.EXPENSIFY_URL, Config.EXPENSIFY.STAGING_EXPENSIFY_URL]; + + const originToReplace = _.find(originsWeShouldReplace, origin => urlString.startsWith(origin)); + if (!originToReplace) { + return urlString; + } + + return urlString.replace( + originToReplace, + Config.EXPENSIFY.URL_API_ROOT, + ); +} + const ImageRenderer = (props) => { const htmlAttribs = props.tnode.attributes; @@ -30,20 +53,10 @@ const ImageRenderer = (props) => { // const isAttachment = Boolean(htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]); const originalFileName = htmlAttribs['data-name']; - let previewSource = htmlAttribs.src; - let source = isAttachment + const previewSource = mapSource(htmlAttribs.src); + const source = mapSource(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, - ); + : 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; From 5930b4a44a360b067ec890bc08f324d2ba069939 Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Tue, 17 Jan 2023 12:17:35 +0200 Subject: [PATCH 2/4] Extract `replaceSourceOrigin.js` to libs and refactor usages --- .../HTMLRenderers/ImageRenderer.js | 29 ++----------------- src/libs/fileDownload/getAttachmentDetails.js | 13 +++------ src/libs/replaceSourceOrigin.js | 24 +++++++++++++++ 3 files changed, 31 insertions(+), 35 deletions(-) create mode 100644 src/libs/replaceSourceOrigin.js diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js index 2ef20db7cc6f..357a0733f6a2 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -1,35 +1,12 @@ import React from 'react'; -import _ from 'underscore'; 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'; - -/** - * Update the image URL so images can be accessed depending on the config environment - * - * @param {String} urlString - * @returns {*|String} - */ -function mapSource(urlString) { - // 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 originsWeShouldReplace = [Config.EXPENSIFY.EXPENSIFY_URL, Config.EXPENSIFY.STAGING_EXPENSIFY_URL]; - - const originToReplace = _.find(originsWeShouldReplace, origin => urlString.startsWith(origin)); - if (!originToReplace) { - return urlString; - } - - return urlString.replace( - originToReplace, - Config.EXPENSIFY.URL_API_ROOT, - ); -} +import replaceSourceOrigin from '../../../libs/replaceSourceOrigin'; const ImageRenderer = (props) => { const htmlAttribs = props.tnode.attributes; @@ -53,8 +30,8 @@ const ImageRenderer = (props) => { // const isAttachment = Boolean(htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]); const originalFileName = htmlAttribs['data-name']; - const previewSource = mapSource(htmlAttribs.src); - const source = mapSource(isAttachment + const previewSource = replaceSourceOrigin(htmlAttribs.src); + const source = replaceSourceOrigin(isAttachment ? htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE] : htmlAttribs.src); diff --git a/src/libs/fileDownload/getAttachmentDetails.js b/src/libs/fileDownload/getAttachmentDetails.js index 8a465403f8a8..39da70fe739f 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 replaceSourceOrigin from '../replaceSourceOrigin'; /** * Extract the thumbnail URL, source URL and the original filename from the HTML. @@ -19,14 +19,9 @@ 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, - ); + const sourceURL = replaceSourceOrigin(html.match(SOURCE_REGEX)[1]); + const imageURL = IS_IMAGE_TAG && replaceSourceOrigin(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/replaceSourceOrigin.js b/src/libs/replaceSourceOrigin.js new file mode 100644 index 000000000000..e65fec8fab3b --- /dev/null +++ b/src/libs/replaceSourceOrigin.js @@ -0,0 +1,24 @@ +import _ from 'underscore'; +import Config from '../CONFIG'; + +/** + * Update the URL so images/files can be accessed depending on the config environment + * + * @param {String} urlString + * @returns {String} + */ +export default function replaceSourceOrigin(urlString) { + // 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 originsToReplace = [Config.EXPENSIFY.EXPENSIFY_URL, Config.EXPENSIFY.STAGING_EXPENSIFY_URL]; + + const originToReplace = _.find(originsToReplace, origin => urlString.startsWith(origin)); + if (!originToReplace) { + return urlString; + } + + return urlString.replace( + originToReplace, + Config.EXPENSIFY.URL_API_ROOT, + ); +} From 482e13f78926a644cff3b4f1e9623c9d989f9e13 Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Mon, 23 Jan 2023 09:35:18 +0200 Subject: [PATCH 3/4] replaceSourceOrigin - update to cover for absolute URLs --- src/libs/replaceSourceOrigin.js | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/libs/replaceSourceOrigin.js b/src/libs/replaceSourceOrigin.js index e65fec8fab3b..277e7b5f213c 100644 --- a/src/libs/replaceSourceOrigin.js +++ b/src/libs/replaceSourceOrigin.js @@ -1,24 +1,21 @@ -import _ from 'underscore'; 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('|')})`); + /** - * Update the URL so images/files can be accessed depending on the config environment + * Updates URLs, so they are accessed relative to URL_API_ROOT + * Matches: absolute, prod or staging URLs + * Unmatched URLs aren't modified * - * @param {String} urlString + * @param {String} url * @returns {String} */ -export default function replaceSourceOrigin(urlString) { - // 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 originsToReplace = [Config.EXPENSIFY.EXPENSIFY_URL, Config.EXPENSIFY.STAGING_EXPENSIFY_URL]; - - const originToReplace = _.find(originsToReplace, origin => urlString.startsWith(origin)); - if (!originToReplace) { - return urlString; - } - - return urlString.replace( - originToReplace, - Config.EXPENSIFY.URL_API_ROOT, - ); +export default function replaceSourceOrigin(url) { + return url.replace(ORIGIN_PATTERN, Config.EXPENSIFY.URL_API_ROOT); } From 72a2950a04df5b4a4f118a8b2d38854fc51d5ff1 Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Wed, 25 Jan 2023 13:35:46 +0200 Subject: [PATCH 4/4] Documentation and better naming for `tryResolveUrlFromApiRoot` --- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.js | 8 +++++--- src/libs/fileDownload/getAttachmentDetails.js | 8 +++++--- ...aceSourceOrigin.js => tryResolveUrlFromApiRoot.js} | 11 +++++++---- 3 files changed, 17 insertions(+), 10 deletions(-) rename src/libs/{replaceSourceOrigin.js => tryResolveUrlFromApiRoot.js} (58%) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js index 357a0733f6a2..537913b500f5 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -6,7 +6,7 @@ import ThumbnailImage from '../../ThumbnailImage'; import PressableWithoutFocus from '../../PressableWithoutFocus'; import CONST from '../../../CONST'; import {ShowContextMenuContext, showContextMenuForReport} from '../../ShowContextMenuContext'; -import replaceSourceOrigin from '../../../libs/replaceSourceOrigin'; +import tryResolveUrlFromApiRoot from '../../../libs/tryResolveUrlFromApiRoot'; const ImageRenderer = (props) => { const htmlAttribs = props.tnode.attributes; @@ -30,8 +30,10 @@ const ImageRenderer = (props) => { // const isAttachment = Boolean(htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]); const originalFileName = htmlAttribs['data-name']; - const previewSource = replaceSourceOrigin(htmlAttribs.src); - const source = replaceSourceOrigin(isAttachment + + // 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); diff --git a/src/libs/fileDownload/getAttachmentDetails.js b/src/libs/fileDownload/getAttachmentDetails.js index 39da70fe739f..8105a78c4799 100644 --- a/src/libs/fileDownload/getAttachmentDetails.js +++ b/src/libs/fileDownload/getAttachmentDetails.js @@ -1,5 +1,5 @@ import CONST from '../../CONST'; -import replaceSourceOrigin from '../replaceSourceOrigin'; +import tryResolveUrlFromApiRoot from '../tryResolveUrlFromApiRoot'; /** * Extract the thumbnail URL, source URL and the original filename from the HTML. @@ -19,8 +19,10 @@ export default function getAttachmentName(html) { originalFileName: null, }; } - const sourceURL = replaceSourceOrigin(html.match(SOURCE_REGEX)[1]); - const imageURL = IS_IMAGE_TAG && replaceSourceOrigin(html.match(PREVIEW_SOURCE_REGEX)[1]); + + // 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]; diff --git a/src/libs/replaceSourceOrigin.js b/src/libs/tryResolveUrlFromApiRoot.js similarity index 58% rename from src/libs/replaceSourceOrigin.js rename to src/libs/tryResolveUrlFromApiRoot.js index 277e7b5f213c..7797d3446459 100644 --- a/src/libs/replaceSourceOrigin.js +++ b/src/libs/tryResolveUrlFromApiRoot.js @@ -9,13 +9,16 @@ const ORIGINS_TO_REPLACE = ['/+', Config.EXPENSIFY.EXPENSIFY_URL, Config.EXPENSI const ORIGIN_PATTERN = new RegExp(`^(${ORIGINS_TO_REPLACE.join('|')})`); /** - * Updates URLs, so they are accessed relative to URL_API_ROOT - * Matches: absolute, prod or staging URLs - * Unmatched URLs aren't modified + * 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 replaceSourceOrigin(url) { +export default function tryResolveUrlFromApiRoot(url) { return url.replace(ORIGIN_PATTERN, Config.EXPENSIFY.URL_API_ROOT); }