notmuch-mutt: replace shell pipeline with internal pipe processing
[notmuch] / util / repair.c
1 /* notmuch - Not much of an email program, (just index and search)
2  *
3  * Copyright © 2019 Daniel Kahn Gillmor
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see https://www.gnu.org/licenses/ .
17  *
18  * Authors: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
19  */
20
21 #include <stdbool.h>
22 #include "repair.h"
23
24
25 static bool
26 _notmuch_crypto_payload_has_legacy_display (GMimeObject *payload)
27 {
28     GMimeMultipart *mpayload;
29     const char *protected_header_parameter;
30     GMimeObject *first;
31
32     if (! g_mime_content_type_is_type (g_mime_object_get_content_type (payload),
33                                        "multipart", "mixed"))
34         return false;
35     protected_header_parameter = g_mime_object_get_content_type_parameter (payload, "protected-headers");
36     if ((! protected_header_parameter) || strcmp (protected_header_parameter, "v1"))
37         return false;
38     if (! GMIME_IS_MULTIPART (payload))
39         return false;
40     mpayload = GMIME_MULTIPART (payload);
41     if (mpayload == NULL)
42         return false;
43     if (g_mime_multipart_get_count (mpayload) != 2)
44         return false;
45     first = g_mime_multipart_get_part (mpayload, 0);
46     /* Early implementations that generated "Legacy Display" parts used
47        Content-Type: text/rfc822-headers, but text/plain is more widely
48        rendered, so it is now the standard choice.  We accept either as a
49        Legacy Display part. */
50     if (! (g_mime_content_type_is_type (g_mime_object_get_content_type (first),
51                                         "text", "plain") ||
52            g_mime_content_type_is_type (g_mime_object_get_content_type (first),
53                                         "text", "rfc822-headers")))
54         return false;
55     protected_header_parameter = g_mime_object_get_content_type_parameter (first, "protected-headers");
56     if ((! protected_header_parameter) || strcmp (protected_header_parameter, "v1"))
57         return false;
58     if (! GMIME_IS_TEXT_PART (first))
59         return false;
60
61     return true;
62 }
63
64 GMimeObject *
65 _notmuch_repair_crypto_payload_skip_legacy_display (GMimeObject *payload)
66 {
67     if (_notmuch_crypto_payload_has_legacy_display (payload)) {
68         return g_mime_multipart_get_part (GMIME_MULTIPART (payload), 1);
69     } else {
70         return payload;
71     }
72 }
73
74 /* see
75  * https://tools.ietf.org/html/draft-dkg-openpgp-pgpmime-message-mangling-00#section-4.1.1 */
76 static bool
77 _notmuch_is_mixed_up_mangled (GMimeObject *part)
78 {
79     GMimeMultipart *mpart = NULL;
80     GMimeObject *parts[3] = {NULL, NULL, NULL};
81     GMimeContentType *type = NULL;
82     char *prelude_string = NULL;
83     bool prelude_is_empty;
84
85     if (part == NULL)
86         return false;
87     type = g_mime_object_get_content_type (part);
88     if (type == NULL)
89         return false;
90     if (! g_mime_content_type_is_type (type, "multipart", "mixed"))
91         return false;
92     if (! GMIME_IS_MULTIPART (part)) /* probably impossible */
93         return false;
94     mpart = GMIME_MULTIPART (part);
95     if (mpart == NULL)
96         return false;
97     if (g_mime_multipart_get_count (mpart) != 3)
98         return false;
99     parts[0] = g_mime_multipart_get_part (mpart, 0);
100     if (! g_mime_content_type_is_type (g_mime_object_get_content_type (parts[0]),
101                                        "text", "plain"))
102         return false;
103     if (! GMIME_IS_TEXT_PART (parts[0]))
104         return false;
105     parts[1] = g_mime_multipart_get_part (mpart, 1);
106     if (! g_mime_content_type_is_type (g_mime_object_get_content_type (parts[1]),
107                                        "application", "pgp-encrypted"))
108         return false;
109     parts[2] = g_mime_multipart_get_part (mpart, 2);
110     if (! g_mime_content_type_is_type (g_mime_object_get_content_type (parts[2]),
111                                        "application", "octet-stream"))
112         return false;
113
114     /* Is parts[0] length 0? */
115     prelude_string = g_mime_text_part_get_text (GMIME_TEXT_PART (parts[0]));
116     prelude_is_empty = (prelude_string[0] == '\0');
117     g_free (prelude_string);
118     if (! prelude_is_empty)
119         return false;
120
121     /* FIXME: after decoding and stripping whitespace, is parts[1]
122      * subpart just "Version: 1" ? */
123
124     /* FIXME: can we determine that parts[2] subpart is *only* PGP
125      * encrypted data?  I tried g_mime_part_get_openpgp_data () but
126      * found https://github.com/jstedfast/gmime/issues/60 */
127
128     return true;
129 }
130
131
132 /* see
133  * https://tools.ietf.org/html/draft-dkg-openpgp-pgpmime-message-mangling-00#section-4.1.2 */
134 GMimeObject *
135 _notmuch_repair_mixed_up_mangled (GMimeObject *part)
136 {
137     GMimeMultipart *mpart = NULL, *mpart_ret = NULL;
138     GMimeObject *ret = NULL;
139
140     if (! _notmuch_is_mixed_up_mangled (part))
141         return NULL;
142     mpart = GMIME_MULTIPART (part);
143     ret = GMIME_OBJECT (g_mime_multipart_encrypted_new ());
144     if (ret == NULL)
145         return NULL;
146     mpart_ret = GMIME_MULTIPART (ret);
147     if (mpart_ret == NULL) {
148         g_object_unref (ret);
149         return NULL;
150     }
151     g_mime_object_set_content_type_parameter (ret, "protocol", "application/pgp-encrypted");
152
153     g_mime_multipart_insert (mpart_ret, 0, g_mime_multipart_get_part (mpart, 1));
154     g_mime_multipart_insert (mpart_ret, 1, g_mime_multipart_get_part (mpart, 2));
155     return ret;
156 }