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"
27 struct _notmuch_message {
39 const char *next_line;
45 strcase_equal (const void *a, const void *b)
47 return strcasecmp (a, b) == 0;
51 strcase_hash (const void *ptr)
55 /* This is the djb2 hash. */
56 unsigned int hash = 5381;
58 hash = ((hash << 5) + hash) + tolower (*s);
66 notmuch_message_open (const char *filename)
68 notmuch_message_t *message;
71 message = xcalloc (1, sizeof (notmuch_message_t));
73 message->fd = open (filename, O_RDONLY);
77 if (fstat (message->fd, &st) < 0)
80 message->map = mmap (NULL, st.st_size, PROT_READ, MAP_PRIVATE,
82 if (message->map == MAP_FAILED)
85 message->headers = g_hash_table_new_full (strcase_hash,
90 message->start = (char *) message->map;
91 message->size = st.st_size;
92 message->next_line = message->start;
93 message->parsing_started = 0;
94 message->parsing_finished = 0;
99 fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
100 notmuch_message_close (message);
106 notmuch_message_close (notmuch_message_t *message)
111 if (message->headers)
112 g_hash_table_unref (message->headers);
115 munmap (message->map, message->size);
123 notmuch_message_restrict_headersv (notmuch_message_t *message,
128 if (message->parsing_started ) {
129 fprintf (stderr, "Error: notmuch_message_restrict_headers called after parsing has started\n");
134 header = va_arg (va_headers, char*);
137 g_hash_table_insert (message->headers,
138 xstrdup (header), NULL);
141 message->restrict_headers = 1;
145 notmuch_message_restrict_headers (notmuch_message_t *message, ...)
149 va_start (va_headers, message);
151 notmuch_message_restrict_headersv (message, va_headers);
154 /* With our mmapped file, we don't get the benefit of terminated
155 * strings, so we can't use things like strchr(). We don't even know
156 * if there's a newline at the end of the file so we also have to be
157 * careful of that. Basically, every time we advance a pointer while
158 * parsing we must ensure we don't go beyond our buffer.
160 #define WITHIN(s) (((s) - message->start) < (message->size -1))
162 /* In each of the macros below, "without overrunning the buffer" means
163 * that the macro will never dereference a character beyond the end of
164 * the buffer. However, all of the macros may return a pointer
165 * pointing to the first character beyond the buffer. So callers
166 * should test with WITHIN before dereferencing the result. */
168 /* Advance 'ptr' until pointing at a non-space character in the same
169 * line, (without overrunning the buffer) */
170 #define SKIP_SPACE_IN_LINE(ptr) \
171 while (WITHIN (ptr) && (*(ptr) == ' ' || *(ptr) == '\t')) \
174 /* Advance 'ptr' until pointing at a non-space character, (without
175 * overrunning the buffer) */
176 #define SKIP_SPACE(ptr) \
177 while (WITHIN (ptr) && isspace(*(ptr))) \
180 /* Advance 'ptr' to the first occurrence of 'c' within the same
181 * line, (without overrunning the buffer). */
182 #define ADVANCE_TO(ptr, c) \
183 while (WITHIN (ptr) && *(ptr) != '\n' && \
189 /* Advance 'ptr' to the beginning of the next line not starting with
190 * an initial tab character, (without overruning the buffer). */
191 #define ADVANCE_TO_NEXT_HEADER_LINE(ptr) \
193 ADVANCE_TO ((ptr), '\n'); \
196 } while (WITHIN (ptr) && \
197 (*(ptr) == '\t' || *(ptr) == ' '));
200 copy_header_value (const char *start, const char *end)
206 result = xmalloc (end - start + 1);
215 if (*s == '\t' && was_newline)
231 notmuch_message_get_header (notmuch_message_t *message,
232 const char *header_desired)
235 const char *s, *colon;
236 char *header, *value;
239 message->parsing_started = 1;
241 contains = g_hash_table_lookup_extended (message->headers,
242 header_desired, NULL,
243 (gpointer *) &value);
247 if (message->parsing_finished)
251 s = message->next_line;
254 message->parsing_finished = 1;
259 fprintf (stderr, "Warning: Unexpected continued value\n");
260 ADVANCE_TO_NEXT_HEADER_LINE (message->next_line);
265 ADVANCE_TO (colon, ':');
267 if (! WITHIN (colon) || *colon == '\n') {
268 fprintf (stderr, "Warning: Unexpected non-header line: %s\n", s);
269 ADVANCE_TO_NEXT_HEADER_LINE (message->next_line);
273 header = xstrndup (s, colon - s);
275 if (message->restrict_headers &&
276 ! g_hash_table_lookup_extended (message->headers,
280 message->next_line = colon;
281 ADVANCE_TO_NEXT_HEADER_LINE (message->next_line);
286 SKIP_SPACE_IN_LINE (s);
288 message->next_line = s;
289 ADVANCE_TO_NEXT_HEADER_LINE (message->next_line);
291 value = copy_header_value (s, message->next_line);
293 match = (strcasecmp (header, header_desired) == 0);
295 g_hash_table_insert (message->headers, header, value);