processFilePart handles MessagePartTypeFile: reads from disk, detects MIME, routes to text-inline or binary-inline as appropriate.
(part MessagePart)
| 75 | // processFilePart handles MessagePartTypeFile: reads from disk, detects MIME, |
| 76 | // routes to text-inline or binary-inline as appropriate. |
| 77 | func processFilePart(part MessagePart) (Document, *ImageResizeResult, error) { |
| 78 | if part.File == nil { |
| 79 | return Document{}, nil, errors.New("ProcessAttachment: file part has nil File field") |
| 80 | } |
| 81 | absPath := part.File.Path |
| 82 | name := filepath.Base(absPath) |
| 83 | |
| 84 | fi, err := os.Stat(absPath) |
| 85 | if err != nil { |
| 86 | return Document{}, nil, fmt.Errorf("ProcessAttachment: cannot stat %q: %w", absPath, err) |
| 87 | } |
| 88 | if !fi.Mode().IsRegular() { |
| 89 | return Document{}, nil, fmt.Errorf("ProcessAttachment: %q is not a regular file", absPath) |
| 90 | } |
| 91 | |
| 92 | mimeType := DetectMimeType(absPath) |
| 93 | |
| 94 | // Route by MIME type. Note: MIME-type check must precede IsTextFile because |
| 95 | // some binary formats (e.g. PDF) may pass the text heuristic when the file |
| 96 | // content happens to be printable ASCII. |
| 97 | switch { |
| 98 | case IsImageMimeType(mimeType): |
| 99 | if fi.Size() > MaxInlineBinarySize { |
| 100 | return Document{}, nil, fmt.Errorf("ProcessAttachment: image file %q too large to inline (%d bytes, max %d)", absPath, fi.Size(), MaxInlineBinarySize) |
| 101 | } |
| 102 | data, err := os.ReadFile(absPath) |
| 103 | if err != nil { |
| 104 | return Document{}, nil, fmt.Errorf("ProcessAttachment: read image %q: %w", absPath, err) |
| 105 | } |
| 106 | return transcodeImageWithMeta(name, data, mimeType) |
| 107 | |
| 108 | case mimeType == "application/pdf" || (IsSupportedMimeType(mimeType) && !IsTextFile(absPath)): |
| 109 | // PDF and other supported binary types — read verbatim. |
| 110 | // The !IsTextFile guard ensures that binary formats whose extension |
| 111 | // is unknown but content is ASCII-printable are not incorrectly inlined. |
| 112 | if fi.Size() > MaxInlineBinarySize { |
| 113 | return Document{}, nil, fmt.Errorf("ProcessAttachment: binary file %q too large to inline (%d bytes, max %d)", absPath, fi.Size(), MaxInlineBinarySize) |
| 114 | } |
| 115 | data, err := os.ReadFile(absPath) |
| 116 | if err != nil { |
| 117 | return Document{}, nil, fmt.Errorf("ProcessAttachment: read binary file %q: %w", absPath, err) |
| 118 | } |
| 119 | return Document{ |
| 120 | Name: name, |
| 121 | MimeType: mimeType, |
| 122 | Size: int64(len(data)), |
| 123 | Source: DocumentSource{InlineData: data}, |
| 124 | }, nil, nil |
| 125 | |
| 126 | case IsTextFile(absPath): |
| 127 | if fi.Size() > MaxInlineFileSize { |
| 128 | return Document{}, nil, fmt.Errorf("ProcessAttachment: text file %q too large to inline (%d bytes, max %d)", absPath, fi.Size(), MaxInlineFileSize) |
| 129 | } |
| 130 | content, err := ReadFileForInline(absPath) |
| 131 | if err != nil { |
| 132 | return Document{}, nil, fmt.Errorf("ProcessAttachment: read text file %q: %w", absPath, err) |
| 133 | } |
| 134 | return Document{ |
no test coverage detected