Skip to content

fix(mail): restore CID validation and stale PartID lookup lost in revert#230

Open
infeng wants to merge 3 commits intomainfrom
fix/restore-cid-validation-and-stale-partid
Open

fix(mail): restore CID validation and stale PartID lookup lost in revert#230
infeng wants to merge 3 commits intomainfrom
fix/restore-cid-validation-and-stale-partid

Conversation

@infeng
Copy link
Copy Markdown
Collaborator

@infeng infeng commented Apr 2, 2026

Summary

The revert of PR #81 (eda2b9c, #199) removed the entire auto-resolve feature but also rolled back two independent bugfixes. This PR restores only those bugfixes:

  • CID character validation: restore strings.ContainsAny(cid, " \t<>()") check in newInlinePart to reject invalid CID characters that produce malformed MIME output
  • Stale PartID lookup: replace findPart(snapshot.Body, snapshot.PrimaryHTMLPartID) with findPrimaryBodyPart(snapshot.Body, "text/html") in validateInlineCIDAfterApply and validateOrphanedInlineCIDAfterApply to avoid stale PartID after ops restructure the MIME tree

Test plan

  • go build ./shortcuts/mail/draft/... passes
  • go test ./shortcuts/mail/draft/... passes
  • Verify add_inline with CID containing spaces/angle brackets is correctly rejected
  • Verify inline CID validation still works after MIME tree restructuring ops (e.g. set_body after add_inline)

Summary by CodeRabbit

  • Bug Fixes

    • Inline content identifiers (CIDs) now reject additional invalid characters (spaces, tabs, angle brackets, parentheses) and trim surrounding markers.
    • CID validation now scans the appropriate HTML section so referenced and orphaned inline resources are detected correctly after body/HTML changes.
  • Tests

    • Added tests covering invalid CID rejection and CID validation across multipart/body updates.

…ert (#199)

The revert of PR #81 (eda2b9c) also removed two independent bugfixes:

1. CID character validation in newInlinePart — reject spaces, tabs,
   angle brackets, and parentheses to prevent malformed MIME output.
2. Stale PartID lookup in validateInlineCIDAfterApply and
   validateOrphanedInlineCIDAfterApply — use findPrimaryBodyPart by
   media type instead of findPart by PrimaryHTMLPartID, which can go
   stale when ops restructure the MIME tree.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

📝 Walkthrough

Walkthrough

Stricter inline CID validation was added and post-apply HTML-part lookup was changed: newInlinePart and replaceInline now reject CIDs containing spaces, tabs, < >, or () (after trimming), and validateInlineCIDAfterApply / validateOrphanedInlineCIDAfterApply locate HTML via findPrimaryBodyPart(snapshot.Body, "text/html") rather than using snapshot.PrimaryHTMLPartID.

Changes

Cohort / File(s) Summary
Inline CID validation & post-apply lookup
shortcuts/mail/draft/patch.go
Tightened CID validation for inline parts (reject spaces, tabs, angle brackets, parentheses after trimming); replaceInline updated similarly; post-apply validators now resolve HTML with findPrimaryBodyPart(snapshot.Body, "text/html") instead of snapshot.PrimaryHTMLPartID.
Tests for CID validation and body-set behavior
shortcuts/mail/draft/patch_test.go
Added tests: TestAddInlineRejectsInvalidCharactersInCID, TestValidateInlineCIDAfterSetBody, TestReplaceInlineRejectsInvalidCharactersInCID to cover invalid CID inputs and CID validation across add_inline/replace_inline + set_body flows.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • chanthuang

Poem

🐰 I checked each cid and shooed out space and brace,
No tabs, no brackets — kept a tidy trace.
HTML now found by body’s guiding art,
Inline links safe, each piece in its part. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.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
Title check ✅ Passed The title accurately describes the main change: restoring two bugfixes (CID validation and stale PartID lookup) that were lost in a revert, with clear mention of the specific aspects.
Description check ✅ Passed The PR description includes all required template sections: a clear summary explaining the motivation (restoring bugfixes lost in revert), specific changes listed with technical details, a comprehensive test plan with checked items, and status of verifications.

✏️ 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 fix/restore-cid-validation-and-stale-partid

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

@github-actions github-actions bot added domain/mail PR touches the mail domain size/M Single-domain feat or fix with limited business impact labels Apr 2, 2026
@infeng infeng requested a review from haidaodashushu April 2, 2026 13:03
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 2, 2026

Greptile Summary

This PR restores two specific bugfixes that were lost in the revert of the auto-resolve feature (PR #199): CID character validation to reject spaces, tabs, angle brackets, and parentheses in newInlinePart and replaceInline, and a switch from stale PrimaryHTMLPartID-based lookup to findPrimaryBodyPart (media-type-based) in the two post-apply CID validators. All four changes are accompanied by targeted tests that cover the previously broken scenarios.

Confidence Score: 5/5

Safe to merge — changes are tightly scoped restorations of previously shipped bugfixes with full test coverage.

Both changes (CID character validation and media-type-based HTML part lookup) are minimal, targeted, and well-tested. No new logic is introduced beyond what existed before the revert, and all edge cases are exercised by the three new test functions. No P1 or P0 issues found.

No files require special attention.

Important Files Changed

Filename Overview
shortcuts/mail/draft/patch.go Restores two independent bugfixes: CID character validation in replaceInline and newInlinePart, and replaces stale PartID-based HTML-part lookup with media-type-based findPrimaryBodyPart in both validate functions.
shortcuts/mail/draft/patch_test.go Adds three new tests: invalid-character CID rejection for add_inline and replace_inline, and a multi-step test verifying orphaned-CID validation survives set_body MIME tree restructuring.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Apply Patch Op] --> B{Op type?}
    B -->|add_inline| C[newInlinePart]
    B -->|replace_inline| D[replaceInline]
    C --> E[RejectCRLF check]
    D --> F[RejectCRLF check]
    E --> G["ContainsAny check\n(spaces, tabs, <>, ())"]
    F --> H["ContainsAny check\n(spaces, tabs, <>, ())"]
    G -->|invalid chars| I[Return error]
    H -->|invalid chars| I
    G -->|valid| J[Add part to MIME tree]
    H -->|valid| K[Update part in MIME tree]
    J --> L[validateInlineCIDAfterApply]
    K --> L
    L --> M["findPrimaryBodyPart(root, 'text/html')\n(by media type — not stale PartID)"]
    M --> N[Check all CID refs resolve]
    N --> O[validateOrphanedInlineCIDAfterApply]
    O --> P["findPrimaryBodyPart(root, 'text/html')"]
    P --> Q[Check no inline MIME part is orphaned]
    Q -->|orphan found| R[Return orphaned cids error]
    Q -->|ok| S[Success]
Loading

Reviews (3): Last reviewed commit: "fix(mail): add CID character validation ..." | Re-trigger Greptile

…ookup

- TestAddInlineRejectsInvalidCharactersInCID: verify spaces, tabs,
  embedded angle brackets, and parentheses in CID are rejected.
- TestValidateInlineCIDAfterSetBody: verify inline CID validation
  works correctly after set_body restructures the MIME tree (covers
  the findPrimaryBodyPart fix for stale PartID).
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
shortcuts/mail/draft/patch.go (1)

608-611: ⚠️ Potential issue | 🟡 Minor

Missing CID character validation in replaceInline.

newInlinePart now validates CID characters (lines 765-767), but replaceInline only checks for CRLF. When a new CID is provided to replace_inline, it bypasses the stricter validation that add_inline enforces. Both operations receive CID from the same untrusted JSON input.

Proposed fix to add consistent validation
 	finalCID := strings.Trim(strings.TrimSpace(cid), "<>")
 	if err := validate.RejectCRLF(finalCID, "inline cid"); err != nil {
 		return err
 	}
+	if strings.ContainsAny(finalCID, " \t<>()") {
+		return fmt.Errorf("inline cid %q contains invalid characters (spaces, tabs, angle brackets, or parentheses are not allowed)", finalCID)
+	}
 	if err := validate.RejectCRLF(fileName, "inline filename"); err != nil {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/mail/draft/patch.go` around lines 608 - 611, The replaceInline
function currently only trims and checks CRLF on finalCID but skips the
CID-character validation performed in newInlinePart; update replaceInline to
call the same CID character validation used in newInlinePart (after computing
finalCID and before using it) so new CID values go through identical
checks—i.e., add the same validate.Reject.../CID-character validation call (the
one used in newInlinePart) right after finalCID is computed (replaceInline) and
return the error if validation fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@shortcuts/mail/draft/patch.go`:
- Around line 608-611: The replaceInline function currently only trims and
checks CRLF on finalCID but skips the CID-character validation performed in
newInlinePart; update replaceInline to call the same CID character validation
used in newInlinePart (after computing finalCID and before using it) so new CID
values go through identical checks—i.e., add the same
validate.Reject.../CID-character validation call (the one used in newInlinePart)
right after finalCID is computed (replaceInline) and return the error if
validation fails.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0bba901a-2b9e-401d-9041-d6ab425a574d

📥 Commits

Reviewing files that changed from the base of the PR and between bf537f8 and 417e7ec.

📒 Files selected for processing (1)
  • shortcuts/mail/draft/patch.go

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 2, 2026

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@bcd7805b7435647e36e8ccf1287298bfa5dea471

🧩 Skill update

npx skills add larksuite/cli#fix/restore-cid-validation-and-stale-partid -y -g

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
shortcuts/mail/draft/patch_test.go (1)

648-650: Strengthen error message assertion for consistency with similar tests.

The test only verifies that an error occurred, but doesn't confirm it's the expected "orphaned cids" error. This is weaker than the analogous assertion at line 487 in TestApplySetBodyOrphanedInlineCIDIsRejected. If a different validation fails unexpectedly, this test would still pass.

💡 Suggested improvement
-	if err == nil {
-		t.Fatal("expected orphaned inline error after set_body dropped CID reference, got nil")
+	if err == nil || !strings.Contains(err.Error(), "orphaned cids") {
+		t.Fatalf("expected orphaned cid error, got: %v", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/mail/draft/patch_test.go` around lines 648 - 650, The test
currently only checks that err is non-nil; update it to assert the error message
is the expected orphaned-CID validation error (as done in
TestApplySetBodyOrphanedInlineCIDIsRejected). Locate the failing assertion
around err in TestApplySetBodySetBodyDroppedCID (or the current test function)
and replace the simple nil check with an assertion that err.Error() (or use
require/assert helpers) contains or equals the "orphaned cids" message used in
the other test so the test fails if a different validation error occurs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@shortcuts/mail/draft/patch_test.go`:
- Around line 648-650: The test currently only checks that err is non-nil;
update it to assert the error message is the expected orphaned-CID validation
error (as done in TestApplySetBodyOrphanedInlineCIDIsRejected). Locate the
failing assertion around err in TestApplySetBodySetBodyDroppedCID (or the
current test function) and replace the simple nil check with an assertion that
err.Error() (or use require/assert helpers) contains or equals the "orphaned
cids" message used in the other test so the test fails if a different validation
error occurs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0f9d47e2-82ec-4c91-9c0a-d96045218d4d

📥 Commits

Reviewing files that changed from the base of the PR and between 417e7ec and 224b045.

📒 Files selected for processing (1)
  • shortcuts/mail/draft/patch_test.go

Copy link
Copy Markdown
Collaborator

@haidaodashushu haidaodashushu left a comment

Choose a reason for hiding this comment

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

One blocking issue:

replace_inline still accepts CID values that this PR now rejects in add_inline, so the fix is only partial and behavior becomes inconsistent.

In shortcuts/mail/draft/patch.go, replaceInline still validates only CR/LF before writing part.ContentID, while the new invalid-character guard was added only in newInlinePart. That means a caller can still set CID: "my logo" or CID: "img(1)" through replace_inline and bypass the new restriction entirely.

The new tests also only cover add_inline; the existing replace_inline tests still check only CR/LF, so this gap would not be caught.

Suggested fix: extract CID validation into a shared helper and use it from both add_inline and replace_inline, then add a replace_inline test for the same invalid-character cases.

…en test assertions

Address CR feedback:
1. Add the same CID character validation (spaces, tabs, angle brackets,
   parentheses) to replaceInline, matching the check in newInlinePart.
   Previously replace_inline could bypass the restriction.
2. Strengthen orphaned CID test assertion to check for specific
   "orphaned cids" error message, not just non-nil error.
3. Add TestReplaceInlineRejectsInvalidCharactersInCID to cover the
   new validation in replace_inline.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
shortcuts/mail/draft/patch.go (2)

909-914: ⚠️ Potential issue | 🟠 Major

Orphan check should only evaluate inline MIME parts, not every Content-ID part.

Line 909-Line 914 currently marks any ContentID part as orphan-candidate, which can falsely flag primary HTML/body parts that carry Content-ID but are not inline attachments.

Suggested fix
 	for _, part := range flattenParts(snapshot.Body) {
-		if part == nil || part.ContentID == "" {
+		if part == nil || !strings.EqualFold(part.ContentDisposition, "inline") || strings.TrimSpace(part.ContentID) == "" {
 			continue
 		}
-		if !refSet[strings.ToLower(part.ContentID)] {
-			orphaned = append(orphaned, part.ContentID)
+		cid := strings.Trim(part.ContentID, "<>")
+		if !refSet[strings.ToLower(cid)] {
+			orphaned = append(orphaned, cid)
 		}
 	}

Based on learnings: In shortcuts/mail/draft/patch.go, orphan-removal candidates must be constrained to Content-Disposition: inline plus non-empty Content-ID; treating any ContentID part as inline is a regression.

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

In `@shortcuts/mail/draft/patch.go` around lines 909 - 914, The orphan detection
currently treats any MIME part with a non-empty ContentID as an orphan
candidate; change the check in the loop over flattenParts(snapshot.Body) so it
only considers parts that have a non-empty ContentID AND have
Content-Disposition indicating inline (e.g., ContentDisposition == "inline",
case-insensitive or via provided enum/helper) before comparing against refSet
and appending to orphaned; update the condition around part.ContentID to also
verify part.ContentDisposition is inline to avoid flagging primary HTML/body
parts.

880-888: ⚠️ Potential issue | 🟠 Major

Restrict CID resolution to inline parts only in post-apply validation.

validateInlineCIDAfterApply says references must resolve to actual inline MIME parts, but Line 880-Line 885 accepts any part with ContentID. That can incorrectly pass when a non-inline part shares a CID.

Suggested fix
-	for _, part := range flattenParts(snapshot.Body) {
-		if part == nil || part.ContentID == "" {
+	for _, part := range flattenParts(snapshot.Body) {
+		if part == nil || !strings.EqualFold(part.ContentDisposition, "inline") || strings.TrimSpace(part.ContentID) == "" {
 			continue
 		}
-		cids[strings.ToLower(part.ContentID)] = true
+		cids[strings.ToLower(strings.Trim(part.ContentID, "<>"))] = true
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/mail/draft/patch.go` around lines 880 - 888, The current CID
collection loop accepts any part with a ContentID, which can include non-inline
attachments; update the loop in validateInlineCIDAfterApply to only register
CIDs from inline MIME parts by checking the part's inline/disposition flag
(e.g., only add when part.Disposition == "inline" or part.IsInline() /
part.Inline == true, using whatever inline indicator exists on the Part type)
before setting cids[strings.ToLower(part.ContentID)] = true so non-inline
attachments do not satisfy HTML cid references.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@shortcuts/mail/draft/patch.go`:
- Around line 909-914: The orphan detection currently treats any MIME part with
a non-empty ContentID as an orphan candidate; change the check in the loop over
flattenParts(snapshot.Body) so it only considers parts that have a non-empty
ContentID AND have Content-Disposition indicating inline (e.g.,
ContentDisposition == "inline", case-insensitive or via provided enum/helper)
before comparing against refSet and appending to orphaned; update the condition
around part.ContentID to also verify part.ContentDisposition is inline to avoid
flagging primary HTML/body parts.
- Around line 880-888: The current CID collection loop accepts any part with a
ContentID, which can include non-inline attachments; update the loop in
validateInlineCIDAfterApply to only register CIDs from inline MIME parts by
checking the part's inline/disposition flag (e.g., only add when
part.Disposition == "inline" or part.IsInline() / part.Inline == true, using
whatever inline indicator exists on the Part type) before setting
cids[strings.ToLower(part.ContentID)] = true so non-inline attachments do not
satisfy HTML cid references.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a49b9db8-51af-412b-8112-a03aefb629bb

📥 Commits

Reviewing files that changed from the base of the PR and between 224b045 and bcd7805.

📒 Files selected for processing (2)
  • shortcuts/mail/draft/patch.go
  • shortcuts/mail/draft/patch_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • shortcuts/mail/draft/patch_test.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/mail PR touches the mail domain size/M Single-domain feat or fix with limited business impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants