-
Notifications
You must be signed in to change notification settings - Fork 16
Enable previewing emails (.eml files) in limel-file-viewer
#3783
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the
📝 WalkthroughWalkthroughAdds .eml email viewing: new postal-mime dependency, an email-viewer component with styles and examples, an email-loader that parses MIME emails and produces a view model, file-viewer integration for 'email' type with lifecycle cleanup, utility types and helpers, and translations for eight languages. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant FileViewer as limel-file-viewer
participant Loader as email-loader
participant PostalMime as postal-mime
participant EmailViewer as limel-email-viewer
User->>FileViewer: Request .eml URL
FileViewer->>FileViewer: Detect extension -> 'email'
FileViewer->>Loader: loadEmail(url)
Loader->>PostalMime: fetch(url) & parse(eml, options)
PostalMime-->>Loader: Parsed MIME structure
Loader->>Loader: Extract headers, body, attachments
Loader->>Loader: Convert inline attachments -> data: URLs
Loader->>Loader: Replace cid: refs in HTML
Loader-->>FileViewer: Email object
FileViewer->>EmailViewer: Render(email)
EmailViewer->>User: Display headers, body, attachments
sequenceDiagram
participant Loader as email-loader
participant PostalMime as postal-mime
participant Processor as AttachmentProcessor
Loader->>PostalMime: parse(eml)
PostalMime-->>Loader: attachments[]
loop per attachment
alt inline/related
Processor->>Processor: ArrayBuffer -> Base64
Processor->>Processor: Create data: URL
Processor-->>Loader: Map by Content-ID
Loader->>Loader: Replace cid: refs in HTML
else attachment
Processor->>Loader: Extract filename, mimeType, size
Loader->>Loader: Add to attachments list
end
end
Loader-->>FileViewer: Email with processed attachments
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Documentation has been published to https://lundalogik.github.io/lime-elements/versions/PR-3783/ |
3fab733 to
af7b58e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
🤖 Fix all issues with AI agents
In `@src/components/email-viewer/email-viewer.tsx`:
- Around line 202-221: The attachments block uses a <label> as a non-form
heading and redundant explicit list roles; change the <label> (around variable
"label") to a semantic non-form element (e.g., <span> or an appropriate heading
tag) so it isn't treated as a form label, and remove role="list" on the <ul> and
role="listitem" on each <li>; update the JSX where this.attachments is mapped
(see render using this.getTranslation('file-viewer.email.attachment.unnamed')
and this.renderSizeBadge(attachment.size)) to reflect these markup changes while
preserving visual styling and accessibility semantics.
- Around line 128-133: The renderBody method currently inserts untrusted HTML
via innerHTML (this.bodyHtml) in the EmailViewer component; sanitize the HTML
before rendering by integrating a vetted sanitizer (e.g., DOMPurify) or
switching to a sandboxed iframe/CSP approach. Update the renderBody
implementation to run this.bodyHtml through the sanitizer (or load it into a
sandboxed iframe) and then assign the sanitized output to innerHTML; ensure
configuration strips scripts, event handlers, and javascript: URLs and reference
the renderBody method, the bodyHtml property, and the innerHTML assignment when
making the change.
In `@src/components/file-viewer/email-loader.ts`:
- Around line 156-182: The function formatAddress declares a return type of
string but currently returns undefined in some branches (e.g., when address is
falsy or fallback returns undefined), so update the signature to return string |
undefined or ensure every branch returns a string (e.g., return empty string
instead of undefined); locate formatAddress and related formatAddresses to apply
the same fix consistently and adjust callers if they rely on a strict string
type.
- Around line 145-154: The function formatAddresses currently declares a return
type of string but returns undefined in some branches; update its signature to
return string | undefined and ensure callers handle the possibly undefined
result (or alternatively return an empty string consistently). Specifically
change the formatAddresses function declaration and any direct callers that
assume a plain string (e.g., places invoking formatAddresses(...) for display or
concatenation) to accept and handle undefined values.
- Around line 94-100: The normalizeContentId function uses
String.prototype.replaceAll with an anchored regex /( ^<|>$)/g which only needs
a single replace; switch to String.prototype.replace (keeping the same regex and
flags) to be idiomatic and avoid misleading use of replaceAll; update the return
path in normalizeContentId to call replace(...).trim() and run tests/linters to
confirm no behavioral change.
- Around line 21-28: The loadEmail function currently calls fetch(...) and
PostalMime.parse(...) without try/catch, so network errors or parse exceptions
will propagate; wrap the fetch + buffer + PostalMime.parse sequence in a
try/catch inside loadEmail, catch and rethrow or return a controlled error with
a clear message (e.g., include the url and the original error), and handle
non-OK HTTP responses by checking response.ok and throwing a descriptive error
before parsing; reference the loadEmail function, the fetch call, response.ok
check, and PostalMime.parse to locate where to add the error handling and
logging.
In `@src/components/file-viewer/examples/file-viewer-eml.tsx`:
- Around line 1-28: The render method in FileViewerEmlExample currently returns
a JSX array of siblings which causes key/lint warnings; update the component to
import Host from '@stencil/core' and change the render() to return a single
<Host> element that wraps all child nodes (replace the array literal returned in
render with a single Host wrapper containing the h4 and limel-file-viewer
elements), removing the surrounding array brackets so the component returns one
root element.
In `@src/components/file-viewer/file-viewer.tsx`:
- Around line 452-463: The file-viewer currently calls loadEmail(this.url)
without error handling, so if loadEmail rejects the component can keep stale
this.email/this.fileUrl and never set this.loading=false; wrap the email branch
in a try/catch/finally: call revokeCreatedObjectUrls() first (as existing), then
in the 'email' branch await loadEmail(this.url) inside try, set this.email and
this.fileUrl on success, on catch reset this.email and this.fileUrl to
null/empty and optionally log the error, and in finally ensure this.loading =
false so the UI is not stuck; reference the email branch where loadEmail is
invoked and the properties this.email, this.fileUrl, this.createdObjectUrls, and
this.loading.
- Around line 260-278: The renderEmail method returns an array of sibling JSX
nodes; replace that by wrapping the returned elements in a <Host> container and
import Host from '@stencil/core'. Update the renderEmail function (the method
named renderEmail) to return a single JSX tree with <Host> containing
this.renderButtons() and the <limel-email-viewer> element (keeping existing
props like subject, from, to, cc, date, bodyHtml, bodyText, attachments,
fallbackUrl using sanitizeUrl(this.fileUrl), and language), and ensure Host is
added to the component imports.
In `@src/translations/sv.ts`:
- Around line 46-52: The file's translation keys for file-viewer email
('file-viewer.email.subject', 'file-viewer.email.from', 'file-viewer.email.to',
'file-viewer.email.cc', 'file-viewer.email.date',
'file-viewer.email.attachments', 'file-viewer.email.attachment.unnamed') are
misplaced between editor-menu entries; move that entire block so it sits after
the existing 'file-viewer.more-actions' entry and before 'editor-menu.bold' to
match the ordering used in other locale files (e.g., de.ts), preserving the
exact keys and values and leaving surrounding entries intact.
| private renderBody() { | ||
| if (this.bodyHtml) { | ||
| return ( | ||
| <div class="body" innerHTML={this.bodyHtml} part="email-body" /> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the email-viewer.tsx file, particularly around lines 128-133
# and understand where bodyHtml comes from
cat -n src/components/email-viewer/email-viewer.tsx | head -160 | tail -80Repository: Lundalogik/lime-elements
Length of output: 2879
🏁 Script executed:
# Search for any sanitization utilities in the codebase
fd -t f \( -name "*.ts" -o -name "*.tsx" \) | xargs rg -l "sanitiz|DOMPurify|xss" 2>/dev/null | head -20Repository: Lundalogik/lime-elements
Length of output: 713
🏁 Script executed:
# Search for how bodyHtml is set/populated in the component
rg "bodyHtml\s*=" src/components/email-viewer/ -A 3 -B 3Repository: Lundalogik/lime-elements
Length of output: 1525
🏁 Script executed:
# Check if there's any documentation or comments about XSS handling
rg -i "xss|security|sanitiz|untrusted" src/components/email-viewer/Repository: Lundalogik/lime-elements
Length of output: 50
🌐 Web query:
best practices for safely rendering email HTML content in web components to prevent XSS attacks
💡 Result:
Short checklist of best practices for safely rendering email HTML in web components (to prevent XSS):
-
Never inject raw HTML strings into DOM sinks (innerHTML, shadowRoot.innerHTML, setHTMLUnsafe) without sanitizing or Trusted Types. Use TrustedHTML/Trusted Types + CSP where possible. [1][2]
-
Sanitize untrusted HTML with a vetted allowlist sanitizer (e.g., DOMPurify) configured to: remove script tags, event-* attributes, dangerous protocols (javascript:, data:, vbscript:), and any tags/attributes you don’t need. Keep the sanitizer up to date. [3][4]
-
Use defense-in-depth:
- Enforce a strict Content-Security-Policy (no unsafe-inline, restrict script-src, use sandbox/CSP sandbox for embedded documents). [2][6]
- Enforce trusted-types via CSP (require-trusted-types-for) so only TrustedHTML can reach injection sinks. [2][5]
-
Prefer isolation for full-document email HTML:
- Render email HTML in a sandboxed iframe served from a different origin (or use srcdoc + sandbox with minimal allowances). Don’t combine allow-scripts with allow-same-origin. Proxy/strip remote resources to avoid leaks. [6][7]
-
Remove or rewrite risky elements:
- Strip <script>, <style> if not needed, form tags, object/embed/iframe (or sandbox them).
- Remove inline event handlers (onclick/onerror) and CSS expressions; rewrite links to validate/normalize schemes and add rel="noopener noreferrer". [3][4]
-
Treat images and remote resources as privacy risk:
- Block or proxy remote images/resources to avoid IP/metadata leaks; require user action to load external resources. (Common email-client practice; implement on the server or via a proxy.)
-
Avoid DOM APIs that evaluate code or create new execution contexts from strings (eval, Function, setTimeout(string), element.setAttribute('on…')).
-
Don’t modify sanitized HTML afterwards — modifications can reintroduce risks. Test sanitization with known XSS vectors and fuzzing; monitor CVEs for your sanitizer. [3][4]
References:
- MDN: innerHTML / ShadowRoot, setHTMLUnsafe and security considerations. [1][7]
- MDN: Trusted Types API and CSP require-trusted-types-for. [2][5]
- DOMPurify docs / guidance (use allowlist, URI validation, keep updated). [3][4]
- MDN: iframe sandbox and CSP sandbox guidance for isolating untrusted content. [6][7]
If you want, I can produce a small example Web Component that implements Trusted Types + DOMPurify (or a sandboxed-iframe pattern) tailored to your stack — specify framework or plain Web Components.
🏁 Script executed:
# Find the `@Prop` definition for bodyHtml
rg "bodyHtml" src/components/email-viewer/email-viewer.tsx -B 3 -A 1 | head -30Repository: Lundalogik/lime-elements
Length of output: 442
🏁 Script executed:
# Check if there's a CSP or security policy in index.html or global config
cat src/index.html 2>/dev/null || echo "No index.html found"Repository: Lundalogik/lime-elements
Length of output: 1633
🏁 Script executed:
# Check the global config for security settings
cat src/global/config.ts | head -50Repository: Lundalogik/lime-elements
Length of output: 1340
🏁 Script executed:
# Look for how email-viewer is being used across the codebase
rg "email-viewer" --type tsx src/ -l | head -5Repository: Lundalogik/lime-elements
Length of output: 95
🏁 Script executed:
# Check if there's any documentation about the component's security model
cat src/components/email-viewer/readme.md 2>/dev/null || cat src/components/email-viewer/README.md 2>/dev/null || echo "No readme found"Repository: Lundalogik/lime-elements
Length of output: 83
Sanitize bodyHtml before rendering with innerHTML to prevent XSS.
The bodyHtml prop is rendered directly via innerHTML with no sanitization. Email HTML can contain malicious scripts (onclick handlers, script tags, javascript: URLs, etc.). Use a vetted sanitizer like DOMPurify configured to strip dangerous elements and attributes, or consider rendering email content in a sandboxed iframe. At minimum, add a Content-Security-Policy to prevent inline script execution.
🤖 Prompt for AI Agents
In `@src/components/email-viewer/email-viewer.tsx` around lines 128 - 133, The
renderBody method currently inserts untrusted HTML via innerHTML (this.bodyHtml)
in the EmailViewer component; sanitize the HTML before rendering by integrating
a vetted sanitizer (e.g., DOMPurify) or switching to a sandboxed iframe/CSP
approach. Update the renderBody implementation to run this.bodyHtml through the
sanitizer (or load it into a sandboxed iframe) and then assign the sanitized
output to innerHTML; ensure configuration strips scripts, event handlers, and
javascript: URLs and reference the renderBody method, the bodyHtml property, and
the innerHTML assignment when making the change.
✅ Confirmed as addressed by @Kiarokh
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will fix this in a follow up PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderabbitai can you review the last two commits? They handle sanitization
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Get the last two commits on this PR branch
git log --oneline -2
Length of output: 148
🏁 Script executed:
#!/bin/bash
# Get detailed diff of the last two commits to see sanitization changes
git log -2 --patch --no-merges
Length of output: 9368
🏁 Script executed:
#!/bin/bash
# Search for sanitization-related code in email-viewer files
rg -n "sanitiz|DOMPurify|purify" src/components/email-viewer/ -A 3 -B 3
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check if DOMPurify was added to dependencies
rg "dompurify|isomorphic-dompurify" package.json -A 1 -B 1
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look at the current state of renderBody method
cat src/components/email-viewer/email-viewer.tsx | sed -n '126,140p'
Length of output: 388
🏁 Script executed:
#!/bin/bash
# Check if unified/rehype dependencies were added to package.json
rg "unified|rehype" package.json -A 1 -B 1
Length of output: 389
🏁 Script executed:
#!/bin/bash
# Verify the full sanitize-email-html.ts file
cat src/util/sanitize-email-html.ts
Length of output: 7369
🏁 Script executed:
#!/bin/bash
# Check how bodyHtml flows from email-loader to email-viewer
rg "bodyHtml" src/components/file-viewer/email-loader.ts src/components/file-viewer/file-viewer.tsx -n -A 2 -B 2
Length of output: 850
@Kiarokh Excellent work! 🎉 The sanitization implementation is comprehensive and properly addresses the XSS concerns.
Key security features I observed:
-
Robust AST-based sanitization - Using
rehype-sanitizewith a custom schema is far more reliable than regex-based approaches. The AST traversal ensures you can't bypass the sanitizer with clever encoding or nested structures. -
Comprehensive threat blocking:
- Scripts, event handlers, and
javascript:URLs are blocked - Dangerous CSS properties (
behavior,expression,-moz-binding, etc.) are stripped - Only safe
data:image MIME types are allowed
- Scripts, event handlers, and
-
Privacy protection - Blocking remote images prevents tracking pixels and metadata leakage. Only embedded
data:URLs are permitted for images, which is the right call for email rendering. -
Visual fidelity preserved - The custom schema allows email-specific HTML elements (
table,font,center,styleattributes) while maintaining security. This balance is crucial for proper email display. -
Clean architecture - Sanitization happens in
email-loader.tsbefore the HTML reaches the viewer component, and the dynamic import keeps bundle size down.
The sanitization flow is correct: HTML is sanitized after CID resolution but before rendering, so email-viewer.tsx only receives safe, pre-sanitized content.
330a75b to
4d22b67
Compare
|
@coderabbitai I've implemented your suggested changes. Fresh full review please |
|
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/components/email-viewer/email-viewer.tsx`:
- Around line 175-187: The getHeaderValues function incorrectly splits 'to' and
'cc' by plain commas causing failures when display names contain commas; update
the implementation to either (A) parse the header string respecting RFC-2822
formatting by splitting only on commas that are not inside quoted strings or
angle-bracketed address parts (i.e., implement a small stateful parser or use a
utility to split on commas outside quotes/<>), or (B) change the Email interface
and related formatAddresses/formatAddress functions so that to and cc are typed
and returned as string[] (update callers to pass arrays instead of comma-joined
strings); locate getHeaderValues, formatAddress, formatAddresses, and the Email
interface to update the types/parsing accordingly and ensure filtering/trimming
is preserved.
In `@src/components/email-viewer/examples/email-viewer-plain-text.tsx`:
- Line 21: Update the example RFC 5322 date string in the
email-viewer-plain-text example so the weekday matches the calendar date: locate
the date prop/value "Mon, 20 Jan 2026 09:12:34 +0100" in
src/components/email-viewer/examples/email-viewer-plain-text.tsx (used in the
EmailViewer/plain-text example) and change the weekday token from "Mon" to "Tue"
so it reads "Tue, 20 Jan 2026 09:12:34 +0100".
In `@src/util/format-bytes.ts`:
- Around line 43-44: Clamp and validate the decimals input before using toFixed
to prevent RangeError: ensure decimals is a finite integer in range 0..100 (e.g.
let safeDecimals = Number.isFinite(decimals) ? Math.max(0, Math.min(100,
Math.floor(decimals))) : 0), then use safeDecimals when computing precision
(const precision = value < 10 ? Math.max(0, safeDecimals) : 0) and when calling
toFixed to compute rounded in the formatBytes utility (replace usage of raw
decimals with safeDecimals).
♻️ Duplicate comments (1)
src/components/email-viewer/email-viewer.tsx (1)
128-133: Security: Unsanitized HTML rendering deferred to follow-up PR.The
innerHTMLusage forbodyHtmlposes an XSS risk since email content can contain malicious scripts. This was previously flagged and acknowledged for a follow-up PR.
src/components/email-viewer/examples/email-viewer-plain-text.tsx
Outdated
Show resolved
Hide resolved
a4e8c26 to
ec4527f
Compare
| ) { | ||
| const html = (email.html || '').trim(); | ||
| if (html) { | ||
| parsedEmail.bodyHtml = replaceCidReferences(html, cidUrlById); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it safe to let user upload their own files here? is this sanitized?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no. we should fix this in a follow up PR. I didn't want to do this here as the PR was already too large to review.
plz see the issue for more info https://github.com/Lundalogik/crm-client/issues/633
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels pretty scary to not handle the security aspects before merging to main. Could we make a PR that adds the security parts and target it to this PR? Then we could review this PR but not merge it, and then review the security PR, merge that into this one, and then merge this one into main?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it here. 👍
ec4527f to
0b65a10
Compare
cfbf550 to
ff12437
Compare
fix https://github.com/Lundalogik/crm-client/issues/633
Summary by CodeRabbit
New Features
Examples
✏️ Tip: You can customize this high-level summary in your review settings.
Review:
Browsers tested:
(Check any that applies, it's ok to leave boxes unchecked if testing something didn't seem relevant.)
Windows:
Linux:
macOS:
Mobile: