1 // SGBlog -- Git-backed CGI/UCSPI blogging/phlogging/gemlogging engine
2 // Copyright (C) 2020-2024 Sergey Matveev <stargrave@stargrave.org>
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, version 3 of the License.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU Affero General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
26 "mime/quotedprintable"
33 CTE = "Content-Transfer-Encoding"
37 func processTP(ct, cte string, body io.Reader) (io.Reader, error) {
38 _, params, err := mime.ParseMediaType(ct)
42 if c := strings.ToLower(params["charset"]); !(c == "" ||
46 return nil, errors.New("only utf-8/iso-8859-1/us-ascii charsets supported")
49 case "quoted-printable":
50 return quotedprintable.NewReader(body), nil
52 return base64.NewDecoder(base64.StdEncoding, body), nil
57 func parseEmail(msg *mail.Message) (subj string, body io.Reader, err error) {
58 subj = msg.Header.Get("Subject")
60 err = errors.New("no Subject")
63 subj, err = new(mime.WordDecoder).DecodeHeader(subj)
67 ct := msg.Header.Get(CT)
71 if strings.HasPrefix(ct, TP) {
72 body, err = processTP(ct, msg.Header.Get(CTE), msg.Body)
75 ct, params, err := mime.ParseMediaType(ct)
77 err = fmt.Errorf("can not ParseMediaType: %w", err)
80 if ct != "multipart/signed" {
81 err = errors.New("only text/plain and multipart/signed+text/plain Content-Type supported")
84 boundary := params["boundary"]
85 if len(boundary) == 0 {
86 err = errors.New("no boundary string")
89 data, err := io.ReadAll(msg.Body)
93 boundaryIdx := bytes.Index(data, []byte("--"+boundary))
94 if boundaryIdx == -1 {
95 err = errors.New("no boundary found")
98 mpr := multipart.NewReader(bytes.NewReader(data[boundaryIdx:]), boundary)
99 var part *multipart.Part
101 part, err = mpr.NextPart()
108 ct = part.Header.Get(CT)
109 if strings.HasPrefix(ct, TP) {
110 body, err = processTP(ct, part.Header.Get(CTE), part)
113 if strings.HasPrefix(ct, "multipart/mixed") {
114 _, params, err = mime.ParseMediaType(ct)
116 err = fmt.Errorf("can not ParseMediaType: %w", err)
119 boundary = params["boundary"]
120 if len(boundary) == 0 {
121 err = errors.New("no boundary string")
124 mpr := multipart.NewReader(part, boundary)
126 part, err = mpr.NextPart()
133 ct = part.Header.Get(CT)
134 if strings.HasPrefix(ct, TP) {
135 body, err = processTP(ct, part.Header.Get(CTE), part)
141 err = errors.New("no text/plain part found")