Skip to content

Download pasted RTD images in the browser instead of on the server - #227

Merged
unilynx merged 2 commits into
mainfrom
feature/rtd-client-side-image-paste
Nov 13, 2026
Merged

unilynx merged 2 commits into
mainfrom
feature/rtd-client-side-image-paste

Conversation

@unilynx

@unilynx unilynx commented Nov 12, 2026

Copy link
Copy Markdown
Member

Why

When content with images is pasted into an RTD, the editor asked the server to download each image URL through getImgFromRemoteURL on the publisher form service. That service is reachable without authentication, so anyone could make the server fetch arbitrary URLs (SSRF), with only weak port and credential checks in place.

What changes

  • Pasted images are downloaded by the browser and uploaded through the existing getUploadedFileFinalURL, the same path the image upload button uses. RPC_GetImgFromRemoteURL and its TypeScript interface entry are removed.
  • data: URLs are decoded directly instead of fetched, because CSP connect-src (eg. in the backend) blocks fetch() of data:/blob:.
  • 'Copy image' pastes use the clipboard's image file when the clipboard offers exactly one <img> and one image file. This avoids needing CORS on the remote URL.
  • Validation: png, jpeg, gif, webp and avif only, max 20 MB, 30 s timeout, and the image must decode (createImageBitmap). Fetches use credentials: "omit" and referrerPolicy: "strict-origin".
  • Upload placeholders keep the pasted image's dimensions from width/height attributes or pixel style dimensions. The spinner isn't stretched (object-fit: none).
  • getUploadedFileFinalURL treats webp/avif as images (image cache URL instead of a file URL).
  • Server-side fix in ValidateAndCleanupImage: non-image data: URLs were disposed but then still stored, and invalid data: URLs weren't handled. Both are now discarded.

Known limitations

  • Remote images whose server doesn't send CORS headers can no longer be imported and are dropped, as failed downloads were before. A TODO marks where the editor should tell the user how many images couldn't be copied.
  • Cross-origin downloads in the backend are blocked by the portal's CSP. It sets connect-src 'self' https://*.googleapis.com, so cross-origin https: image URLs in pasted HTML will be dropped there unless connect-src is widened. Same-origin images, data: URLs and clipboard image files work.

Tests

  • New steps in tollium.richdoc.teststructured: unusable pasted images are rejected (non-image data: URL, corrupt png, 404, file: URL), and png and webp clipboard files are imported when the HTML's remote URL can't be fetched.
  • Passing: tollium.richdoc.teststructured, tollium.richdoc.testfree, tollium.richdoc.testrteservice, wh.jscomponents.rte.teststructured, wh.jscomponents.rte.teststructured-copypaste, wh.jscomponents.rte.testbase, publisher.forms.testrte and publisher.forms.testuploadfield.

@unilynx
unilynx requested a review from a team November 12, 2026 12:02
@unilynx

unilynx commented Nov 12, 2026

Copy link
Copy Markdown
Member Author

@webharerob decodeImageDataURL is added as internal api - could be moved to @webhare/upload next to getFileAsDataURL and then also used to replace dataURItoBlob in imgeditcontroller.ts. worth it?

Copilot AI left a comment

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.

🟡 Changes recommended

The paste handler can discard valid images, retain unused files and object URLs, and buffer oversized downloads before enforcing the size limit.

3 open issues
What changed in this PR

Moves pasted RTD image downloads into the browser and reuses the existing upload service, removing the unauthenticated server-side URL-fetching endpoint that enabled SSRF.

Changes:

  • Adds browser-side image validation, direct data URL decoding, and clipboard-file handling.
  • Preserves pasted image dimensions during upload and supports WebP/AVIF image-cache URLs.
  • Fixes invalid data URL cleanup and adds paste regression tests.
File Description
whtree/​modules/​webhare_testsuite/​tests/​tollium/​richdoc/​teststructured.ts Tests rejected images and PNG/WebP clipboard imports.
whtree/​modules/​tollium/​web/​ui/​components/​richeditor/​richeditor.scss Prevents upload spinners from stretching.
whtree/​modules/​tollium/​web/​ui/​components/​richeditor/​internal/​structurededitor.tsx Prefers clipboard image files and preserves styled dimensions.
whtree/​modules/​tollium/​web/​ui/​components/​richeditor/​internal/​editorbase.tsx Downloads, validates, and uploads pasted images in the browser.
whtree/​modules/​publisher/​lib/​internal/​rtd/​service.whlib Discards invalid and non-image data URLs before storage.
whtree/​modules/​publisher/​lib/​internal/​forms/​rpc.whlib Removes remote fetching and routes WebP/AVIF uploads through the image cache.
whtree/​jssdk/​forms/​src/​formservice.ts Removes the remote-image download method from the service interface.

Review effort: Balanced

webharebot[bot]
webharebot Bot previously approved these changes Nov 12, 2026

@webharebot webharebot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 Auto-Approved: PR author (@unilynx) is an internal organization/repository member.

@unilynx

unilynx commented Nov 13, 2026

Copy link
Copy Markdown
Member Author

@webharerob decodeImageDataURL is added as internal api - could be moved to @webhare/upload next to getFileAsDataURL and then also used to replace dataURItoBlob in imgeditcontroller.ts. worth it?

will not change that yet

@unilynx
unilynx requested a balanced review from Copilot November 13, 2026 10:55
@unilynx
unilynx marked this pull request as draft November 13, 2026 11:00

Copilot AI left a comment

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.

🟡 Changes recommended

The preliminary length guard incorrectly applies base64 limits to percent-encoded payloads, causing valid images below the 20 MiB decoded-size limit to be silently removed on paste.

6 open issues

Review effort: Balanced

The RTD imported pasted images by asking the server to fetch their URLs
(getImgFromRemoteURL), which let any client make the server fetch arbitrary
URLs. The browser now downloads pasted images itself and uploads them through
getUploadedFileFinalURL, and the server-side fetch is removed.

- data: URLs are decoded directly as the CSP generally blocks fetching them
- When 'Copy image' offers both a remote URL and the image file, use the file
- Accept png, jpeg, gif, webp and avif, max 20MB, and verify they decode
- Upload placeholders take the pasted image's dimensions
- Server-side RTD cleanup now properly discards invalid or non-image data: URLs
@unilynx
unilynx force-pushed the feature/rtd-client-side-image-paste branch from 0742c19 to a086296 Compare November 13, 2026 11:12
@unilynx
unilynx marked this pull request as ready for review November 13, 2026 11:12

@webharebot webharebot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 Auto-Approved: PR author (@unilynx) is an internal organization/repository member.

@unilynx
unilynx merged commit 36085fa into main Nov 13, 2026
31 checks passed
@unilynx
unilynx deleted the feature/rtd-client-side-image-paste branch November 13, 2026 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants