test: copy files in test_expect_equal_file instead of moving them
[notmuch] / gmime-filter-headers.c
1 /*
2  * Copyright © 2009 Keith Packard <keithp@keithp.com>
3  * Copyright © 2010 Michal Sojka <sojkam1@fel.cvut.cz>
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, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
18  */
19
20 #include "gmime-filter-headers.h"
21 #include <string.h>
22 #include <gmime/gmime-utils.h>
23 #include <glib/gprintf.h>
24 #include <stdlib.h>
25 #include <xutil.h>
26
27 /**
28  * SECTION: gmime-filter-headers
29  * @title: GMimeFilterHeaders
30  * @short_description: Add/remove headers markers
31  *
32  * A #GMimeFilter for decoding rfc2047 encoded headers to UTF-8
33  **/
34
35
36 static void g_mime_filter_headers_class_init (GMimeFilterHeadersClass *klass);
37 static void g_mime_filter_headers_init (GMimeFilterHeaders *filter, GMimeFilterHeadersClass *klass);
38 static void g_mime_filter_headers_finalize (GObject *object);
39
40 static GMimeFilter *filter_copy (GMimeFilter *filter);
41 static void filter_filter (GMimeFilter *filter, char *in, size_t len, size_t prespace,
42                            char **out, size_t *outlen, size_t *outprespace);
43 static void filter_complete (GMimeFilter *filter, char *in, size_t len, size_t prespace,
44                              char **out, size_t *outlen, size_t *outprespace);
45 static void filter_reset (GMimeFilter *filter);
46
47
48 static GMimeFilterClass *parent_class = NULL;
49
50 GType
51 g_mime_filter_headers_get_type (void)
52 {
53         static GType type = 0;
54
55         if (!type) {
56                 static const GTypeInfo info = {
57                         sizeof (GMimeFilterHeadersClass),
58                         NULL, /* base_class_init */
59                         NULL, /* base_class_finalize */
60                         (GClassInitFunc) g_mime_filter_headers_class_init,
61                         NULL, /* class_finalize */
62                         NULL, /* class_data */
63                         sizeof (GMimeFilterHeaders),
64                         0,    /* n_preallocs */
65                         (GInstanceInitFunc) g_mime_filter_headers_init,
66                         NULL    /* value_table */
67                 };
68
69                 type = g_type_register_static (GMIME_TYPE_FILTER, "GMimeFilterHeaders", &info, (GTypeFlags) 0);
70         }
71
72         return type;
73 }
74
75
76 static void
77 g_mime_filter_headers_class_init (GMimeFilterHeadersClass *klass)
78 {
79         GObjectClass *object_class = G_OBJECT_CLASS (klass);
80         GMimeFilterClass *filter_class = GMIME_FILTER_CLASS (klass);
81
82         parent_class = (GMimeFilterClass *) g_type_class_ref (GMIME_TYPE_FILTER);
83
84         object_class->finalize = g_mime_filter_headers_finalize;
85
86         filter_class->copy = filter_copy;
87         filter_class->filter = filter_filter;
88         filter_class->complete = filter_complete;
89         filter_class->reset = filter_reset;
90 }
91
92 static void
93 g_mime_filter_headers_init (GMimeFilterHeaders *filter, GMimeFilterHeadersClass *klass)
94 {
95         (void) klass;
96         filter->saw_nl = TRUE;
97         filter->line = NULL;
98         filter->line_size = 0;
99         filter->lineptr = NULL;
100 }
101
102 static void
103 g_mime_filter_headers_finalize (GObject *object)
104 {
105         free (GMIME_FILTER_HEADERS (object)->line);
106         G_OBJECT_CLASS (parent_class)->finalize (object);
107 }
108
109
110 static GMimeFilter *
111 filter_copy (GMimeFilter *filter)
112 {
113         (void) filter;
114         return g_mime_filter_headers_new ();
115 }
116
117 static void
118 output_decoded_header (GMimeFilterHeaders *headers, char **outptr)
119 {
120         char *colon, *name, *s, *decoded_value;
121         size_t offset;
122         gint ret;
123
124         colon = strchr (headers->line, ':');
125         if (colon == NULL)
126                 return;
127
128         name = headers->line;
129         *colon = '\0';
130         s = colon + 1;
131         while (*s == ' ' || *s == '\t')
132                 s++;
133         decoded_value = g_mime_utils_header_decode_text(s);
134         if (decoded_value == NULL)
135                 return;
136         offset = *outptr - GMIME_FILTER (headers)->outbuf;
137         g_mime_filter_set_size (GMIME_FILTER (headers), strlen(name) + 2 +
138                                strlen(decoded_value) + 2, TRUE);
139         *outptr = GMIME_FILTER (headers)->outbuf + offset;
140         ret = g_sprintf (*outptr, "%s: %s\n", name, decoded_value);
141         if (ret > 0)
142                 *outptr += ret;
143         free (decoded_value);
144 }
145
146 static void
147 output_final_newline (GMimeFilterHeaders *headers, char **outptr)
148 {
149         size_t offset;
150
151         offset = *outptr - GMIME_FILTER (headers)->outbuf;
152         g_mime_filter_set_size (GMIME_FILTER (headers), 1, TRUE);
153         *outptr = GMIME_FILTER (headers)->outbuf + offset;
154         *(*outptr)++ = '\n';
155 }
156
157 static void
158 filter_filter (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace,
159                char **outbuf, size_t *outlen, size_t *outprespace)
160 {
161         GMimeFilterHeaders *headers = (GMimeFilterHeaders *) filter;
162         register const char *inptr = inbuf;
163         const char *inend = inbuf + inlen;
164         char *lineptr, *lineend, *outptr;
165
166         (void) prespace;
167         if (headers->line == NULL) {
168                 headers->line_size = 200;
169                 headers->lineptr = headers->line = malloc (headers->line_size);
170         }
171         lineptr = headers->lineptr;
172         lineend = headers->line + headers->line_size - 1;
173         if (lineptr == NULL)
174                 return;
175         outptr = filter->outbuf;
176         while (inptr < inend) {
177                 if (*inptr == '\n') {
178                         if (headers->saw_nl)
179                                 output_final_newline(headers, &outptr);
180                         headers->saw_nl = TRUE;
181                         inptr++;
182                         continue;
183                 }
184
185                 if (lineptr == lineend) {
186                         headers->line_size *= 2;
187                         headers->line = xrealloc (headers->line, headers->line_size);
188                         lineptr = headers->line + (headers->line_size / 2) - 1;
189                         lineend = headers->line + headers->line_size - 1;
190                 }
191
192                 if (headers->saw_nl && *inptr != ' ' && *inptr != '\t') {
193                         *lineptr = '\0';
194                         output_decoded_header (headers, &outptr);
195                         lineptr = headers->line;
196                 }
197                 if (headers->saw_nl && (*inptr == ' ' || *inptr == '\t')) {
198                         *lineptr = ' ';
199                         lineptr++;
200                         while (inptr < inend && (*inptr == ' ' || *inptr == '\t'))
201                                 inptr++;
202                         headers->saw_nl = FALSE;
203                         continue;
204                 }
205                 headers->saw_nl = FALSE;
206
207                 if (*inptr != '\r')
208                         *lineptr++ = *inptr;
209                 inptr++;
210         }
211         if (headers->saw_nl) {
212                 *lineptr = '\0';
213                 output_decoded_header (headers, &outptr);
214                 lineptr = headers->line;
215         }
216         headers->lineptr = lineptr;
217         *outlen = outptr - filter->outbuf;
218         *outprespace = filter->outpre;
219         *outbuf = filter->outbuf;
220 }
221
222 static void
223 filter_complete (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace,
224                  char **outbuf, size_t *outlen, size_t *outprespace)
225 {
226         if (inbuf && inlen)
227                 filter_filter (filter, inbuf, inlen, prespace, outbuf, outlen, outprespace);
228 }
229
230 static void
231 filter_reset (GMimeFilter *filter)
232 {
233         GMimeFilterHeaders *headers = (GMimeFilterHeaders *) filter;
234
235         headers->saw_nl = TRUE;
236         free(headers->line);
237         headers->line = NULL;
238         headers->line_size = 0;
239 }
240
241
242 /**
243  * g_mime_filter_headers_new:
244  * @encode: %TRUE if the filter should encode or %FALSE otherwise
245  * @dots: encode/decode dots (as for SMTP)
246  *
247  * Creates a new #GMimeFilterHeaders filter.
248  *
249  * If @encode is %TRUE, then all lines will be prefixed by "> ",
250  * otherwise any lines starting with "> " will have that removed
251  *
252  * Returns: a new #GMimeFilterHeaders filter.
253  **/
254 GMimeFilter *
255 g_mime_filter_headers_new (void)
256 {
257         GMimeFilterHeaders *new_headers;
258
259         new_headers = (GMimeFilterHeaders *) g_object_newv (GMIME_TYPE_FILTER_HEADERS, 0, NULL);
260
261         return (GMimeFilter *) new_headers;
262 }
263