walkParts returns the first text/plain, the first text/html rendered to text, and the first text/html decoded as-is (htmlRaw) found anywhere in the (possibly nested multipart) body.
(contentType, cte string, body io.Reader, depth int)
| 126 | // and the first text/html decoded as-is (htmlRaw) found anywhere in the |
| 127 | // (possibly nested multipart) body. |
| 128 | func walkParts(contentType, cte string, body io.Reader, depth int) (plain, htmlText, htmlRaw string) { |
| 129 | mediaType, params, err := mime.ParseMediaType(contentType) |
| 130 | if err != nil { |
| 131 | // No/!invalid Content-Type → treat as text/plain. |
| 132 | return decode(body, cte), "", "" |
| 133 | } |
| 134 | switch { |
| 135 | case strings.HasPrefix(mediaType, "multipart/"): |
| 136 | if depth >= maxMIMEDepth { |
| 137 | return "", "", "" // bail on pathological nesting (see maxMIMEDepth) |
| 138 | } |
| 139 | boundary := params["boundary"] |
| 140 | if boundary == "" { |
| 141 | return "", "", "" |
| 142 | } |
| 143 | mr := multipart.NewReader(body, boundary) |
| 144 | for { |
| 145 | part, err := mr.NextPart() |
| 146 | if err != nil { |
| 147 | break |
| 148 | } |
| 149 | // Skip parts the sender flagged as attachments — they're files |
| 150 | // (fetched via the attachment endpoint), not the displayable body. |
| 151 | // Without this an attached .html file would surface as parsed.html. |
| 152 | if disp, _, derr := mime.ParseMediaType(part.Header.Get("Content-Disposition")); derr == nil && disp == "attachment" { |
| 153 | part.Close() |
| 154 | continue |
| 155 | } |
| 156 | p, h, hr := walkParts(part.Header.Get("Content-Type"), part.Header.Get("Content-Transfer-Encoding"), part, depth+1) |
| 157 | if plain == "" && p != "" { |
| 158 | plain = p |
| 159 | } |
| 160 | if htmlText == "" && h != "" { |
| 161 | htmlText = h |
| 162 | } |
| 163 | if htmlRaw == "" && hr != "" { |
| 164 | htmlRaw = hr |
| 165 | } |
| 166 | part.Close() |
| 167 | // Stop once both representations are in hand. Unlike the old |
| 168 | // text-only walk we can't stop at text/plain alone — the text/html |
| 169 | // sibling (typically later in a multipart/alternative) is the |
| 170 | // display body we still need. |
| 171 | if plain != "" && htmlRaw != "" { |
| 172 | break |
| 173 | } |
| 174 | } |
| 175 | return plain, htmlText, htmlRaw |
| 176 | case mediaType == "text/plain": |
| 177 | return decode(body, cte), "", "" |
| 178 | case mediaType == "text/html": |
| 179 | decoded := decode(body, cte) |
| 180 | return "", htmlToText(decoded), decoded |
| 181 | default: |
| 182 | return "", "", "" |
| 183 | } |
| 184 | } |
| 185 |
no test coverage detected