{"record":{"id":"c18339ff1ba3517c","repo":"chenhg5/cc-connect","slug":"googlechat-upload-create-media-part-w","errorCode":null,"errorMessage":"googlechat: upload: create media part: %w","messagePattern":"googlechat: upload: create media part: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"platform/googlechat/googlechat.go","lineNumber":468,"sourceCode":"}\n\n// uploadAttachment uploads raw bytes to the Chat media endpoint using a\n// multipart/related request and returns the attachmentDataRef resource name.\nfunc (p *Platform) uploadAttachment(ctx context.Context, space, filename, mimeType string, data []byte) (string, error) {\n\tbuf := bytes.NewBuffer(make([]byte, 0, 256+len(data)))\n\tmw := multipart.NewWriter(buf)\n\n\tmetaPart, err := mw.CreatePart(textproto.MIMEHeader{\"Content-Type\": {\"application/json; charset=UTF-8\"}})\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"googlechat: upload: create metadata part: %w\", err)\n\t}\n\tif err := json.NewEncoder(metaPart).Encode(map[string]string{\"filename\": filename}); err != nil {\n\t\treturn \"\", fmt.Errorf(\"googlechat: upload: encode metadata: %w\", err)\n\t}\n\n\tmediaPart, err := mw.CreatePart(textproto.MIMEHeader{\"Content-Type\": {mimeType}})\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"googlechat: upload: create media part: %w\", err)\n\t}\n\tif _, err := mediaPart.Write(data); err != nil {\n\t\treturn \"\", fmt.Errorf(\"googlechat: upload: write media: %w\", err)\n\t}\n\tif err := mw.Close(); err != nil {\n\t\treturn \"\", fmt.Errorf(\"googlechat: upload: finalize multipart body: %w\", err)\n\t}\n\n\tuploadURL := chatUploadBase + space + \"/attachments:upload?uploadType=multipart\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, uploadURL, buf)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"googlechat: upload: build request: %w\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"multipart/related; boundary=\"+mw.Boundary())\n\n\tresp, err := p.doRequest(req)\n\tif err != nil {\n\t\treturn \"\", err","sourceCodeStart":450,"sourceCodeEnd":486,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/googlechat/googlechat.go#L450-L486","documentation":"Returned by uploadAttachment when multipart.Writer.CreatePart fails while creating the media part for the uploaded file. CreatePart only fails on an invalid MIME header; since the header is just the provided mimeType string, this is practically unreachable with stock code unless mimeType contains illegal header characters. It surfaces via postAttachment from SendImage/SendFile.","triggerScenarios":"Calling SendImage()/SendFile() with a mimeType containing characters illegal in an HTTP header (e.g. newlines, control chars) can make CreatePart return an error.","commonSituations":"Config mistakes where mimeType is user-supplied or derived from an untrusted filename extension and contains whitespace/newlines.","solutions":["Sanitize or hardcode the mimeType (e.g. use mime.TypeByExtension or a fixed allowlist like image/png).","Strip newlines/control characters from any dynamically computed mimeType before passing it in.","Validate mimeType in config at startup if it comes from config.toml."],"exampleFix":"// before\nmime := cfg.MimeType // user-provided, may contain \"\\n\"\n// after\nmime = strings.Map(func(r rune) rune {\n    if r < 32 || r == 127 { return -1 }\n    return r\n}, cfg.MimeType)","handlingStrategy":"validation","validationCode":"// validate mimeType before calling SendFile/SendImage\nmime := cfg.MimeType\nif mime == \"\" || !strings.Contains(mime, \"/\") || strings.ContainsAny(mime, \" \\t\\r\\n\\x00\") {\n    return fmt.Errorf(\"invalid mimeType %q\", mime)\n}","typeGuard":"func isValidMimeType(m string) bool {\n    parts := strings.SplitN(m, \"/\", 2)\n    return len(parts) == 2 && parts[0] != \"\" && parts[1] != \"\" &&\n        !strings.ContainsAny(m, \" \\t\\r\\n\\x00\")\n}","tryCatchPattern":null,"preventionTips":["Derive mimeType from the file extension with mime.TypeByExtension instead of free-form config.","Strip control characters and whitespace from any dynamic mimeType.","Validate mimeType values in config.toml at startup.","Use a fixed allowlist of upload MIME types where possible."],"tags":["googlechat","upload","multipart","mime-type"],"backgroundTag":"multipart-write-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}