Skip to content

feat/CUS-11210-Added class to get the attachement file using subject filter - #363

Merged
akhil-testsigma merged 1 commit into
devfrom
feat/CUS-11210-Added-class-to-get-the-attachement-file-using-subject-filter
Mar 25, 2026
Merged

akhil-testsigma merged 1 commit into
devfrom
feat/CUS-11210-Added-class-to-get-the-attachement-file-using-subject-filter

Conversation

@akhil-testsigma

@akhil-testsigma akhil-testsigma commented Mar 25, 2026 •

Copy link
Copy Markdown
Contributor

Publish this addon as PUBLIC

Addon Name: GmailAPI
Jarvis Link: https://jarvis.testsigma.com/ui/tenants/2817/addons
Jira : https://testsigma.atlassian.net/browse/CUS-11210
Added class to get the attachement file using subject filter

Summary by CodeRabbit

  • New Features

    • Added Gmail attachment retrieval action supporting Android, iOS, and web platforms with subject filtering capabilities and multi-part message handling.
  • Chores

    • Updated project version to 1.1.7.
    • Upgraded multiple dependencies including Selenium Java, Appium Java Client, Lombok, Testsigma SDK, and Apache Commons Lang.

@akhil-testsigma akhil-testsigma changed the title feat/cus-11210-Added class to get the attachement file using subject filter feat/CUS-11210-Added class to get the attachement file using subject filter Mar 25, 2026
@coderabbitai

coderabbitai Bot commented Mar 25, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR updates Maven dependencies to newer versions (Selenium 4.37.0, Appium 9.4.0, TestSigma SDK 1.2.24_cloud, and others) and introduces three new Gmail attachment retrieval action classes for Android, iOS, and Web platforms. Each class connects to Gmail via IMAP, filters messages by subject, and downloads the latest received attachment.

Changes

Cohort / File(s) Summary
Maven Dependencies
gmail_api/pom.xml
Updated project version to 1.1.7; bumped TestSigma SDK to 1.2.24_cloud, Lombok to 1.18.30, Selenium to 4.37.0, Appium to 9.4.0, and Apache Commons Lang to 3.17.0.
Gmail Attachment Retrieval Actions
gmail_api/src/main/java/com/android/GetLatestReceivedAttachment.java, gmail_api/src/main/java/com/ios/GetLatestReceivedAttachment.java, gmail_api/src/main/java/com/web/GetLatestReceivedAttachment.java
Added three platform-specific action classes that connect to Gmail via IMAP/IMAPS, retrieve the latest inbox message, optionally filter by subject (supports contains, startsWith, endsWith, regex matching), validate and download the requested attachment by index, and store the file path in a runtime variable. All three classes implement identical logic with platform-specific base classes (AndroidAction, IOSAction, WebAction).

Sequence Diagram

sequenceDiagram
    participant Test as Test Framework
    participant Action as GetLatestReceivedAttachment
    participant IMAP as Gmail IMAP Server
    participant FS as File System
    
    Test->>Action: execute(username, passcode, subject-filter, attachment-index)
    activate Action
    
    Action->>IMAP: Connect via IMAPS
    activate IMAP
    IMAP-->>Action: Connection established
    
    Action->>IMAP: Authenticate (username, passcode)
    IMAP-->>Action: Authentication successful
    
    Action->>IMAP: Open INBOX (read-only)
    IMAP-->>Action: INBOX opened
    
    Action->>IMAP: Fetch latest message
    IMAP-->>Action: Message retrieved
    deactivate IMAP
    
    Action->>Action: Apply subject filter (if provided)
    alt Subject matches filter
        Action->>Action: Extract multipart attachments
        Action->>Action: Validate attachment index
        alt Attachment exists at index
            Action->>FS: Download attachment to temp directory
            FS-->>Action: File saved (path returned)
            Action->>Test: Store file path in runtime variable
            Test-->>Action: Variable stored
            Action-->>Test: Result.SUCCESS
        else Index out of range
            Action-->>Test: Result.FAILED (attachment index error)
        end
    else Subject does not match
        Action-->>Test: Result.FAILED (subject mismatch error)
    else Inbox empty or no attachments
        Action-->>Test: Result.FAILED (inbox/attachment error)
    end
    
    deactivate Action
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Suggested reviewers

  • Ganesh-Testsigma
  • vigneshtestsigma

Poem

🐰 Hopping through the inbox with joy and delight,
Gmail attachments retrieved with precision so tight,
Three platforms now dance in harmonious accord,
Android, iOS, and Web—your downloads adored! ✨📧

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes—adding a class to retrieve attachment files using subject filter functionality across Android, iOS, and Web platforms.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/CUS-11210-Added-class-to-get-the-attachement-file-using-subject-filter

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@gmail_api/src/main/java/com/android/GetLatestReceivedAttachment.java`:
- Around line 204-206: Sanitize the attachment filename returned by
target.getFileName() before building filePath: strip any path separators and
parent traversal sequences, reject or normalize absolute paths, allow only a
safe whitelist of characters (or replace others), and ensure the resolved path
is inside downloadDir (use path normalization and a check that
Paths.get(downloadDir).resolve(safeName).normalize().startsWith(downloadDir)).
Also prevent overwrites by generating a unique filename (append timestamp or
UUID) if the sanitized name already exists, then pass that safe path to
target.saveFile; apply these changes around the MimeBodyPart target / filePath
creation and saveFile call.
- Around line 176-180: The current attachment detection logic using
bodyPart.getFileName() is unreliable and non-recursive; update the traversal in
GetLatestReceivedAttachment (the loop over multipart and the attachments
List<MimeBodyPart>) to instead check part.getDisposition() == Part.ATTACHMENT
(or Part.INLINE if you want inline attachments) when classifying attachments and
implement a recursive helper that walks any Multipart/BodyPart boundaries
(handling nested Multipart instances such as multipart/related and
multipart/alternative) to collect all MimeBodyPart attachments into the
attachments list so attachment-index is stable across MIME structures.

In `@gmail_api/src/main/java/com/ios/GetLatestReceivedAttachment.java`:
- Around line 204-206: The code directly uses target.getFileName() to build
filePath and call target.saveFile(...), which allows path traversal or
overwrites; sanitize and validate the attachment name before saving by
extracting only the base name (strip any path separators, remove "..", and
reject absolute paths), normalize the result (e.g., remove unsafe characters),
then construct the destination path using a safe join (use downloadDir +
File.separator + sanitizedName or a safe path join API) and ensure uniqueness
(append a counter, timestamp, or use a temp file API) if the file exists; update
the code around MimeBodyPart target, target.getFileName(), filePath, and
target.saveFile(...) to perform these checks and use the sanitized/unique
filename for saving.
- Around line 82-84: The regex compilation in GetLatestReceivedAttachment
(subjectPattern = Pattern.compile(filterRaw, ...)) can throw
PatternSyntaxException; update the code to catch PatternSyntaxException around
the Pattern.compile call (or expand the surrounding IMAP try/catch to handle
it), and when caught set Result.FAILED and populate errorMessage with the
exception message (and ensure the step returns/aborts normally instead of
crashing); reference the subjectPattern assignment and the main IMAP try/catch
so the fix locates where to add the catch and error handling.
- Around line 176-180: The current attachment collection in
GetLatestReceivedAttachment.java incorrectly relies on BodyPart.getFileName()
and a flat loop, causing inline images to be treated as attachments and nested
multipart attachments to be missed; update the logic that builds the attachments
List<MimeBodyPart> to inspect bodyPart.getDisposition() and only treat parts
with Part.ATTACHMENT (and optionally Part.INLINE if you want inlined files) as
attachments, and implement a recursive traversal that, when a part's content is
itself a Multipart (use instanceof Multipart and multipart.getBodyPart(...)),
descends into child multiparts to collect nested attachments, adding only
MimeBodyPart instances with the correct disposition to the attachments list.

In `@gmail_api/src/main/java/com/web/GetLatestReceivedAttachment.java`:
- Around line 204-206: Sanitize the attachment filename before constructing
filePath: extract the raw name from MimeBodyPart target.getFileName(), strip any
path separators and sequences like "../", remove or replace unsafe characters,
and enforce a safe fallback (e.g., unique suffix or UUID) to avoid collisions;
then build the destination using a safe join (resolve/normalize) against
downloadDir and verify the resolved path startsWith the intended downloadDir
before calling target.saveFile. Reference: MimeBodyPart target,
target.getFileName(), downloadDir, filePath, target.saveFile(...), attachments
and requestedIndex.
- Around line 176-180: The current loop that adds to attachments by checking
BodyPart.getFileName() is too naive; update the logic (the code manipulating
attachments and iterating multipart) to recursively traverse nested Multipart
parts and treat a part as an attachment only when its disposition is
Part.ATTACHMENT (and not Part.INLINE) or when it has a filename and a non-inline
disposition; use MimeBodyPart APIs to inspect getDisposition(), getFileName(),
and Content-Type/Content-Disposition headers for edge cases, descending into
nested Multipart bodies when a body part’s content is itself a Multipart to
ensure attachments in multipart/related or deeper nests are collected correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 481ea8c9-ac34-456a-ac09-aa5730f6db94

📥 Commits

Reviewing files that changed from the base of the PR and between d6f8371 and e73db1a.

📒 Files selected for processing (4)
  • gmail_api/pom.xml
  • gmail_api/src/main/java/com/android/GetLatestReceivedAttachment.java
  • gmail_api/src/main/java/com/ios/GetLatestReceivedAttachment.java
  • gmail_api/src/main/java/com/web/GetLatestReceivedAttachment.java

Comment on lines +176 to +180
List<MimeBodyPart> attachments = new ArrayList<>();
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
if (bodyPart.getFileName() != null) {
attachments.add((MimeBodyPart) bodyPart);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "GetLatestReceivedAttachment.java" | head -20

Repository: testsigmahq/testsigma-addons

Length of output: 1907


🏁 Script executed:

# Let's search for the file in the repository
fd -t f "GetLatestReceivedAttachment.java"

Repository: testsigmahq/testsigma-addons

Length of output: 1907


🏁 Script executed:

# Check directory structure
git ls-files | grep -i "getlatestreceivedattachment" | head -10

Repository: testsigmahq/testsigma-addons

Length of output: 1907


🏁 Script executed:

# Broader search for Gmail API files
git ls-files | grep -i "gmail" | head -20

Repository: testsigmahq/testsigma-addons

Length of output: 1907


🌐 Web query:

BodyPart getFileName MIME attachment inline images Gmail

💡 Result:

In JavaMail, for a javax.mail.BodyPart (typically MimeBodyPart) from a Gmail message fetched via IMAP, bodyPart.getFileName retrieves the "filename" parameter from the Content-Disposition header, falling back to the "name" parameter from the Content-Type header if absent. It returns null if neither is present or if parsing fails due to malformed headers (e.g., line-wrapped parameters, RFC 2231 encoded filenames, or non-ASCII characters). Key distinctions: - Attachments typically have Content-Disposition: attachment; filename="example.jpg", so getFileName returns "example.jpg". - Inline images (embedded in HTML via Content-ID) often have Content-Disposition: inline without a filename parameter, so getFileName returns null. Use getContentID instead for identification. - Some messages lack filenames entirely (e.g., message/rfc822 parts), returning null. Common issues with Gmail/IMAP: - Malformed or line-wrapped headers (e.g., long filenames split across lines) cause parsing failures, returning null. Gmail's IMAP BODYSTRUCTURE may report incorrect sizes or structures. - RFC 2231 splitting (filename0, filename1) or encoded filenames may not decode properly without properties like mail.mime.decodeparameters=true or mail.mime.decodetext.strict=false. - Gmail-specific bugs (e.g., truncated Base64 body parts, incorrect BODYSTRUCTURE for attachments). To reliably get filenames: 1. Check part.getDisposition == Part.ATTACHMENT. 2. Use part.getFileName; if null, manually parse headers: String cd = part.getHeader("Content-Disposition"); // Parse for filename=... (handle quoting, encoding). 3. Fallback to Content-Type name parameter. 4. For inline images, they intentionally lack filenames; treat getFileName == null && Content-ID present as inline. Example code: if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition) || part.getFileName != null) { String filename = part.getFileName; if (filename == null) { // Fallback: parse Content-Disposition or Content-Type manually ParameterList params = new ContentDisposition(part.getDisposition).getParameterList; filename = params.get("filename"); if (filename == null) filename = MimeUtility.decodeText(params.get("name")); } // Use filename } Set Session properties for better parsing: props.setProperty("mail.mime.decodeparameters", "true"); props.setProperty("mail.mime.decodetext.strict", "false"); Sources confirm getFileName extracts from standard MIME parameters but can fail on edge cases common in Gmail emails.

Citations:


🌐 Web query:

email MIME multipart inline vs attachment Content-Disposition

💡 Result:

In email MIME multipart messages, the Content-Disposition header determines how a part is presented: "inline" for automatic display within the message body (e.g., embedded images), or "attachment" for download requiring user action. Key differences: - Content-Disposition: inline: Intended for immediate display when the message is viewed. Body parts are presented in order. Often used with Content-ID for HTML img src="cid:...". Example: Content-Disposition: inline; filename="image.jpg" (RFC 2183, sections 2.1, 3). - Content-Disposition: attachment: Requires user action to view/open/save. Suggests a filename for saving. Example: Content-Disposition: attachment; filename="document.pdf" (RFC 2183, sections 2.2, 3). In multipart structures: - multipart/related: For inline content related to the root body (e.g., HTML + images). - multipart/mixed: For attachments, or mixing inline and attachments (outer mixed containing related + attachments). If absent, defaults to inline behavior in many clients, but explicit use is recommended for clarity. This applies to any MIME body part.

Citations:


🌐 Web query:

Gmail MIME structure nested multiparts attachments

💡 Result:

Gmail uses standard MIME structures for emails with nested multiparts and attachments, following RFC 2045-2049 and RFC 5322. Emails are parsed into a hierarchical structure accessible via the Gmail API's payload field or as raw RFC 2822/base64url encoded data viewable via "Show Original". Key MIME types in Gmail: - text/plain: Plain text body. - text/html: HTML body. - multipart/alternative: Contains alternative versions (plain text and HTML). - multipart/related: Groups HTML body with embedded images (often nested inside other multiparts). - multipart/mixed: Top-level container for body + attachments (often wraps multipart/alternative or multipart/related + attachment parts). Typical nested structure for HTML email with embedded image and attachment: multipart/mixed (top-level) ├── multipart/related │ └── multipart/alternative │ ├── text/plain │ └── text/html (references embedded images via Content-ID) │ └── image/* (embedded images, Content-Disposition: inline) └── application/octet-stream (attachment, Content-Disposition: attachment; filename=*) Attachments appear as leaf parts with Content-Disposition: attachment and filename. They can be nested at any level (legal per MIME specs). In Gmail API format=full, this appears as nested MessagePart objects with mimeType, parts[], body.data (base64url-encoded), or body.attachmentId. For large attachments, use users.messages.attachments.get. To view raw MIME: In Gmail web, open email > ⋮ > Show original (RFC 2822 format with boundaries). No Gmail-specific deviations from standard MIME; structures match common clients (Outlook, Apple Mail). Parse recursively: traverse parts, decode body.data (base64url), check Content-Disposition for attachments.

Citations:


Check for Part.ATTACHMENT disposition instead of just getFileName() != null, and recursively handle nested multiparts.

BodyPart.getFileName() != null is insufficient for reliable attachment detection and misses nested multipart structures. The proper check requires part.getDisposition() == Part.ATTACHMENT. Additionally, your flat loop only examines top-level body parts; it skips attachments nested within multipart/related or multipart/alternative structures, which are common in Gmail layouts. This causes attachment-index to be unstable across different MIME structures. Consider recursively traversing all multipart boundaries and verifying Content-Disposition: attachment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gmail_api/src/main/java/com/android/GetLatestReceivedAttachment.java` around
lines 176 - 180, The current attachment detection logic using
bodyPart.getFileName() is unreliable and non-recursive; update the traversal in
GetLatestReceivedAttachment (the loop over multipart and the attachments
List<MimeBodyPart>) to instead check part.getDisposition() == Part.ATTACHMENT
(or Part.INLINE if you want inline attachments) when classifying attachments and
implement a recursive helper that walks any Multipart/BodyPart boundaries
(handling nested Multipart instances such as multipart/related and
multipart/alternative) to collect all MimeBodyPart attachments into the
attachments list so attachment-index is stable across MIME structures.

Comment on lines +204 to +206
MimeBodyPart target = attachments.get(requestedIndex - 1);
filePath = downloadDir + target.getFileName();
target.saveFile(new File(filePath));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Sanitize the attachment name before saving it locally.

target.getFileName() is sender-controlled. Concatenating it into downloadDir allows ../, absolute paths, or duplicate names to escape the temp folder or overwrite existing files on the runner.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gmail_api/src/main/java/com/android/GetLatestReceivedAttachment.java` around
lines 204 - 206, Sanitize the attachment filename returned by
target.getFileName() before building filePath: strip any path separators and
parent traversal sequences, reject or normalize absolute paths, allow only a
safe whitelist of characters (or replace others), and ensure the resolved path
is inside downloadDir (use path normalization and a check that
Paths.get(downloadDir).resolve(safeName).normalize().startsWith(downloadDir)).
Also prevent overwrites by generating a unique filename (append timestamp or
UUID) if the sanitized name already exists, then pass that safe path to
target.saveFile; apply these changes around the MimeBodyPart target / filePath
creation and saveFile call.

Comment on lines +82 to +84
if ("regex".equalsIgnoreCase(matchType) && !filterRaw.isEmpty()) {
subjectPattern = Pattern.compile(filterRaw, Pattern.CASE_INSENSITIVE);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

❓ Verification inconclusive

🏁 Script executed:

git ls-files | grep -E "GetLatestReceivedAttachment" | head -20

Repository: testsigmahq/testsigma-addons

Length of output: 1907


🏁 Script executed:

fd "GetLatestReceivedAttachment" --type f

Repository: testsigmahq/testsigma-addons

Length of output: 1907


Return a normal step failure when the regex is invalid.

Pattern.compile(...) may throw PatternSyntaxException if filterRaw is malformed. Verify whether this exception is caught by the main IMAP try/catch block or if it aborts the step without setting Result.FAILED and errorMessage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gmail_api/src/main/java/com/ios/GetLatestReceivedAttachment.java` around
lines 82 - 84, The regex compilation in GetLatestReceivedAttachment
(subjectPattern = Pattern.compile(filterRaw, ...)) can throw
PatternSyntaxException; update the code to catch PatternSyntaxException around
the Pattern.compile call (or expand the surrounding IMAP try/catch to handle
it), and when caught set Result.FAILED and populate errorMessage with the
exception message (and ensure the step returns/aborts normally instead of
crashing); reference the subjectPattern assignment and the main IMAP try/catch
so the fix locates where to add the catch and error handling.

Comment on lines +176 to +180
List<MimeBodyPart> attachments = new ArrayList<>();
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
if (bodyPart.getFileName() != null) {
attachments.add((MimeBodyPart) bodyPart);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "GetLatestReceivedAttachment.java" | head -20

Repository: testsigmahq/testsigma-addons

Length of output: 1907


🌐 Web query:

JavaMail BodyPart.getFileName() inline images attachments MIME

💡 Result:

In JavaMail, BodyPart.getFileName (implemented by MimeBodyPart) retrieves the filename associated with a MIME body part from email headers. It first checks the "filename" parameter in the Content-Disposition header. If not found, it falls back to the "name" parameter in the Content-Type header. If neither is present, it returns null. This behavior applies uniformly to both regular attachments (Content-Disposition: attachment) and inline images/embedded content (Content-Disposition: inline), as both are MIME body parts. For inline images: - They typically use Content-Disposition: inline with a Content-ID header for HTML referencing (e.g., ). - The filename may or may not be set; if present (e.g., via setFileName), getFileName returns it. - Examples show inline images often have filenames set during construction (e.g., imagePart.setFileName("image.jpg")), which getFileName retrieves. - Inline parts without filename parameters return null, just like attachments without filenames. To distinguish inline from attachments, use BodyPart.getDisposition: - Returns Part.ATTACHMENT, Part.INLINE, or null. - Example: if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition)) for attachments. Code example to process parts: Multipart multipart = (Multipart) message.getContent; for (int i = 0; i < multipart.getCount; i++) { BodyPart bodyPart = multipart.getBodyPart(i); String disposition = bodyPart.getDisposition; String fileName = bodyPart.getFileName; // Works for both inline and attachment if (Part.ATTACHMENT.equalsIgnoreCase(disposition)) { // Handle attachment } else if (Part.INLINE.equalsIgnoreCase(disposition)) { // Handle inline (e.g., image) } } Note: Filenames may be RFC 2047/2231 encoded; JavaMail handles decoding (controlled by properties like mail.mime.decodefilename). For split/encoded params, ensure proper JavaMail config (e.g., mail.mime.decodetext.strict=false).

Citations:


🌐 Web query:

Gmail MIME structure nested multiparts attachment handling

💡 Result:

Gmail handles MIME structures for nested multiparts and attachments according to standard RFC 2045-2049 and RFC 2822 specifications, preserving the full hierarchical structure without modification. Emails received or sent via Gmail maintain their original MIME tree, including nested multipart/mixed, multipart/alternative, multipart/related containers with attachments at any level. Key aspects: - MIME Structure: Top-level often multipart/mixed for body + attachments, with nested multipart/alternative (text/plain + text/html), multipart/related (embedded images), and leaf parts like application/octet-stream for attachments. Attachments can be nested deeply, including message/rfc822 (forwarded emails). - Viewing Raw Structure: Use "Show original" in Gmail web interface to see the complete RFC 2822 raw MIME source with boundaries, headers, and nested parts. - Gmail API Handling: messages.get with format=full returns parsed payload as nested MessagePart objects. Multipart parts have recursive 'parts' array; attachments identified by filename or Content-Disposition: attachment, with body.data (base64url) or body.attachmentId (fetch separately). Recursive traversal required to access all parts. - Sending: messages.send accepts raw RFC 2822 base64url-encoded MIME, fully supporting nested structures and attachments. - Parsing Examples: Common hierarchies include multipart/mixed > multipart/alternative > text/* for body with attachments; Gmail displays attachments correctly regardless of nesting depth. No Gmail-specific deviations from MIME standards; it processes and displays nested attachments faithfully, as confirmed by API docs and raw message inspection.

Citations:


🌐 Web query:

Content-Disposition inline vs attachment JavaMail BodyPart

💡 Result:

In JavaMail, the Content-Disposition header for a BodyPart (typically MimeBodyPart) determines how email clients handle the part: "inline" for display within the message body or "attachment" for download as a file. Key differences: - INLINE: The part is intended to be rendered inline, e.g., images embedded in HTML via cid: references. Use MimeBodyPart.setDisposition(MimeBodyPart.INLINE) or Part.INLINE. - ATTACHMENT: The part appears as a downloadable attachment. Use MimeBodyPart.setDisposition(MimeBodyPart.ATTACHMENT) or Part.ATTACHMENT. Often set automatically by attachFile without explicit INLINE. For inline images: 1. Create MimeBodyPart for image. 2. Set DataHandler (e.g., attachFile). 3. imagePart.setContentID(""); 4. imagePart.setDisposition(MimeBodyPart.INLINE); 5. Add to MimeMultipart("related"). 6. Reference in HTML body as . Example: MimeBodyPart imagePart = new MimeBodyPart; imagePart.attachFile("image.jpg"); imagePart.setContentID(""); imagePart.setDisposition(MimeBodyPart.INLINE); Body text/HTML parts default to inline unless explicitly set to ATTACHMENT. Setting INLINE on main body is acceptable but rarely changes behavior. This follows RFC 2183, as implemented in javax.mail.Part interface.

Citations:


Use getDisposition() to distinguish attachments from inline parts, and recursively traverse nested multiparts.

BodyPart.getFileName() != null alone is insufficient because:

  • It returns non-null for both Content-Disposition: attachment and Content-Disposition: inline parts (e.g., inline images with filenames)
  • The flat top-level loop skips attachments nested in child multiparts (multipart/alternative, multipart/related, etc.), which are common in Gmail MIME structures

This causes both false positives (inline images counted as attachments) and false negatives (missed nested attachments), making attachment-index unstable.

Use bodyPart.getDisposition() to check for Part.ATTACHMENT and implement recursive traversal for nested multiparts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gmail_api/src/main/java/com/ios/GetLatestReceivedAttachment.java` around
lines 176 - 180, The current attachment collection in
GetLatestReceivedAttachment.java incorrectly relies on BodyPart.getFileName()
and a flat loop, causing inline images to be treated as attachments and nested
multipart attachments to be missed; update the logic that builds the attachments
List<MimeBodyPart> to inspect bodyPart.getDisposition() and only treat parts
with Part.ATTACHMENT (and optionally Part.INLINE if you want inlined files) as
attachments, and implement a recursive traversal that, when a part's content is
itself a Multipart (use instanceof Multipart and multipart.getBodyPart(...)),
descends into child multiparts to collect nested attachments, adding only
MimeBodyPart instances with the correct disposition to the attachments list.

Comment on lines +204 to +206
MimeBodyPart target = attachments.get(requestedIndex - 1);
filePath = downloadDir + target.getFileName();
target.saveFile(new File(filePath));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Sanitize the attachment name before saving it locally.

target.getFileName() is sender-controlled. Concatenating it into downloadDir allows ../, absolute paths, or duplicate names to escape the temp folder or overwrite existing files on the runner.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gmail_api/src/main/java/com/ios/GetLatestReceivedAttachment.java` around
lines 204 - 206, The code directly uses target.getFileName() to build filePath
and call target.saveFile(...), which allows path traversal or overwrites;
sanitize and validate the attachment name before saving by extracting only the
base name (strip any path separators, remove "..", and reject absolute paths),
normalize the result (e.g., remove unsafe characters), then construct the
destination path using a safe join (use downloadDir + File.separator +
sanitizedName or a safe path join API) and ensure uniqueness (append a counter,
timestamp, or use a temp file API) if the file exists; update the code around
MimeBodyPart target, target.getFileName(), filePath, and target.saveFile(...) to
perform these checks and use the sanitized/unique filename for saving.

Comment on lines +176 to +180
List<MimeBodyPart> attachments = new ArrayList<>();
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
if (bodyPart.getFileName() != null) {
attachments.add((MimeBodyPart) bodyPart);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "GetLatestReceivedAttachment.java" | head -5

Repository: testsigmahq/testsigma-addons

Length of output: 1907


🌐 Web query:

javax.mail BodyPart getFileName() inline images embedded resources MIME

💡 Result:

In JavaMail, BodyPart.getFileName (inherited by MimeBodyPart) retrieves the filename for embedded resources like inline images in MIME emails as follows: - It first checks the "filename" parameter in the Content-Disposition header. - If absent, it falls back to the "name" parameter in the Content-Type header. - Returns null if neither is present. For inline images (embedded via multipart/related, referenced by Content-ID in HTML img src="cid:..."), the Content-Disposition is typically "inline" (or absent), but a filename parameter can still be specified. Examples from emails show inline images with headers like: Content-Type: image/gif; name="02629422.gif" Content-Disposition: inline; filename="02629422.gif" Thus, getFileName will return "02629422.gif" if present. If no filename/name parameter exists (possible per MIME spec), it returns null. To distinguish inline from attachments, check getDisposition == Part.INLINE, but use getFileName regardless for the suggested filename when saving/extracting. To extract inline images: - Iterate Multipart parts. - For image/* MIME types with Content-ID header (getHeader("Content-ID") != null), it's likely embedded. - Use part.getInputStream or part.saveFile with part.getFileName or a generated name if null. Code example: Multipart mp = (Multipart) message.getContent; for (int i = 0; i < mp.getCount; i++) { BodyPart part = mp.getBodyPart(i); if (part.isMimeType("image/*") && part.getHeader("Content-ID") != null) { String fname = part.getFileName; // may be null if (fname == null) fname = "embedded_image_" + i + ".png"; part.saveFile(new File("images/" + fname)); } } This handles both cases where filename is provided or absent.

Citations:


🌐 Web query:

javax.mail nested multipart handling attachment detection

💡 Result:

In javax.mail (now Jakarta Mail), handling nested Multipart structures and detecting attachments requires a recursive traversal of Parts, as emails can have deeply nested MIME structures like multipart/mixed containing multipart/alternative, or message/rfc822 parts embedding entire messages. Key approach: - Treat everything as a Part (Message and BodyPart implement Part). - Recursively process content: - If Multipart, iterate BodyParts and recurse. - If Message (e.g., contentType "message/rfc822" for EML attachments), cast to Message and process as top-level. - For IMAP-specific nested messages, check instanceof IMAPNestedMessage and recurse. - Attachments typically have disposition "attachment" or non-text contentType with filename; use part.getDisposition, part.isMimeType("application/"), part.getFileName != null. Example recursive method to find attachments: private void processPart(Part part) throws MessagingException, IOException { if (part.isMimeType("multipart/")) { Multipart mp = (Multipart) part.getContent; for (int i = 0; i < mp.getCount; i++) { processPart(mp.getBodyPart(i)); } } else if (part.getContent instanceof Message) { // Nested message (e.g., EML attachment) processPart((Part) part.getContent); } else if (part.getDisposition != null && (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition) || (part.getFileName != null && !part.isMimeType("text/*")))) { // Save attachment String fileName = part.getFileName; if (fileName != null) { fileName = MimeUtility.decodeText(fileName); try (InputStream is = part.getInputStream; FileOutputStream fos = new FileOutputStream(fileName)) { is.transferTo(fos); } } } } Call with: processPart(message); This handles arbitrary nesting per MIME spec and JavaMail patterns. For IMAP, add check: if (part.getContent instanceof IMAPNestedMessage) { processMessage((IMAPNestedMessage)part.getContent); } Official docs confirm Multipart holds BodyParts; recurse via getContent instanceof Multipart.

Citations:


🌐 Web query:

Gmail MIME structure nested multiparts how to properly detect attachments

💡 Result:

To properly detect attachments in Gmail's MIME structure, which supports nested multiparts per RFC 2045/2046/5322, traverse the entire MIME tree recursively. Attachments are identified by: 1. Content-Disposition: attachment (primary indicator, RFC 2183) 2. Non-text filename present 3. Non-inline image/media parts (e.g., not referenced by CID in HTML) Gmail follows standard MIME: messages are multipart/mixed (attachments + body), with body often multipart/alternative (text/plain + text/html), and html may contain multipart/related (inline images). ## Via Gmail API (Recommended for Gmail) Fetch message with format=full (parses MIME tree) or raw (full RFC2822). - Use payload.parts recursively. - Check part.filename (non-empty indicates attachment). - Or headers for Content-Disposition: attachment. - Large attachments have attachmentId; fetch via attachments.get. Example (JavaScript): function findAttachments(payload) { let attachments = []; function recurse(part) { if (part.filename) attachments.push(part); if (part.parts) part.parts.forEach(recurse); // Also check headers: part.headers.find(h => h.name === 'Content-Disposition')?.value.includes('attachment') } recurse(payload); return attachments; } Official docs confirm filename present only for attachments. For raw, parse with MIME library. ## Via Raw MIME (Python email stdlib) Use msg.walk - skips multipart containers, visits leaf parts recursively. import email from email import policy msg = email.message_from_bytes(raw_data, policy=policy.default) attachments = [] for part in msg.walk: if part.get_content_maintype == 'multipart': continue # Skip containers disp = str(part.get('Content-Disposition', '')).lower filename = part.get_filename if filename or 'attachment' in disp: # Save attachment data = part.get_payload(decode=True) attachments.append((filename or 'unknown', data)) This handles arbitrary nesting (e.g., S/MIME signed messages, message/rfc822). Gmail "Show Original" reveals full structure - attachments anywhere in tree with proper headers. Inline images (multipart/related) typically have Content-Disposition: inline or Content-ID referenced in HTML - exclude if 'inline' in disp and no filename. Sources confirm recursive walk necessary; top-level only misses nested attachments.

Citations:


Fix attachment detection to distinguish inline images and handle nested multiparts.

BodyPart.getFileName() != null alone is insufficient—inline images with filename parameters in Content-Type or Content-Disposition headers will be incorrectly counted as attachments. Use getDisposition() to filter for Part.ATTACHMENT and exclude Part.INLINE.

Additionally, a flat loop only processes top-level multipart parts. Gmail's standard structure (multipart/mixed → multipart/alternative → multipart/related for inline images) means attachments in nested structures are skipped, and inline images in multipart/related sections get miscounted. Implement recursive traversal to handle arbitrary MIME nesting and properly identify attachments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gmail_api/src/main/java/com/web/GetLatestReceivedAttachment.java` around
lines 176 - 180, The current loop that adds to attachments by checking
BodyPart.getFileName() is too naive; update the logic (the code manipulating
attachments and iterating multipart) to recursively traverse nested Multipart
parts and treat a part as an attachment only when its disposition is
Part.ATTACHMENT (and not Part.INLINE) or when it has a filename and a non-inline
disposition; use MimeBodyPart APIs to inspect getDisposition(), getFileName(),
and Content-Type/Content-Disposition headers for edge cases, descending into
nested Multipart bodies when a body part’s content is itself a Multipart to
ensure attachments in multipart/related or deeper nests are collected correctly.

Comment on lines +204 to +206
MimeBodyPart target = attachments.get(requestedIndex - 1);
filePath = downloadDir + target.getFileName();
target.saveFile(new File(filePath));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Sanitize the attachment name before saving it locally.

target.getFileName() is sender-controlled. Concatenating it into downloadDir allows ../, absolute paths, or duplicate names to escape the temp folder or overwrite existing files on the runner.

🛡️ Proposed fix
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
...
-    String downloadDir = System.getProperty("java.io.tmpdir") + "/testsigma_attachments/";
-    new File(downloadDir).mkdirs();
+    Path downloadDir = Files.createDirectories(
+        Paths.get(System.getProperty("java.io.tmpdir"), "testsigma_attachments"));
...
-      filePath = downloadDir + target.getFileName();
-      target.saveFile(new File(filePath));
+      String safeName = Paths.get(target.getFileName()).getFileName().toString();
+      Path targetPath = Files.createTempFile(downloadDir, "gmail_", "_" + safeName);
+      target.saveFile(targetPath.toFile());
+      filePath = targetPath.toString();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gmail_api/src/main/java/com/web/GetLatestReceivedAttachment.java` around
lines 204 - 206, Sanitize the attachment filename before constructing filePath:
extract the raw name from MimeBodyPart target.getFileName(), strip any path
separators and sequences like "../", remove or replace unsafe characters, and
enforce a safe fallback (e.g., unique suffix or UUID) to avoid collisions; then
build the destination using a safe join (resolve/normalize) against downloadDir
and verify the resolved path startsWith the intended downloadDir before calling
target.saveFile. Reference: MimeBodyPart target, target.getFileName(),
downloadDir, filePath, target.saveFile(...), attachments and requestedIndex.

@akhil-testsigma
akhil-testsigma merged commit bd29957 into dev Mar 25, 2026
2 checks passed
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.

2 participants