Re: [PATCH v4 01/16] add util/search-path.{c, h} to test for executables in $PATH
[notmuch-archives.git] / b8 / 1d3a47748a8e92c62f3277fbb1485ecff69e45
1 Return-Path: <jani@nikula.org>\r
2 X-Original-To: notmuch@notmuchmail.org\r
3 Delivered-To: notmuch@notmuchmail.org\r
4 Received: from localhost (localhost [127.0.0.1])\r
5         by olra.theworths.org (Postfix) with ESMTP id ED41C431FAF\r
6         for <notmuch@notmuchmail.org>; Mon,  3 Feb 2014 11:52:47 -0800 (PST)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Amavis-Alert: BAD HEADER SECTION, Duplicate header field: "References"\r
9 X-Spam-Flag: NO\r
10 X-Spam-Score: -0.7\r
11 X-Spam-Level: \r
12 X-Spam-Status: No, score=-0.7 tagged_above=-999 required=5\r
13         tests=[RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled\r
14 Received: from olra.theworths.org ([127.0.0.1])\r
15         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
16         with ESMTP id oH12yEE4QUMx for <notmuch@notmuchmail.org>;\r
17         Mon,  3 Feb 2014 11:52:41 -0800 (PST)\r
18 Received: from mail-ea0-f179.google.com (mail-ea0-f179.google.com\r
19         [209.85.215.179]) (using TLSv1 with cipher RC4-SHA (128/128 bits))\r
20         (No client certificate requested)\r
21         by olra.theworths.org (Postfix) with ESMTPS id CB8D9429E2F\r
22         for <notmuch@notmuchmail.org>; Mon,  3 Feb 2014 11:52:15 -0800 (PST)\r
23 Received: by mail-ea0-f179.google.com with SMTP id q10so3297157ead.38\r
24         for <notmuch@notmuchmail.org>; Mon, 03 Feb 2014 11:52:14 -0800 (PST)\r
25 X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r
26         d=1e100.net; s=20130820;\r
27         h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to\r
28         :references:in-reply-to:references;\r
29         bh=j6/yU5yTiE2oITHiaMSWAlokJzHivQmMFo+30O6a6Ng=;\r
30         b=ZieoV/+mYSKPYsY+Acs5GwfGUprgj8H96hai3eXuSoSrsLXILG+UYugsMglqCzjmbE\r
31         9qK80eONAimH/ZR6pAWQFRhTHKZDCZown6ZxEwjoEojO5JTh5724gl3xForeFpTzSOHD\r
32         /MXIDjdBWr9k0uYOlSLsLeUl76SYy3/Amvu5ja5BJwI1zWMicKXOJsDCEcdBrjGynBDu\r
33         1gppUkKhhg/FoZy5NmwgaU4vtUTp80tEtGNvVN5mq4mYwRsez2iWxou2hp4H3GjW7s44\r
34         UIXZqx2tqG35eT3MmjxvpoXkChsqpdMjZKvYbZHSR9gtplEa1obFJRDMYJZqlUBIbg3y\r
35         jQ0A==\r
36 X-Gm-Message-State:\r
37  ALoCoQm7JhTE+y70/Scwe4sc8hPEhqsxMK+yS9sBvxyN2IwiShDzIOM1MlKV4I+q/s60iaQvayS6\r
38 X-Received: by 10.14.182.5 with SMTP id n5mr4549528eem.68.1391457134621;\r
39         Mon, 03 Feb 2014 11:52:14 -0800 (PST)\r
40 Received: from localhost (dsl-hkibrasgw2-58c36f-91.dhcp.inet.fi.\r
41         [88.195.111.91])\r
42         by mx.google.com with ESMTPSA id g1sm78793412eet.6.2014.02.03.11.52.12\r
43         for <multiple recipients>\r
44         (version=TLSv1.2 cipher=RC4-SHA bits=128/128);\r
45         Mon, 03 Feb 2014 11:52:13 -0800 (PST)\r
46 From: Jani Nikula <jani@nikula.org>\r
47 To: notmuch@notmuchmail.org\r
48 Subject: [PATCH v3 5/6] lib: replace the header parser with gmime\r
49 Date: Mon,  3 Feb 2014 21:51:45 +0200\r
50 Message-Id:\r
51  <bdef45dc21c777130a7ae1fa650f172d8c6eaaf4.1391456555.git.jani@nikula.org>\r
52 X-Mailer: git-send-email 1.8.5.2\r
53 In-Reply-To: <cover.1391456555.git.jani@nikula.org>\r
54 References: <cover.1391456555.git.jani@nikula.org>\r
55 In-Reply-To: <cover.1391456555.git.jani@nikula.org>\r
56 References: <cover.1391456555.git.jani@nikula.org>\r
57 X-BeenThere: notmuch@notmuchmail.org\r
58 X-Mailman-Version: 2.1.13\r
59 Precedence: list\r
60 List-Id: "Use and development of the notmuch mail system."\r
61         <notmuch.notmuchmail.org>\r
62 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
63         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
64 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
65 List-Post: <mailto:notmuch@notmuchmail.org>\r
66 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
67 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
68         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
69 X-List-Received-Date: Mon, 03 Feb 2014 19:52:48 -0000\r
70 \r
71 The notmuch library includes a full blown message header parser. Yet\r
72 the same message headers are parsed by gmime during indexing. Switch\r
73 to gmime parsing completely.\r
74 \r
75 These are the main differences between the parsers:\r
76 \r
77 * Gmime stops header parsing at the first invalid header, and presumes\r
78   the message body starts from there. The current parser is quite\r
79   liberal in accepting broken headers. The change means we will be\r
80   much pickier about accepting invalid messages. (Note that the\r
81   headers after the invalid header would not have been indexed before\r
82   this change anyway, as we use gmime for that.)\r
83 \r
84 * The current parser converts tabs used in header folding to\r
85   spaces. Gmime preserve the tabs. Due to a broken python library used\r
86   in mailman, there are plenty of mailing lists that produce headers\r
87   with tabs in header folding, and we'll see plenty of tabs. (This\r
88   change has been mitigated in preparatory patches.)\r
89 \r
90 * For pure header parsing, the current parser is likely faster than\r
91   gmime, which parses the whole message rather than just the\r
92   headers. Since we use gmime for indexing anyway, we can drop an\r
93   extra header parsing round (this is done in a follow-up patch).\r
94 \r
95 At this step, we only switch the header parsing to gmime.\r
96 ---\r
97  lib/database.cc       |   4 +\r
98  lib/index.cc          |  11 --\r
99  lib/message-file.c    | 349 ++++++++++++++++----------------------------------\r
100  lib/message.cc        |   6 +\r
101  lib/notmuch-private.h |   4 +\r
102  5 files changed, 125 insertions(+), 249 deletions(-)\r
103 \r
104 diff --git a/lib/database.cc b/lib/database.cc\r
105 index f395061..d1bea88 100644\r
106 --- a/lib/database.cc\r
107 +++ b/lib/database.cc\r
108 @@ -1940,6 +1940,10 @@ notmuch_database_add_message (notmuch_database_t *notmuch,\r
109                                            "to",\r
110                                            (char *) NULL);\r
111  \r
112 +    ret = notmuch_message_file_parse (message_file);\r
113 +    if (ret)\r
114 +       goto DONE;\r
115 +\r
116      try {\r
117         /* Before we do any real work, (especially before doing a\r
118          * potential SHA-1 computation on the entire file's contents),\r
119 diff --git a/lib/index.cc b/lib/index.cc\r
120 index 78c18cf..976e49f 100644\r
121 --- a/lib/index.cc\r
122 +++ b/lib/index.cc\r
123 @@ -437,7 +437,6 @@ _notmuch_message_index_file (notmuch_message_t *message,\r
124      static int initialized = 0;\r
125      char from_buf[5];\r
126      bool is_mbox = false;\r
127 -    static bool mbox_warning = false;\r
128  \r
129      if (! initialized) {\r
130         g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);\r
131 @@ -471,16 +470,6 @@ _notmuch_message_index_file (notmuch_message_t *message,\r
132             ret = NOTMUCH_STATUS_FILE_NOT_EMAIL;\r
133             goto DONE;\r
134         }\r
135 -       /* For historical reasons, we support single-message mboxes,\r
136 -        * but this behavior is likely to change in the future, so\r
137 -        * warn. */\r
138 -       if (!mbox_warning) {\r
139 -           mbox_warning = true;\r
140 -           fprintf (stderr, "\\r
141 -Warning: %s is an mbox containing a single message,\n\\r
142 -likely caused by misconfigured mail delivery.  Support for single-message\n\\r
143 -mboxes is deprecated and may be removed in the future.\n", filename);\r
144 -       }\r
145      }\r
146  \r
147      from = g_mime_message_get_sender (mime_message);\r
148 diff --git a/lib/message-file.c b/lib/message-file.c\r
149 index a2850c2..33f6468 100644\r
150 --- a/lib/message-file.c\r
151 +++ b/lib/message-file.c\r
152 @@ -26,30 +26,19 @@\r
153  \r
154  #include <glib.h> /* GHashTable */\r
155  \r
156 -typedef struct {\r
157 -    char *str;\r
158 -    size_t size;\r
159 -    size_t len;\r
160 -} header_value_closure_t;\r
161 -\r
162  struct _notmuch_message_file {\r
163      /* File object */\r
164      FILE *file;\r
165 +    char *filename;\r
166  \r
167      /* Header storage */\r
168      int restrict_headers;\r
169      GHashTable *headers;\r
170 -    int broken_headers;\r
171 -    int good_headers;\r
172 -    size_t header_size; /* Length of full message header in bytes. */\r
173 -\r
174 -    /* Parsing state */\r
175 -    char *line;\r
176 -    size_t line_size;\r
177 -    header_value_closure_t value;\r
178  \r
179 -    int parsing_started;\r
180 -    int parsing_finished;\r
181 +    GMimeStream *stream;\r
182 +    GMimeParser *parser;\r
183 +    GMimeMessage *message;\r
184 +    notmuch_bool_t parsed;\r
185  };\r
186  \r
187  static int\r
188 @@ -76,16 +65,18 @@ strcase_hash (const void *ptr)\r
189  static int\r
190  _notmuch_message_file_destructor (notmuch_message_file_t *message)\r
191  {\r
192 -    if (message->line)\r
193 -       free (message->line);\r
194 -\r
195 -    if (message->value.size)\r
196 -       free (message->value.str);\r
197 -\r
198      if (message->headers)\r
199         g_hash_table_destroy (message->headers);\r
200  \r
201 -    if (message->file)\r
202 +    if (message->message)\r
203 +       g_object_unref (message->message);\r
204 +\r
205 +    if (message->parser)\r
206 +       g_object_unref (message->parser);\r
207 +\r
208 +    if (message->stream)\r
209 +       g_object_unref (message->stream);\r
210 +    else if (message->file) /* GMimeStream takes over the FILE* */\r
211         fclose (message->file);\r
212  \r
213      return 0;\r
214 @@ -102,19 +93,21 @@ _notmuch_message_file_open_ctx (void *ctx, const char *filename)\r
215      if (unlikely (message == NULL))\r
216         return NULL;\r
217  \r
218 +    /* only needed for error messages during parsing */\r
219 +    message->filename = talloc_strdup (message, filename);\r
220 +    if (message->filename == NULL)\r
221 +       goto FAIL;\r
222 +\r
223      talloc_set_destructor (message, _notmuch_message_file_destructor);\r
224  \r
225      message->file = fopen (filename, "r");\r
226      if (message->file == NULL)\r
227         goto FAIL;\r
228  \r
229 -    message->headers = g_hash_table_new_full (strcase_hash,\r
230 -                                             strcase_equal,\r
231 -                                             free,\r
232 -                                             g_free);\r
233 -\r
234 -    message->parsing_started = 0;\r
235 -    message->parsing_finished = 0;\r
236 +    message->headers = g_hash_table_new_full (strcase_hash, strcase_equal,\r
237 +                                             free, g_free);\r
238 +    if (message->headers == NULL)\r
239 +       goto FAIL;\r
240  \r
241      return message;\r
242  \r
243 @@ -143,7 +136,7 @@ notmuch_message_file_restrict_headersv (notmuch_message_file_t *message,\r
244  {\r
245      char *header;\r
246  \r
247 -    if (message->parsing_started)\r
248 +    if (message->parsed)\r
249         INTERNAL_ERROR ("notmuch_message_file_restrict_headers called after parsing has started");\r
250  \r
251      while (1) {\r
252 @@ -167,234 +160,114 @@ notmuch_message_file_restrict_headers (notmuch_message_file_t *message, ...)\r
253      notmuch_message_file_restrict_headersv (message, va_headers);\r
254  }\r
255  \r
256 -static void\r
257 -copy_header_unfolding (header_value_closure_t *value,\r
258 -                      const char *chunk)\r
259 +/*\r
260 + * gmime does not provide access to all Received: headers the way we\r
261 + * want, so we'll to use the parser header callback to gather them\r
262 + * into a hash table.\r
263 + */\r
264 +static void header_cb (unused(GMimeParser *parser), const char *header,\r
265 +                      const char *value, unused(gint64 offset),\r
266 +                      gpointer user_data)\r
267  {\r
268 -    char *last;\r
269 +    notmuch_message_file_t *message = (notmuch_message_file_t *) user_data;\r
270 +    char *existing = NULL;\r
271 +    notmuch_bool_t found;\r
272  \r
273 -    if (chunk == NULL)\r
274 +    found = g_hash_table_lookup_extended (message->headers, header,\r
275 +                                         NULL, (gpointer *) &existing);\r
276 +    if (! found && message->restrict_headers)\r
277         return;\r
278  \r
279 -    while (*chunk == ' ' || *chunk == '\t')\r
280 -       chunk++;\r
281 -\r
282 -    if (value->len + 1 + strlen (chunk) + 1 > value->size) {\r
283 -       unsigned int new_size = value->size;\r
284 -       if (value->size == 0)\r
285 -           new_size = strlen (chunk) + 1;\r
286 -       else\r
287 -           while (value->len + 1 + strlen (chunk) + 1 > new_size)\r
288 -               new_size *= 2;\r
289 -       value->str = xrealloc (value->str, new_size);\r
290 -       value->size = new_size;\r
291 -    }\r
292 -\r
293 -    last = value->str + value->len;\r
294 -    if (value->len) {\r
295 -       *last = ' ';\r
296 -       last++;\r
297 -       value->len++;\r
298 -    }\r
299 -\r
300 -    strcpy (last, chunk);\r
301 -    value->len += strlen (chunk);\r
302 -\r
303 -    last = value->str + value->len - 1;\r
304 -    if (*last == '\n') {\r
305 -       *last = '\0';\r
306 -       value->len--;\r
307 +    if (existing == NULL) {\r
308 +       /* No value, add one */\r
309 +       char *decoded = g_mime_utils_header_decode_text (value);\r
310 +       g_hash_table_insert (message->headers, xstrdup (header), decoded);\r
311 +    } else if (strcasecmp (header, "received") == 0) {\r
312 +       /* Concat all of the Received: headers we encounter. */\r
313 +       char *combined, *decoded;\r
314 +       size_t combined_size;\r
315 +\r
316 +       decoded = g_mime_utils_header_decode_text (value);\r
317 +\r
318 +       combined_size = strlen(existing) + strlen(decoded) + 2;\r
319 +       combined = g_malloc (combined_size);\r
320 +       snprintf (combined, combined_size, "%s %s", existing, decoded);\r
321 +       g_free (decoded);\r
322 +       g_hash_table_insert (message->headers, xstrdup (header), combined);\r
323      }\r
324  }\r
325  \r
326 -/* As a special-case, a value of NULL for header_desired will force\r
327 - * the entire header to be parsed if it is not parsed already. This is\r
328 - * used by the _notmuch_message_file_get_headers_end function.\r
329 - * Another special case is the Received: header. For this header we\r
330 - * want to concatenate all instances of the header instead of just\r
331 - * hashing the first instance as we use this when analyzing the path\r
332 - * the mail has taken from sender to recipient.\r
333 - */\r
334 -const char *\r
335 -notmuch_message_file_get_header (notmuch_message_file_t *message,\r
336 -                                const char *header_desired)\r
337 +notmuch_status_t\r
338 +notmuch_message_file_parse (notmuch_message_file_t *message)\r
339  {\r
340 -    int contains;\r
341 -    char *header, *decoded_value, *header_sofar, *combined_header;\r
342 -    const char *s, *colon;\r
343 -    int match, newhdr, hdrsofar, is_received;\r
344      static int initialized = 0;\r
345 -\r
346 -    is_received = (strcmp(header_desired,"received") == 0);\r
347 +    char from_buf[5];\r
348 +    notmuch_bool_t is_mbox = FALSE;\r
349 +    static notmuch_bool_t mbox_warning = FALSE;\r
350  \r
351      if (! initialized) {\r
352         g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);\r
353         initialized = 1;\r
354      }\r
355  \r
356 -    message->parsing_started = 1;\r
357 -\r
358 -    if (header_desired == NULL)\r
359 -       contains = 0;\r
360 -    else\r
361 -       contains = g_hash_table_lookup_extended (message->headers,\r
362 -                                                header_desired, NULL,\r
363 -                                                (gpointer *) &decoded_value);\r
364 -\r
365 -    if (contains && decoded_value)\r
366 -       return decoded_value;\r
367 -\r
368 -    if (message->parsing_finished)\r
369 -       return "";\r
370 -\r
371 -#define NEXT_HEADER_LINE(closure)                              \\r
372 -    while (1) {                                                        \\r
373 -       ssize_t bytes_read = getline (&message->line,           \\r
374 -                                     &message->line_size,      \\r
375 -                                     message->file);           \\r
376 -       if (bytes_read == -1) {                                 \\r
377 -           message->parsing_finished = 1;                      \\r
378 -           break;                                              \\r
379 -       }                                                       \\r
380 -       if (*message->line == '\n') {                           \\r
381 -           message->parsing_finished = 1;                      \\r
382 -           break;                                              \\r
383 -       }                                                       \\r
384 -       if (closure &&                                          \\r
385 -           (*message->line == ' ' || *message->line == '\t'))  \\r
386 -       {                                                       \\r
387 -           copy_header_unfolding ((closure), message->line);   \\r
388 -       }                                                       \\r
389 -       if (*message->line == ' ' || *message->line == '\t')    \\r
390 -           message->header_size += strlen (message->line);     \\r
391 -       else                                                    \\r
392 -           break;                                              \\r
393 -    }\r
394 -\r
395 -    if (message->line == NULL)\r
396 -       NEXT_HEADER_LINE (NULL);\r
397 -\r
398 -    while (1) {\r
399 -\r
400 -       if (message->parsing_finished)\r
401 -           break;\r
402 -\r
403 -       colon = strchr (message->line, ':');\r
404 -\r
405 -       if (colon == NULL) {\r
406 -           message->broken_headers++;\r
407 -           /* A simple heuristic for giving up on things that just\r
408 -            * don't look like mail messages. */\r
409 -           if (message->broken_headers >= 10 &&\r
410 -               message->good_headers < 5)\r
411 -           {\r
412 -               message->parsing_finished = 1;\r
413 -               break;\r
414 -           }\r
415 -           NEXT_HEADER_LINE (NULL);\r
416 -           continue;\r
417 +    /* Is this mbox? */\r
418 +    if (fread (from_buf, sizeof (from_buf), 1, message->file) == 1 &&\r
419 +       strncmp (from_buf, "From ", 5) == 0)\r
420 +       is_mbox = TRUE;\r
421 +    rewind (message->file);\r
422 +\r
423 +    message->stream = g_mime_stream_file_new (message->file);\r
424 +    message->parser = g_mime_parser_new_with_stream (message->stream);\r
425 +    g_mime_parser_set_scan_from (message->parser, is_mbox);\r
426 +    g_mime_parser_set_header_regex (message->parser, ".*", header_cb,\r
427 +                                   (gpointer) message);\r
428 +\r
429 +    message->message = g_mime_parser_construct_message (message->parser);\r
430 +    if (! message->message)\r
431 +       return NOTMUCH_STATUS_FILE_NOT_EMAIL;\r
432 +\r
433 +    if (is_mbox) {\r
434 +       if (! g_mime_parser_eos (message->parser)) {\r
435 +           /* This is a multi-message mbox. */\r
436 +           return NOTMUCH_STATUS_FILE_NOT_EMAIL;\r
437         }\r
438 -\r
439 -       message->header_size += strlen (message->line);\r
440 -\r
441 -       message->good_headers++;\r
442 -\r
443 -       header = xstrndup (message->line, colon - message->line);\r
444 -\r
445 -       if (message->restrict_headers &&\r
446 -           ! g_hash_table_lookup_extended (message->headers,\r
447 -                                           header, NULL, NULL))\r
448 -       {\r
449 -           free (header);\r
450 -           NEXT_HEADER_LINE (NULL);\r
451 -           continue;\r
452 -       }\r
453 -\r
454 -       s = colon + 1;\r
455 -       while (*s == ' ' || *s == '\t')\r
456 -           s++;\r
457 -\r
458 -       message->value.len = 0;\r
459 -       copy_header_unfolding (&message->value, s);\r
460 -\r
461 -       NEXT_HEADER_LINE (&message->value);\r
462 -\r
463 -       if (header_desired == NULL)\r
464 -           match = 0;\r
465 -       else\r
466 -           match = (strcasecmp (header, header_desired) == 0);\r
467 -\r
468 -       decoded_value = g_mime_utils_header_decode_text (message->value.str);\r
469 -       header_sofar = (char *)g_hash_table_lookup (message->headers, header);\r
470 -       /* we treat the Received: header special - we want to concat ALL of \r
471 -        * the Received: headers we encounter.\r
472 -        * for everything else we return the first instance of a header */\r
473 -       if (strcasecmp(header, "received") == 0) {\r
474 -           if (header_sofar == NULL) {\r
475 -               /* first Received: header we encountered; just add it */\r
476 -               g_hash_table_insert (message->headers, header, decoded_value);\r
477 -           } else {\r
478 -               /* we need to add the header to those we already collected */\r
479 -               newhdr = strlen(decoded_value);\r
480 -               hdrsofar = strlen(header_sofar);\r
481 -               combined_header = g_malloc(hdrsofar + newhdr + 2);\r
482 -               strncpy(combined_header,header_sofar,hdrsofar);\r
483 -               *(combined_header+hdrsofar) = ' ';\r
484 -               strncpy(combined_header+hdrsofar+1,decoded_value,newhdr+1);\r
485 -               g_free (decoded_value);\r
486 -               g_hash_table_insert (message->headers, header, combined_header);\r
487 -           }\r
488 -       } else {\r
489 -           if (header_sofar == NULL) {\r
490 -               /* Only insert if we don't have a value for this header, yet. */\r
491 -               g_hash_table_insert (message->headers, header, decoded_value);\r
492 -           } else {\r
493 -               free (header);\r
494 -               g_free (decoded_value);\r
495 -               decoded_value = header_sofar;\r
496 -           }\r
497 +       /*\r
498 +        * For historical reasons, we support single-message mboxes,\r
499 +        * but this behavior is likely to change in the future, so\r
500 +        * warn.\r
501 +        */\r
502 +       if (! mbox_warning) {\r
503 +           mbox_warning = TRUE;\r
504 +           fprintf (stderr, "\\r
505 +Warning: %s is an mbox containing a single message,\n\\r
506 +likely caused by misconfigured mail delivery.  Support for single-message\n\\r
507 +mboxes is deprecated and may be removed in the future.\n", message->filename);\r
508         }\r
509 -       /* if we found a match we can bail - unless of course we are\r
510 -        * collecting all the Received: headers */\r
511 -       if (match && !is_received)\r
512 -           return decoded_value;\r
513      }\r
514  \r
515 -    if (message->parsing_finished) {\r
516 -        fclose (message->file);\r
517 -        message->file = NULL;\r
518 -    }\r
519 +    message->parsed = TRUE;\r
520  \r
521 -    if (message->line)\r
522 -       free (message->line);\r
523 -    message->line = NULL;\r
524 +    return NOTMUCH_STATUS_SUCCESS;\r
525 +}\r
526  \r
527 -    if (message->value.size) {\r
528 -       free (message->value.str);\r
529 -       message->value.str = NULL;\r
530 -       message->value.size = 0;\r
531 -       message->value.len = 0;\r
532 -    }\r
533 +/* return NULL on errors, empty string for non-existing headers */\r
534 +const char *\r
535 +notmuch_message_file_get_header (notmuch_message_file_t *message,\r
536 +                                const char *header)\r
537 +{\r
538 +    const char *value = NULL;\r
539 +    notmuch_bool_t found;\r
540  \r
541 -    /* For the Received: header we actually might end up here even\r
542 -     * though we found the header (as we force continued parsing\r
543 -     * in that case). So let's check if that's the header we were\r
544 -     * looking for and return the value that we found (if any)\r
545 -     */\r
546 -    if (is_received)\r
547 -       return (char *)g_hash_table_lookup (message->headers, "received");\r
548 -\r
549 -    /* We've parsed all headers and never found the one we're looking\r
550 -     * for. It's probably just not there, but let's check that we\r
551 -     * didn't make a mistake preventing us from seeing it. */\r
552 -    if (message->restrict_headers && header_desired &&\r
553 -       ! g_hash_table_lookup_extended (message->headers,\r
554 -                                       header_desired, NULL, NULL))\r
555 -    {\r
556 -       INTERNAL_ERROR ("Attempt to get header \"%s\" which was not\n"\r
557 -                       "included in call to notmuch_message_file_restrict_headers\n",\r
558 -                       header_desired);\r
559 -    }\r
560 +    /* the caller shouldn't ask for headers before parsing */\r
561 +    if (! message->parsed)\r
562 +       return NULL;\r
563 +\r
564 +    found = g_hash_table_lookup_extended (message->headers, header,\r
565 +                                         NULL, (gpointer *) &value);\r
566 +\r
567 +    /* the caller shouldn't ask for non-restricted headers */\r
568 +    if (! found && message->restrict_headers)\r
569 +       return NULL;\r
570  \r
571 -    return "";\r
572 +    return value ? value : "";\r
573  }\r
574 diff --git a/lib/message.cc b/lib/message.cc\r
575 index c91f3a5..9a22d36 100644\r
576 --- a/lib/message.cc\r
577 +++ b/lib/message.cc\r
578 @@ -407,6 +407,12 @@ _notmuch_message_ensure_message_file (notmuch_message_t *message)\r
579         return;\r
580  \r
581      message->message_file = _notmuch_message_file_open_ctx (message, filename);\r
582 +\r
583 +    /* XXX: better return value handling */\r
584 +    if (notmuch_message_file_parse (message->message_file)) {\r
585 +       notmuch_message_file_close (message->message_file);\r
586 +       message->message_file = NULL;\r
587 +    }\r
588  }\r
589  \r
590  const char *\r
591 diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h\r
592 index af185c7..7277df1 100644\r
593 --- a/lib/notmuch-private.h\r
594 +++ b/lib/notmuch-private.h\r
595 @@ -345,6 +345,10 @@ notmuch_message_file_open (const char *filename);\r
596  notmuch_message_file_t *\r
597  _notmuch_message_file_open_ctx (void *ctx, const char *filename);\r
598  \r
599 +/* Parse the message */\r
600 +notmuch_status_t\r
601 +notmuch_message_file_parse (notmuch_message_file_t *message);\r
602 +\r
603  /* Close a notmuch message previously opened with notmuch_message_open. */\r
604  void\r
605  notmuch_message_file_close (notmuch_message_file_t *message);\r
606 -- \r
607 1.8.5.2\r
608 \r