{"record":{"id":"6d46d80c1037d303","repo":"larksuite/cli","slug":"multipart-part-s-missing-boundary","errorCode":null,"errorMessage":"multipart part %s missing boundary","messagePattern":"multipart part (.+?) missing boundary","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shortcuts/mail/draft/parse.go","lineNumber":167,"sourceCode":"\t} else {\n\t\tpart.MediaParams[\"charset\"] = \"UTF-8\"\n\t}\n\tif disp := headerValue(headers, \"Content-Disposition\"); disp != \"\" {\n\t\tdispType, params, err := mime.ParseMediaType(disp)\n\t\tif err == nil {\n\t\t\tpart.ContentDisposition = strings.ToLower(dispType)\n\t\t\tpart.ContentDispositionArg = lowerCaseKeys(params)\n\t\t}\n\t\t// On parse error, silently ignore the disposition. The original\n\t\t// header is preserved in part.Headers for serialization.\n\t}\n\tpart.ContentID = strings.Trim(strings.TrimSpace(headerValue(headers, \"Content-ID\")), \"<>\")\n\tpart.TransferEncoding = strings.ToLower(strings.TrimSpace(headerValue(headers, \"Content-Transfer-Encoding\")))\n\n\tif strings.HasPrefix(part.MediaType, \"multipart/\") {\n\t\tboundary := part.MediaParams[\"boundary\"]\n\t\tif boundary == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"multipart part %s missing boundary\", partID)\n\t\t}\n\t\tchildren, preamble, epilogue, err := parseMultipartChildren(body, boundary, partID, depth)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif len(children) == 0 {\n\t\t\t// Boundary declared but never found in the body. Reclassify as\n\t\t\t// text rather than returning an empty multipart with no children\n\t\t\t// (following mail-parser's approach per Postel's law).\n\t\t\tpart.MediaType = \"text/plain\"\n\t\t\tpart.MediaParams = map[string]string{\"charset\": \"UTF-8\"}\n\t\t\tpart.Body = body\n\t\t\tpart.EncodingProblem = true\n\t\t\treturn part, nil\n\t\t}\n\t\tpart.Children = children\n\t\tpart.Preamble = preamble\n\t\tpart.Epilogue = epilogue","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/shortcuts/mail/draft/parse.go#L149-L185","documentation":"During RFC822/MIME parsing of a mail draft, parsePart encountered a part whose media type starts with 'multipart/' but whose Content-Type parameters contain no 'boundary' parameter. Without a boundary the multipart body cannot be split into child parts, so parsing aborts. This guards against malformed MIME structures.","triggerScenarios":"Parsing a draft whose raw MIME body contains a 'multipart/*' Content-Type header (e.g. multipart/mixed, multipart/alternative) with no 'boundary=' parameter, typically produced when parsing parts returned by parseRootPart or recursively via parseMultipartChildren.","commonSituations":"Feeding hand-crafted or third-party-generated MIME into the draft parser; upstream tools that strip Content-Type parameters; truncated or rewritten emails that lost the boundary parameter.","solutions":["Inspect the raw MIME source and ensure every multipart/* Content-Type header includes a boundary parameter, e.g. Content-Type: multipart/mixed; boundary=\"===BOUNDARY===\"","If you generate MIME yourself, use a MIME library (Go: mime/multipart with Writer.SetBoundary) instead of concatenating strings.","If the MIME comes from a Lark draft, re-fetch the raw content and check it was not truncated or re-encoded in transit.","Wrap the parse call and treat this as malformed input: reject or repair the payload before parsing."],"exampleFix":"// before\nContent-Type: multipart/mixed;\n// after\nContent-Type: multipart/mixed; boundary=\"----=_Part_0_123456\"","handlingStrategy":"validation","validationCode":"func hasBoundary(ct string) bool {\n    _, params, _ := mime.ParseMediaType(ct)\n    if !strings.HasPrefix(strings.ToLower(ct), \"multipart/\") { return true }\n    return params[\"boundary\"] != \"\"\n}\n// check before parsing: if !hasBoundary(contentType) { repair or reject }","typeGuard":"func isParseableMultipart(ct string) bool {\n    if !strings.HasPrefix(strings.ToLower(ct), \"multipart/\") { return true }\n    _, params, err := mime.ParseMediaType(ct)\n    return err == nil && params[\"boundary\"] != \"\"\n}","tryCatchPattern":"part, err := parseRootPart(raw)\nif err != nil {\n    var e *errs.TypedError\n    if errors.As(err, &e) { log.Printf(\"mime parse failed: %s\", e.Message) }\n    return fmt.Errorf(\"malformed draft MIME: %w\", err)\n}","preventionTips":["Always generate multipart bodies with mime/multipart and an explicit SetBoundary.","Round-trip validate generated MIME by re-parsing it before sending.","Never strip Content-Type parameters when forwarding raw email bodies.","Check for truncation when MIME passes through size-limited channels."],"tags":["email","mime","parsing"],"backgroundTag":"malformed-mime-structure","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}