// Copyright (c) 2026 Lark Technologies Pte. Ltd. // SPDX-License-Identifier: MIT //nolint:forbidigo // intermediate draft large-attachment parser errors; mail command layer wraps into typed ValidationError. package draft import ( "bytes" "encoding/base64" "encoding/json" "fmt" "net/url" "regexp" "strconv" "strings" xhtml "golang.org/x/net/html" ) // largeAttHeaderEntry is a union of the CLI and server JSON formats for // entries in the large attachment header. type largeAttHeaderEntry struct { ID string `json:"id,omitempty"` FileKey string `json:"file_key,omitempty"` FileName string `json:"file_name,omitempty"` FileSize int64 `json:"file_size,omitempty"` } func (e largeAttHeaderEntry) token() string { if e.ID != "" { return e.ID } return e.FileKey } // IsLargeAttachmentHeader returns true if the header name matches either // the CLI-written or server-returned large attachment header. func IsLargeAttachmentHeader(name string) bool { return strings.EqualFold(name, LargeAttachmentIDsHeader) || strings.EqualFold(name, ServerLargeAttachmentHeader) } // decodeLargeAttachmentHeader decodes the base64 value and returns entries. func decodeLargeAttachmentHeader(value string) ([]largeAttHeaderEntry, error) { decoded, err := base64.StdEncoding.DecodeString(strings.TrimSpace(value)) if err != nil { return nil, err } var items []largeAttHeaderEntry if err := json.Unmarshal(decoded, &items); err != nil { return nil, err } return items, nil } // parseLargeAttachmentTokens returns the ordered list of large attachment // tokens from either X-Lms-Large-Attachment-Ids (CLI format) or // X-Lark-Large-Attachment (server format). Returns nil when neither // header is present or the value is malformed. func parseLargeAttachmentTokens(headers []Header) []string { for _, h := range headers { if !IsLargeAttachmentHeader(h.Name) { continue } items, err := decodeLargeAttachmentHeader(h.Value) if err != nil { return nil } out := make([]string, 0, len(items)) for _, it := range items { if tok := it.token(); tok != "" { out = append(out, tok) } } return out } return nil } // ParseLargeAttachmentSummariesFromHeader extracts full metadata from the // large attachment header. Returns non-nil only when the server-format // header (X-Lark-Large-Attachment) is found, since it carries file_name // and file_size that the CLI-format header lacks. func ParseLargeAttachmentSummariesFromHeader(headers []Header) []LargeAttachmentSummary { for _, h := range headers { if !strings.EqualFold(h.Name, ServerLargeAttachmentHeader) { continue } items, err := decodeLargeAttachmentHeader(h.Value) if err != nil { return nil } out := make([]LargeAttachmentSummary, 0, len(items)) for _, it := range items { tok := it.token() if tok == "" { continue } out = append(out, LargeAttachmentSummary{ Token: tok, FileName: it.FileName, SizeBytes: it.FileSize, }) } return out } return nil } // ParseLargeAttachmentItemsFromHTML walks the HTML body looking for large // attachment card items (