2 SGBlog -- Git-based CGI blogging engine
3 Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Affero General Public License as
7 published by the Free Software Foundation, version 3 of the License.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU Affero General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
28 "mime/quotedprintable"
35 CTE = "Content-Transfer-Encoding"
39 func processTP(ct, cte string, body io.Reader) (io.Reader, error) {
40 _, params, err := mime.ParseMediaType(ct)
44 if c := params["charset"]; !(c == "" || c == "utf-8" || c == "iso-8859-1" || c == "us-ascii") {
45 return nil, errors.New("only utf-8/iso-8859-1/us-ascii charsets supported")
48 case "quoted-printable":
49 return quotedprintable.NewReader(body), nil
51 return base64.NewDecoder(base64.StdEncoding, body), nil
56 func parseEmail(msg *mail.Message) (subj string, body io.Reader, err error) {
57 subj = msg.Header.Get("Subject")
59 err = errors.New("no Subject")
62 words := strings.Fields(subj)
63 for i, word := range words {
64 if strings.HasPrefix(word, "=?") && strings.HasSuffix(word, "?=") {
65 word, err = new(mime.WordDecoder).Decode(word)
72 subj = strings.Join(words, " ")
74 ct := msg.Header.Get(CT)
78 if strings.HasPrefix(ct, TP) {
79 body, err = processTP(ct, msg.Header.Get(CTE), msg.Body)
82 ct, params, err := mime.ParseMediaType(ct)
83 if ct != "multipart/signed" {
84 err = errors.New("only text/plain and multipart/signed+text/plain Content-Type supported")
87 boundary := params["boundary"]
88 if len(boundary) == 0 {
89 err = errors.New("no boundary string")
92 data, err := ioutil.ReadAll(msg.Body)
96 boundaryIdx := bytes.Index(data, []byte("--"+boundary))
97 if boundaryIdx == -1 {
98 err = errors.New("no boundary found")
101 mpr := multipart.NewReader(bytes.NewReader(data[boundaryIdx:]), boundary)
102 var part *multipart.Part
104 part, err = mpr.NextPart()
111 ct = part.Header.Get(CT)
112 if strings.HasPrefix(ct, TP) {
113 body, err = processTP(ct, part.Header.Get(CTE), part)
116 if strings.HasPrefix(ct, "multipart/mixed") {
117 ct, params, err = mime.ParseMediaType(ct)
118 boundary = params["boundary"]
119 if len(boundary) == 0 {
120 err = errors.New("no boundary string")
123 mpr := multipart.NewReader(part, boundary)
125 part, err = mpr.NextPart()
132 ct = part.Header.Get(CT)
133 if strings.HasPrefix(ct, TP) {
134 body, err = processTP(ct, part.Header.Get(CTE), part)
140 err = errors.New("no text/plain part found")