Files
wehub-resource-sync bf9395e022
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:22:54 +08:00

190 lines
5.6 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
//nolint:forbidigo // intermediate draft calendar patch errors; mail command layer wraps into typed ValidationError.
package draft
import (
"fmt"
"strings"
)
const calendarMediaType = "text/calendar"
// applyCalendarSet installs or replaces the text/calendar MIME part in the
// snapshot. The caller is expected to have pre-built icsData using the
// snapshot's From/To/Cc addresses.
func applyCalendarSet(snapshot *DraftSnapshot, icsData []byte) error {
if len(icsData) == 0 {
return fmt.Errorf("set_calendar: ICS data is empty (shortcut layer must pre-build it)")
}
setCalendarPart(snapshot, icsData)
return nil
}
// applyCalendarRemove strips the text/calendar part from the snapshot.
// No-op if no calendar part exists.
func applyCalendarRemove(snapshot *DraftSnapshot) error {
removeCalendarPart(snapshot)
return nil
}
// setCalendarPart places exactly one text/calendar part inside
// multipart/alternative, matching the Feishu client behavior. Any existing
// text/calendar parts elsewhere in the tree are removed first.
func setCalendarPart(snapshot *DraftSnapshot, icsData []byte) {
newPart := &Part{
MediaType: calendarMediaType,
MediaParams: map[string]string{"charset": "UTF-8", "method": "REQUEST"},
Body: icsData,
Dirty: true,
}
if snapshot.Body == nil {
snapshot.Body = newPart
return
}
// Remove all existing text/calendar parts from everywhere in the tree.
if strings.EqualFold(snapshot.Body.MediaType, calendarMediaType) {
snapshot.Body = newPart
return
}
removeAllPartsByMediaType(snapshot.Body, calendarMediaType)
// Place inside the existing multipart/alternative.
if alt := FindPartByMediaType(snapshot.Body, "multipart/alternative"); alt != nil {
alt.Children = append(alt.Children, newPart)
alt.Dirty = true
return
}
// No multipart/alternative exists. If the body is a single leaf,
// wrap it in multipart/alternative together with the calendar.
if !snapshot.Body.IsMultipart() {
original := *snapshot.Body
// Reset all header-carrying fields so the serializer constructs a fresh
// Content-Type from MediaType instead of reusing the stale leaf headers.
snapshot.Body.Headers = nil
snapshot.Body.MediaType = "multipart/alternative"
snapshot.Body.MediaParams = nil
snapshot.Body.ContentDisposition = ""
snapshot.Body.ContentDispositionArg = nil
snapshot.Body.ContentID = ""
snapshot.Body.PartID = ""
snapshot.Body.Body = nil
snapshot.Body.TransferEncoding = ""
snapshot.Body.RawEntity = nil
snapshot.Body.Preamble = nil
snapshot.Body.Epilogue = nil
snapshot.Body.EncodingProblem = false
snapshot.Body.Children = []*Part{&original, newPart}
snapshot.Body.Dirty = true
return
}
// Multipart body without an alternative sub-part (e.g. multipart/mixed
// with a text/html child). Find the first text/* child and wrap it in
// a new multipart/alternative that also contains the calendar.
for i, child := range snapshot.Body.Children {
if child != nil && strings.HasPrefix(strings.ToLower(child.MediaType), "text/") {
alt := &Part{
MediaType: "multipart/alternative",
Children: []*Part{child, newPart},
Dirty: true,
}
snapshot.Body.Children[i] = alt
snapshot.Body.Dirty = true
return
}
}
// Fallback: append to the root multipart container.
snapshot.Body.Children = append(snapshot.Body.Children, newPart)
snapshot.Body.Dirty = true
}
func removeCalendarPart(snapshot *DraftSnapshot) {
if snapshot.Body == nil {
return
}
if strings.EqualFold(snapshot.Body.MediaType, calendarMediaType) {
snapshot.Body = nil
return
}
removeAllPartsByMediaType(snapshot.Body, calendarMediaType)
}
// FindPartByMediaType walks the MIME tree and returns the first part with
// the given media type, or nil when not found.
func FindPartByMediaType(root *Part, mediaType string) *Part {
if root == nil {
return nil
}
if strings.EqualFold(root.MediaType, mediaType) {
return root
}
for _, child := range root.Children {
if found := FindPartByMediaType(child, mediaType); found != nil {
return found
}
}
return nil
}
// findAllPartsByMediaType walks the MIME tree and returns every part with
// the given media type. Used in tests to assert tree contents.
func findAllPartsByMediaType(root *Part, mediaType string) []*Part {
if root == nil {
return nil
}
var result []*Part
if strings.EqualFold(root.MediaType, mediaType) {
result = append(result, root)
}
for _, child := range root.Children {
result = append(result, findAllPartsByMediaType(child, mediaType)...)
}
return result
}
// removePartByMediaType removes the first part with the given media type from
// the MIME tree. The parent is marked dirty when a removal happens.
func removePartByMediaType(root *Part, mediaType string) {
if root == nil {
return
}
for i, child := range root.Children {
if child != nil && strings.EqualFold(child.MediaType, mediaType) {
root.Children = append(root.Children[:i], root.Children[i+1:]...)
root.Dirty = true
return
}
removePartByMediaType(child, mediaType)
}
}
// removeAllPartsByMediaType removes every part with the given media type from
// the MIME tree, at all nesting levels.
func removeAllPartsByMediaType(root *Part, mediaType string) {
if root == nil {
return
}
var kept []*Part
removed := false
for _, child := range root.Children {
if child != nil && strings.EqualFold(child.MediaType, mediaType) {
removed = true
continue
}
kept = append(kept, child)
}
if removed {
root.Children = kept
root.Dirty = true
}
for _, child := range root.Children {
removeAllPartsByMediaType(child, mediaType)
}
}