1 /* message.c - Utility functions for parsing an email message for notmuch.
3 * Copyright © 2009 Carl Worth
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.
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.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see http://www.gnu.org/licenses/ .
18 * Author: Carl Worth <cworth@cworth.org>
23 #include "notmuch-private.h"
25 #include <glib.h> /* GHashTable */
31 } header_value_closure_t;
33 struct _notmuch_message_file {
46 header_value_closure_t value;
53 strcase_equal (const void *a, const void *b)
55 return strcasecmp (a, b) == 0;
59 strcase_hash (const void *ptr)
63 /* This is the djb2 hash. */
64 unsigned int hash = 5381;
66 hash = ((hash << 5) + hash) + tolower (*s);
73 notmuch_message_file_t *
74 notmuch_message_file_open (const char *filename)
76 notmuch_message_file_t *message;
78 message = xcalloc (1, sizeof (notmuch_message_file_t));
80 message->file = fopen (filename, "r");
81 if (message->file == NULL)
84 message->headers = g_hash_table_new_full (strcase_hash,
89 message->parsing_started = 0;
90 message->parsing_finished = 0;
95 fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
96 notmuch_message_file_close (message);
102 notmuch_message_file_close (notmuch_message_file_t *message)
108 free (message->line);
110 if (message->value.size)
111 free (message->value.str);
113 if (message->headers)
114 g_hash_table_destroy (message->headers);
117 fclose (message->file);
123 notmuch_message_file_restrict_headersv (notmuch_message_file_t *message,
128 if (message->parsing_started)
129 INTERNAL_ERROR ("notmuch_message_file_restrict_headers called after parsing has started");
132 header = va_arg (va_headers, char*);
135 g_hash_table_insert (message->headers,
136 xstrdup (header), NULL);
139 message->restrict_headers = 1;
143 notmuch_message_file_restrict_headers (notmuch_message_file_t *message, ...)
147 va_start (va_headers, message);
149 notmuch_message_file_restrict_headersv (message, va_headers);
153 copy_header_unfolding (header_value_closure_t *value,
161 while (*chunk == ' ' || *chunk == '\t')
164 if (value->len + 1 + strlen (chunk) + 1 > value->size) {
165 unsigned int new_size = value->size;
166 if (value->size == 0)
167 new_size = strlen (chunk) + 1;
169 while (value->len + 1 + strlen (chunk) + 1 > new_size)
171 value->str = xrealloc (value->str, new_size);
172 value->size = new_size;
175 last = value->str + value->len;
182 strcpy (last, chunk);
183 value->len += strlen (chunk);
185 last = value->str + value->len - 1;
193 notmuch_message_file_get_header (notmuch_message_file_t *message,
194 const char *header_desired)
197 char *header, *value;
198 const char *s, *colon;
201 message->parsing_started = 1;
203 contains = g_hash_table_lookup_extended (message->headers,
204 header_desired, NULL,
205 (gpointer *) &value);
206 if (contains && value)
209 if (message->parsing_finished)
212 #define NEXT_HEADER_LINE(closure) \
214 ssize_t bytes_read = getline (&message->line, \
215 &message->line_size, \
217 if (bytes_read == -1) { \
218 message->parsing_finished = 1; \
221 if (*message->line == '\n') { \
222 message->parsing_finished = 1; \
226 (*message->line == ' ' || *message->line == '\t')) \
228 copy_header_unfolding ((closure), message->line); \
230 } while (*message->line == ' ' || *message->line == '\t');
232 if (message->line == NULL)
233 NEXT_HEADER_LINE (NULL);
237 if (message->parsing_finished)
240 colon = strchr (message->line, ':');
243 message->broken_headers++;
244 /* A simple heuristic for giving up on things that just
245 * don't look like mail messages. */
246 if (message->broken_headers >= 10 &&
247 message->good_headers < 5)
249 message->parsing_finished = 1;
252 NEXT_HEADER_LINE (NULL);
256 message->good_headers++;
258 header = xstrndup (message->line, colon - message->line);
260 if (message->restrict_headers &&
261 ! g_hash_table_lookup_extended (message->headers,
265 NEXT_HEADER_LINE (NULL);
270 while (*s == ' ' || *s == '\t')
273 message->value.len = 0;
274 copy_header_unfolding (&message->value, s);
276 NEXT_HEADER_LINE (&message->value);
278 match = (strcasecmp (header, header_desired) == 0);
280 value = xstrdup (message->value.str);
282 g_hash_table_insert (message->headers, header, value);
289 free (message->line);
290 message->line = NULL;
292 if (message->value.size) {
293 free (message->value.str);
294 message->value.str = NULL;
295 message->value.size = 0;
296 message->value.len = 0;
299 /* We've parsed all headers and never found the one we're looking
300 * for. It's probably just not there, but let's check that we
301 * didn't make a mistake preventing us from seeing it. */
302 if (message->restrict_headers &&
303 ! g_hash_table_lookup_extended (message->headers,
304 header_desired, NULL, NULL))
306 INTERNAL_ERROR ("Attempt to get header \"%s\" which was not\n"
307 "included in call to notmuch_message_file_restrict_headers\n",