1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2000-2003 Ximian Inc.
5 * Authors: Michael Zucchi <notzed@ximian.com>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of version 2 of the GNU Lesser General Public
9 * License as published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
22 /* What should hopefully be a fast mail parser */
24 /* Do not change this code without asking me (Michael Zucchi) first
26 There is almost always a reason something was done a certain way.
34 #include <sys/types.h>
38 #include <libedataserver/e-memory.h>
40 #include "camel-mime-filter.h"
41 #include "camel-mime-parser.h"
42 #include "camel-mime-utils.h"
43 #include "camel-private.h"
44 #include "camel-seekable-stream.h"
45 #include "camel-stream.h"
52 /*#define PRESERVE_HEADERS*/
63 #define SCAN_BUF 4096 /* size of read buffer */
64 #define SCAN_HEAD 128 /* headroom guaranteed to be before each read buffer */
66 /* a little hacky, but i couldn't be bothered renaming everything */
67 #define _header_scan_state _CamelMimeParserPrivate
68 #define _PRIVATE(o) (((CamelMimeParser *)(o))->priv)
70 struct _header_scan_state {
74 camel_mime_parser_state_t state;
76 /* for building headers during scanning */
81 int fd; /* input for a fd input */
82 CamelStream *stream; /* or for a stream */
84 int ioerrno; /* io error state */
86 /* for scanning input buffers */
87 char *realbuf; /* the real buffer, SCAN_HEAD*2 + SCAN_BUF bytes */
88 char *inbuf; /* points to a subset of the allocated memory, the underflow */
89 char *inptr; /* (upto SCAN_HEAD) is for use by filters so they dont copy all data */
94 off_t seek; /* current offset to start of buffer */
95 int unstep; /* how many states to 'unstep' (repeat the current state) */
97 unsigned int midline:1; /* are we mid-line interrupted? */
98 unsigned int scan_from:1; /* do we care about From lines? */
99 unsigned int scan_pre_from:1; /* do we return pre-from data? */
100 unsigned int eof:1; /* reached eof? */
102 off_t start_of_from; /* where from started */
103 off_t start_of_boundary; /* where the last boundary started */
104 off_t start_of_headers; /* where headers started from the last scan */
106 off_t header_start; /* start of last header, or -1 */
108 /* filters to apply to all content before output */
109 int filterid; /* id of next filter */
110 struct _header_scan_filter *filters;
112 /* per message/part info */
113 struct _header_scan_stack *parts;
117 struct _header_scan_stack {
118 struct _header_scan_stack *parent;
120 camel_mime_parser_state_t savestate; /* state at invocation of this part */
123 EMemPool *pool; /* memory pool to keep track of headers/etc at this level */
125 struct _camel_header_raw *headers; /* headers for this part */
127 CamelContentType *content_type;
129 /* I dont use GString's casue you can't efficiently append a buffer to them */
130 GByteArray *pretext; /* for multipart types, save the pre-boundary data here */
131 GByteArray *posttext; /* for multipart types, save the post-boundary data here */
132 int prestage; /* used to determine if it is a pre-boundary or post-boundary data segment */
134 GByteArray *from_line; /* the from line */
136 char *boundary; /* for multipart/ * boundaries, including leading -- and trailing -- for the final part */
137 int boundarylen; /* actual length of boundary, including leading -- if there is one */
138 int boundarylenfinal; /* length of boundary, including trailing -- if there is one */
139 int atleast; /* the biggest boundary from here to the parent */
142 struct _header_scan_filter {
143 struct _header_scan_filter *next;
145 CamelMimeFilter *filter;
148 static void folder_scan_step(struct _header_scan_state *s, char **databuffer, size_t *datalength);
149 static void folder_scan_drop_step(struct _header_scan_state *s);
150 static int folder_scan_init_with_fd(struct _header_scan_state *s, int fd);
151 static int folder_scan_init_with_stream(struct _header_scan_state *s, CamelStream *stream);
152 static struct _header_scan_state *folder_scan_init(void);
153 static void folder_scan_close(struct _header_scan_state *s);
154 static struct _header_scan_stack *folder_scan_content(struct _header_scan_state *s, int *lastone, char **data, size_t *length);
155 static struct _header_scan_stack *folder_scan_header(struct _header_scan_state *s, int *lastone);
156 static int folder_scan_skip_line(struct _header_scan_state *s, GByteArray *save);
157 static off_t folder_seek(struct _header_scan_state *s, off_t offset, int whence);
158 static off_t folder_tell(struct _header_scan_state *s);
159 static int folder_read(struct _header_scan_state *s);
160 static void folder_push_part(struct _header_scan_state *s, struct _header_scan_stack *h);
163 static void header_append_mempool(struct _header_scan_state *s, struct _header_scan_stack *h, char *header, int offset);
166 static void camel_mime_parser_class_init (CamelMimeParserClass *klass);
167 static void camel_mime_parser_init (CamelMimeParser *obj);
170 static char *states[] = {
171 "CAMEL_MIME_PARSER_STATE_INITIAL",
172 "CAMEL_MIME_PARSER_STATE_PRE_FROM", /* pre-from data */
173 "CAMEL_MIME_PARSER_STATE_FROM", /* got 'From' line */
174 "CAMEL_MIME_PARSER_STATE_HEADER", /* toplevel header */
175 "CAMEL_MIME_PARSER_STATE_BODY", /* scanning body of message */
176 "CAMEL_MIME_PARSER_STATE_MULTIPART", /* got multipart header */
177 "CAMEL_MIME_PARSER_STATE_MESSAGE", /* rfc822/news message */
179 "CAMEL_MIME_PARSER_STATE_PART", /* part of a multipart */
181 "CAMEL_MIME_PARSER_STATE_EOF", /* end of file */
182 "CAMEL_MIME_PARSER_STATE_PRE_FROM_END",
183 "CAMEL_MIME_PARSER_STATE_FROM_END",
184 "CAMEL_MIME_PARSER_STATE_HEAER_END",
185 "CAMEL_MIME_PARSER_STATE_BODY_END",
186 "CAMEL_MIME_PARSER_STATE_MULTIPART_END",
187 "CAMEL_MIME_PARSER_STATE_MESSAGE_END",
191 static CamelObjectClass *camel_mime_parser_parent;
194 camel_mime_parser_class_init (CamelMimeParserClass *klass)
196 camel_mime_parser_parent = camel_type_get_global_classfuncs (camel_object_get_type ());
200 camel_mime_parser_init (CamelMimeParser *obj)
202 struct _header_scan_state *s;
204 s = folder_scan_init();
209 camel_mime_parser_finalise(CamelObject *o)
211 struct _header_scan_state *s = _PRIVATE(o);
213 purify_watch_remove_all();
215 folder_scan_close(s);
219 camel_mime_parser_get_type (void)
221 static CamelType type = CAMEL_INVALID_TYPE;
223 if (type == CAMEL_INVALID_TYPE) {
224 type = camel_type_register (camel_object_get_type (), "CamelMimeParser",
225 sizeof (CamelMimeParser),
226 sizeof (CamelMimeParserClass),
227 (CamelObjectClassInitFunc) camel_mime_parser_class_init,
229 (CamelObjectInitFunc) camel_mime_parser_init,
230 (CamelObjectFinalizeFunc) camel_mime_parser_finalise);
237 * camel_mime_parser_new:
239 * Create a new CamelMimeParser object.
241 * Return value: A new CamelMimeParser widget.
244 camel_mime_parser_new (void)
246 CamelMimeParser *new = CAMEL_MIME_PARSER ( camel_object_new (camel_mime_parser_get_type ()));
252 * camel_mime_parser_filter_add:
256 * Add a filter that will be applied to any body content before it is passed
257 * to the caller. Filters may be pipelined to perform multi-pass operations
258 * on the content, and are applied in the order they were added.
260 * Note that filters are only applied to the body content of messages, and once
261 * a filter has been set, all content returned by a filter_step() with a state
262 * of CAMEL_MIME_PARSER_STATE_BODY will have passed through the filter.
264 * Return value: An id that may be passed to filter_remove() to remove
265 * the filter, or -1 if the operation failed.
268 camel_mime_parser_filter_add(CamelMimeParser *m, CamelMimeFilter *mf)
270 struct _header_scan_state *s = _PRIVATE(m);
271 struct _header_scan_filter *f, *new;
273 new = g_malloc(sizeof(*new));
275 new->id = s->filterid++;
276 if (s->filterid == -1)
279 camel_object_ref((CamelObject *)mf);
281 /* yes, this is correct, since 'next' is the first element of the struct */
282 f = (struct _header_scan_filter *)&s->filters;
290 * camel_mime_parser_filter_remove:
294 * Remove a processing filter from the pipeline. There is no
295 * restriction on the order the filters can be removed.
298 camel_mime_parser_filter_remove(CamelMimeParser *m, int id)
300 struct _header_scan_state *s = _PRIVATE(m);
301 struct _header_scan_filter *f, *old;
303 f = (struct _header_scan_filter *)&s->filters;
304 while (f && f->next) {
307 camel_object_unref((CamelObject *)old->filter);
310 /* there should only be a single matching id, but
311 scan the whole lot anyway */
318 * camel_mime_parser_header:
320 * @name: Name of header.
321 * @offset: Pointer that can receive the offset of the header in
322 * the stream from the start of parsing.
324 * Lookup a header by name.
326 * Return value: The header value, or NULL if the header is not
330 camel_mime_parser_header(CamelMimeParser *m, const char *name, int *offset)
332 struct _header_scan_state *s = _PRIVATE(m);
336 return camel_header_raw_find(&s->parts->headers, name, offset);
342 * camel_mime_parser_headers_raw:
345 * Get the list of the raw headers which are defined for the
346 * current state of the parser. These headers are valid
347 * until the next call to parser_step(), or parser_drop_step().
349 * Return value: The raw headers, or NULL if there are no headers
350 * defined for the current part or state. These are READ ONLY.
352 struct _camel_header_raw *
353 camel_mime_parser_headers_raw(CamelMimeParser *m)
355 struct _header_scan_state *s = _PRIVATE(m);
358 return s->parts->headers;
363 byte_array_to_string(GByteArray *array)
368 if (array->len == 0 || array->data[array->len-1] != '\0')
369 g_byte_array_append(array, "", 1);
371 return (const char *) array->data;
375 * camel_mime_parser_preface:
378 * Retrieve the preface text for the current multipart.
379 * Can only be used when the state is CAMEL_MIME_PARSER_STATE_MULTIPART_END.
381 * Return value: The preface text, or NULL if there wasn't any.
384 camel_mime_parser_preface(CamelMimeParser *m)
386 struct _header_scan_state *s = _PRIVATE(m);
389 return byte_array_to_string(s->parts->pretext);
395 * camel_mime_parser_postface:
398 * Retrieve the postface text for the current multipart.
399 * Only returns valid data when the current state if
400 * CAMEL_MIME_PARSER_STATE_MULTIPART_END.
402 * Return value: The postface text, or NULL if there wasn't any.
405 camel_mime_parser_postface(CamelMimeParser *m)
407 struct _header_scan_state *s = _PRIVATE(m);
410 return byte_array_to_string(s->parts->posttext);
416 * camel_mime_parser_from_line:
419 * Get the last scanned "From " line, from a recently scanned from.
420 * This should only be called in the CAMEL_MIME_PARSER_STATE_FROM state. The
421 * from line will include the closing \n found (if there was one).
423 * The return value will remain valid while in the CAMEL_MIME_PARSER_STATE_FROM
424 * state, or any deeper state.
426 * Return value: The From line, or NULL if called out of context.
429 camel_mime_parser_from_line(CamelMimeParser *m)
431 struct _header_scan_state *s = _PRIVATE(m);
434 return byte_array_to_string(s->parts->from_line);
440 * camel_mime_parser_init_with_fd:
442 * @fd: A valid file descriptor.
444 * Initialise the scanner with an fd. The scanner's offsets
445 * will be relative to the current file position of the file
446 * descriptor. As a result, seekable descritors should
447 * be seeked using the parser seek functions.
449 * Return value: Returns -1 on error.
452 camel_mime_parser_init_with_fd(CamelMimeParser *m, int fd)
454 struct _header_scan_state *s = _PRIVATE(m);
456 return folder_scan_init_with_fd(s, fd);
460 * camel_mime_parser_init_with_stream:
464 * Initialise the scanner with a source stream. The scanner's
465 * offsets will be relative to the current file position of
466 * the stream. As a result, seekable streams should only
467 * be seeked using the parser seek function.
469 * Return value: -1 on error.
472 camel_mime_parser_init_with_stream(CamelMimeParser *m, CamelStream *stream)
474 struct _header_scan_state *s = _PRIVATE(m);
476 return folder_scan_init_with_stream(s, stream);
480 * camel_mime_parser_scan_from:
481 * @parser: MIME parser object
482 * @scan_from: #TRUE if the scanner should scan From lines.
484 * Tell the scanner if it should scan "^From " lines or not.
486 * If the scanner is scanning from lines, two additional
487 * states CAMEL_MIME_PARSER_STATE_FROM and CAMEL_MIME_PARSER_STATE_FROM_END will be returned
488 * to the caller during parsing.
490 * This may also be preceeded by an optional
491 * CAMEL_MIME_PARSER_STATE_PRE_FROM state which contains the scanned data
492 * found before the From line is encountered. See also
496 camel_mime_parser_scan_from (CamelMimeParser *parser, gboolean scan_from)
498 struct _header_scan_state *s = _PRIVATE (parser);
500 s->scan_from = scan_from;
504 * camel_mime_parser_scan_pre_from:
505 * @parser: MIME parser object
506 * @scan_pre_from: #TRUE if we want to get pre-from data.
508 * Tell the scanner whether we want to know abou the pre-from
509 * data during a scan. If we do, then we may get an additional
510 * state CAMEL_MIME_PARSER_STATE_PRE_FROM which returns the specified data.
513 camel_mime_parser_scan_pre_from (CamelMimeParser *parser, gboolean scan_pre_from)
515 struct _header_scan_state *s = _PRIVATE (parser);
517 s->scan_pre_from = scan_pre_from;
521 * camel_mime_parser_content_type:
522 * @parser: MIME parser object
524 * Get the content type defined in the current part.
526 * Return value: A content_type structure, or NULL if there
527 * is no content-type defined for this part of state of the
531 camel_mime_parser_content_type (CamelMimeParser *parser)
533 struct _header_scan_state *s = _PRIVATE (parser);
535 /* FIXME: should this search up until it's found the 'right'
536 content-type? can it? */
538 return s->parts->content_type;
544 * camel_mime_parser_unstep:
545 * @parser: MIME parser object
547 * Cause the last step operation to repeat itself. If this is
548 * called repeated times, then the same step will be repeated
551 * Note that it is not possible to scan back using this function,
552 * only to have a way of peeking the next state.
555 camel_mime_parser_unstep (CamelMimeParser *parser)
557 struct _header_scan_state *s = _PRIVATE (parser);
563 * camel_mime_parser_drop_step:
564 * @parser: MIME parser object
566 * Drop the last step call. This should only be used
567 * in conjunction with seeking of the stream as the
568 * stream may be in an undefined state relative to the
569 * state of the parser.
571 * Use this call with care.
574 camel_mime_parser_drop_step (CamelMimeParser *parser)
576 struct _header_scan_state *s = _PRIVATE (parser);
579 folder_scan_drop_step(s);
583 * camel_mime_parser_step:
584 * @parser: MIME parser object
585 * @databuffer: Pointer to accept a pointer to the data
586 * associated with this step (if any). May be #NULL,
587 * in which case datalength is also ingored.
588 * @datalength: Pointer to accept a pointer to the data
589 * length associated with this step (if any).
591 * Parse the next part of the MIME message. If _unstep()
592 * has been called, then continue to return the same state
593 * for that many calls.
595 * If the step is CAMEL_MIME_PARSER_STATE_BODY then the databuffer and datalength
596 * pointers will be setup to point to the internal data buffer
597 * of the scanner and may be processed as required. Any
598 * filters will have already been applied to this data.
600 * Refer to the state diagram elsewhere for a full listing of
601 * the states an application is gauranteed to get from the
604 * Return value: The current new state of the parser
607 camel_mime_parser_state_t
608 camel_mime_parser_step (CamelMimeParser *parser, char **databuffer, size_t *datalength)
610 struct _header_scan_state *s = _PRIVATE (parser);
612 d(printf("OLD STATE: '%s' :\n", states[s->state]));
614 if (s->unstep <= 0) {
618 if (databuffer == NULL) {
620 datalength = &dummylength;
623 folder_scan_step(s, databuffer, datalength);
627 d(printf("NEW STATE: '%s' :\n", states[s->state]));
633 * camel_mime_parser_read:
634 * @parser: MIME parser object
638 * Read at most @len bytes from the internal mime parser buffer.
640 * Returns the address of the internal buffer in @databuffer,
641 * and the length of useful data.
643 * @len may be specified as INT_MAX, in which case you will
644 * get the full remainder of the buffer at each call.
646 * Note that no parsing of the data read through this function
647 * occurs, so no state changes occur, but the seek position
648 * is updated appropriately.
650 * Return value: The number of bytes available, or -1 on error.
653 camel_mime_parser_read (CamelMimeParser *parser, const char **databuffer, int len)
655 struct _header_scan_state *s = _PRIVATE (parser);
661 d(printf("parser::read() reading %d bytes\n", len));
663 there = MIN(s->inend - s->inptr, len);
664 d(printf("parser::read() there = %d bytes\n", there));
666 *databuffer = s->inptr;
671 if (folder_read(s) == -1)
674 there = MIN(s->inend - s->inptr, len);
675 d(printf("parser::read() had to re-read, now there = %d bytes\n", there));
677 *databuffer = s->inptr;
684 * camel_mime_parser_tell:
685 * @parser: MIME parser object
687 * Return the current scanning offset. The meaning of this
688 * value will depend on the current state of the parser.
690 * An incomplete listing of the states:
692 * CAMEL_MIME_PARSER_STATE_INITIAL, The start of the current message.
693 * CAMEL_MIME_PARSER_STATE_HEADER, CAMEL_MIME_PARSER_STATE_MESSAGE, CAMEL_MIME_PARSER_STATE_MULTIPART, the character
694 * position immediately after the end of the header.
695 * CAMEL_MIME_PARSER_STATE_BODY, Position within the message of the start
696 * of the current data block.
697 * CAMEL_MIME_PARSER_STATE_*_END, The position of the character starting
698 * the next section of the scan (the last position + 1 of
699 * the respective current state).
701 * Return value: See above.
704 camel_mime_parser_tell (CamelMimeParser *parser)
706 struct _header_scan_state *s = _PRIVATE (parser);
708 return folder_tell(s);
712 * camel_mime_parser_tell_start_headers:
713 * @parser: MIME parser object
715 * Find out the position within the file of where the
716 * headers started, this is cached by the parser
719 * Return value: The header start position, or -1 if
720 * no headers were scanned in the current state.
723 camel_mime_parser_tell_start_headers (CamelMimeParser *parser)
725 struct _header_scan_state *s = _PRIVATE (parser);
727 return s->start_of_headers;
731 * camel_mime_parser_tell_start_from:
732 * @parser: MIME parser object
734 * If the parser is scanning From lines, then this returns
735 * the position of the start of the From line.
737 * Return value: The start of the from line, or -1 if there
738 * was no From line, or From lines are not being scanned.
741 camel_mime_parser_tell_start_from (CamelMimeParser *parser)
743 struct _header_scan_state *s = _PRIVATE (parser);
745 return s->start_of_from;
749 * camel_mime_parser_tell_start_boundary:
750 * @parser: MIME parser object
752 * When parsing a multipart, this returns the start of the last
755 * Return value: The start of the boundary, or -1 if there
756 * was no boundary encountered yet.
759 camel_mime_parser_tell_start_boundary(CamelMimeParser *parser)
761 struct _header_scan_state *s = _PRIVATE (parser);
763 return s->start_of_boundary;
767 * camel_mime_parser_seek:
768 * @parser: MIME parser object
769 * @offset: Number of bytes to offset the seek by.
770 * @whence: SEEK_SET, SEEK_CUR, SEEK_END
772 * Reset the source position to a known value.
774 * Note that if the source stream/descriptor was not
775 * positioned at 0 to begin with, and an absolute seek
776 * is specified (whence != SEEK_CUR), then the seek
777 * position may not match the desired seek position.
779 * Return value: The new seek offset, or -1 on
780 * an error (for example, trying to seek on a non-seekable
781 * stream or file descriptor).
784 camel_mime_parser_seek(CamelMimeParser *parser, off_t offset, int whence)
786 struct _header_scan_state *s = _PRIVATE (parser);
788 return folder_seek(s, offset, whence);
792 * camel_mime_parser_state:
793 * @parser: MIME parser object
795 * Get the current parser state.
797 * Return value: The current parser state.
799 camel_mime_parser_state_t
800 camel_mime_parser_state (CamelMimeParser *parser)
802 struct _header_scan_state *s = _PRIVATE (parser);
808 * camel_mime_parser_push_state:
809 * @mp: MIME parser object
810 * @newstate: New state
811 * @boundary: Boundary marker for state.
813 * Pre-load a new parser state. Used to post-parse multipart content
817 camel_mime_parser_push_state(CamelMimeParser *mp, camel_mime_parser_state_t newstate, const char *boundary)
819 struct _header_scan_stack *h;
820 struct _header_scan_state *s = _PRIVATE(mp);
822 h = g_malloc0(sizeof(*h));
823 h->boundarylen = strlen(boundary)+2;
824 h->boundarylenfinal = h->boundarylen+2;
825 h->boundary = g_malloc(h->boundarylen+3);
826 sprintf(h->boundary, "--%s--", boundary);
827 folder_push_part(s, h);
832 * camel_mime_parser_stream:
833 * @parser: MIME parser object
835 * Get the stream, if any, the parser has been initialised
836 * with. May be used to setup sub-streams, but should not
837 * be read from directly (without saving and restoring
838 * the seek position in between).
840 * Return value: The stream from _init_with_stream(), or NULL
841 * if the parser is reading from a file descriptor or is
845 camel_mime_parser_stream (CamelMimeParser *parser)
847 struct _header_scan_state *s = _PRIVATE (parser);
853 * camel_mime_parser_fd:
854 * @parser: MIME parser object
856 * Return the file descriptor, if any, the parser has been
859 * Should not be read from unless the parser it to terminate,
860 * or the seek offset can be reset before the next parse
863 * Return value: The file descriptor or -1 if the parser
864 * is reading from a stream or has not been initialised.
867 camel_mime_parser_fd (CamelMimeParser *parser)
869 struct _header_scan_state *s = _PRIVATE (parser);
874 /* Return errno of the parser, incase any error occured during processing */
876 camel_mime_parser_errno (CamelMimeParser *parser)
878 struct _header_scan_state *s = _PRIVATE (parser);
883 /* ********************************************************************** */
885 /* ********************************************************************** */
887 /* read the next bit of data, ensure there is enough room 'atleast' bytes */
889 folder_read(struct _header_scan_state *s)
894 if (s->inptr<s->inend-s->atleast || s->eof)
895 return s->inend-s->inptr;
897 purify_watch_remove(inend_id);
898 purify_watch_remove(inbuffer_id);
900 /* check for any remaning bytes (under the atleast limit( */
901 inoffset = s->inend - s->inptr;
903 memmove(s->inbuf, s->inptr, inoffset);
906 len = camel_stream_read(s->stream, s->inbuf+inoffset, SCAN_BUF-inoffset);
908 len = read(s->fd, s->inbuf+inoffset, SCAN_BUF-inoffset);
910 r(printf("read %d bytes, offset = %d\n", len, inoffset));
912 /* add on the last read block */
913 s->seek += s->inptr - s->inbuf;
915 s->inend = s->inbuf+len+inoffset;
917 r(printf("content = %d '%.*s'\n",s->inend - s->inptr, s->inend - s->inptr, s->inptr));
919 s->ioerrno = errno?errno:EIO;
922 g_assert(s->inptr<=s->inend);
924 inend_id = purify_watch(&s->inend);
925 inbuffer_id = purify_watch_n(s->inend+1, SCAN_HEAD-1, "rw");
927 r(printf("content = %d '%.*s'\n", s->inend - s->inptr, s->inend - s->inptr, s->inptr));
928 /* set a sentinal, for the inner loops to check against */
930 return s->inend-s->inptr;
933 /* return the current absolute position of the data pointer */
935 folder_tell(struct _header_scan_state *s)
937 return s->seek + (s->inptr - s->inbuf);
941 need some way to prime the parser state, so this actually works for
942 other than top-level messages
945 folder_seek(struct _header_scan_state *s, off_t offset, int whence)
950 if (CAMEL_IS_SEEKABLE_STREAM(s->stream)) {
951 /* NOTE: assumes whence seekable stream == whence libc, which is probably
952 the case (or bloody well should've been) */
953 newoffset = camel_seekable_stream_seek((CamelSeekableStream *)s->stream, offset, whence);
959 newoffset = lseek(s->fd, offset, whence);
962 purify_watch_remove(inend_id);
963 purify_watch_remove(inbuffer_id);
965 if (newoffset != -1) {
971 s->ioerrno = errno?errno:EIO;
974 inend_id = purify_watch(&s->inend);
975 inbuffer_id = purify_watch_n(s->inend+1, SCAN_HEAD-1, "rw");
981 folder_push_part(struct _header_scan_state *s, struct _header_scan_stack *h)
983 if (s->parts && s->parts->atleast > h->boundarylenfinal)
984 h->atleast = s->parts->atleast;
986 h->atleast = MAX(h->boundarylenfinal, 1);
988 h->parent = s->parts;
993 folder_pull_part(struct _header_scan_state *s)
995 struct _header_scan_stack *h;
999 s->parts = h->parent;
1000 g_free(h->boundary);
1002 e_mempool_destroy(h->pool);
1004 camel_header_raw_clear(&h->headers);
1006 camel_content_type_unref(h->content_type);
1008 g_byte_array_free(h->pretext, TRUE);
1010 g_byte_array_free(h->posttext, TRUE);
1012 g_byte_array_free(h->from_line, TRUE);
1015 g_warning("Header stack underflow!\n");
1020 folder_scan_skip_line(struct _header_scan_state *s, GByteArray *save)
1022 int atleast = s->atleast;
1023 register char *inptr, *inend, c;
1028 d(printf("skipping line\n"));
1030 while ( (len = folder_read(s)) > 0 && len > s->atleast) { /* ensure we have at least enough room here */
1036 && (c = *inptr++)!='\n') {
1037 d(printf("(%2x,%c)", c, isprint(c)?c:'.'));
1042 g_byte_array_append(save, s->inptr, inptr-s->inptr);
1047 s->atleast = atleast;
1052 d(printf("couldn't find end of line?\n"));
1054 s->atleast = atleast;
1056 return -1; /* not found */
1059 /* TODO: Is there any way to make this run faster? It gets called a lot ... */
1060 static struct _header_scan_stack *
1061 folder_boundary_check(struct _header_scan_state *s, const char *boundary, int *lastone)
1063 struct _header_scan_stack *part;
1064 int len = s->inend - boundary; /* make sure we dont access past the buffer */
1066 h(printf("checking boundary marker upto %d bytes\n", len));
1069 h(printf(" boundary: %s\n", part->boundary));
1070 h(printf(" against: '%.*s'\n", part->boundarylen, boundary));
1072 && part->boundarylen <= len
1073 && memcmp(boundary, part->boundary, part->boundarylen)==0) {
1074 h(printf("matched boundary: %s\n", part->boundary));
1075 /* again, make sure we're in range */
1076 if (part->boundarylenfinal <= len) {
1077 int extra = part->boundarylenfinal - part->boundarylen;
1079 /* check the extra stuff on an final boundary, normally -- for mime parts */
1081 *lastone = memcmp(&boundary[part->boundarylen],
1082 &part->boundary[part->boundarylen],
1087 h(printf("checking lastone = %s\n", *lastone?"TRUE":"FALSE"));
1089 h(printf("not enough room to check last one?\n"));
1092 /*printf("ok, we found it! : %s \n", (*lastone)?"Last one":"More to come?");*/
1095 part = part->parent;
1102 header_append_mempool(struct _header_scan_state *s, struct _header_scan_stack *h, char *header, int offset)
1104 struct _camel_header_raw *l, *n;
1107 content = strchr(header, ':');
1110 n = e_mempool_alloc(h->pool, sizeof(*n));
1113 len = content-header;
1114 n->name = e_mempool_alloc(h->pool, len+1);
1115 memcpy(n->name, header, len);
1120 len = s->outptr - content;
1121 n->value = e_mempool_alloc(h->pool, len+1);
1122 memcpy(n->value, content, len);
1127 l = (struct _camel_header_raw *)&h->headers;
1136 #define header_raw_append_parse(a, b, c) (header_append_mempool(s, h, b, c))
1140 /* Copy the string start->inptr into the header buffer (s->outbuf),
1142 remove trailing \r chars (\n's assumed already removed)
1143 and track the start offset of the header */
1144 /* Basically an optimised version of g_byte_array_append() */
1145 #define header_append(s, start, inptr) \
1147 register int headerlen = inptr-start; \
1149 if (headerlen > 0) { \
1150 if (headerlen >= (s->outend - s->outptr)) { \
1151 register char *outnew; \
1152 register int olen = ((s->outend - s->outbuf) + headerlen) * 2 + 1; \
1153 outnew = g_realloc(s->outbuf, olen); \
1154 s->outptr = s->outptr - s->outbuf + outnew; \
1155 s->outbuf = outnew; \
1156 s->outend = outnew + olen; \
1158 if (start[headerlen-1] == '\r') \
1160 memcpy(s->outptr, start, headerlen); \
1161 s->outptr += headerlen; \
1163 if (s->header_start == -1) \
1164 s->header_start = (start-s->inbuf) + s->seek; \
1167 static struct _header_scan_stack *
1168 folder_scan_header(struct _header_scan_state *s, int *lastone)
1170 int atleast = s->atleast, newatleast;
1173 struct _header_scan_stack *h;
1175 register char *inptr;
1177 h(printf("scanning first bit\n"));
1179 h = g_malloc0(sizeof(*h));
1181 h->pool = e_mempool_new(8192, 4096, E_MEMPOOL_ALIGN_STRUCT);
1185 newatleast = s->parts->atleast;
1191 s->atleast = newatleast;
1193 h(printf("atleast = %d\n", s->atleast));
1195 while ((len = folder_read(s))>0 && len >= s->atleast) { /* ensure we have at least enough room here */
1197 inend = s->inend-s->atleast+1;
1199 while (inptr<inend) {
1201 if (folder_boundary_check(s, inptr, lastone)) {
1202 if ((s->outptr>s->outbuf))
1203 goto header_truncated; /* may not actually be truncated */
1211 /* goto next line/sentinal */
1212 while ((*inptr++)!='\n')
1215 g_assert(inptr<=s->inend+1);
1217 /* check for sentinal or real end of line */
1218 if (inptr > inend) {
1219 h(printf("not at end of line yet, going further\n"));
1220 /* didn't find end of line within our allowed area */
1223 header_append(s, start, inptr);
1225 h(printf("got line part: '%.*s'\n", inptr-1-start, start));
1226 /* got a line, strip and add it, process it */
1228 #ifdef PRESERVE_HEADERS
1229 header_append(s, start, inptr);
1231 header_append(s, start, inptr-1);
1233 /* check for end of headers */
1234 if (s->outbuf == s->outptr)
1237 /* check for continuation/compress headers, we have atleast 1 char here to work with */
1238 if (inptr[0] == ' ' || inptr[0] == '\t') {
1239 h(printf("continuation\n"));
1240 #ifndef PRESERVE_HEADERS
1241 /* TODO: this wont catch multiple space continuation across a read boundary, but
1242 that is assumed rare, and not fatal anyway */
1245 while (*inptr == ' ' || *inptr == '\t');
1250 /* otherwise, complete header, add it */
1251 #ifdef PRESERVE_HEADERS
1253 if (s->outptr[-1] == '\r')
1258 h(printf("header '%s' at %d\n", s->outbuf, (int)s->header_start));
1260 header_raw_append_parse(&h->headers, s->outbuf, s->header_start);
1261 s->outptr = s->outbuf;
1262 s->header_start = -1;
1268 h(printf("end of file? read %d bytes\n", len));
1270 } while (s->atleast > 1);
1272 if ((s->outptr > s->outbuf) || s->inend > s->inptr) {
1275 if (inptr > start) {
1276 if (inptr[-1] == '\n')
1279 goto header_truncated;
1282 s->atleast = atleast;
1287 header_append(s, start, inptr);
1290 if (s->outbuf == s->outptr)
1293 header_raw_append_parse(&h->headers, s->outbuf, s->header_start);
1295 s->outptr = s->outbuf;
1298 s->atleast = atleast;
1299 s->header_start = -1;
1303 static struct _header_scan_stack *
1304 folder_scan_content(struct _header_scan_state *s, int *lastone, char **data, size_t *length)
1306 int atleast = s->atleast, newatleast;
1307 register char *inptr;
1311 struct _header_scan_stack *part;
1312 int onboundary = FALSE;
1314 c(printf("scanning content\n"));
1318 newatleast = part->atleast;
1323 c(printf("atleast = %d\n", newatleast));
1326 s->atleast = newatleast;
1328 while ((len = folder_read(s))>0 && len >= s->atleast) { /* ensure we have at least enough room here */
1333 inend = s->inend-s->atleast+1;
1336 c(printf("inptr = %p, inend = %p\n", inptr, inend));
1338 while (inptr<inend) {
1340 && (part = folder_boundary_check(s, inptr, lastone))) {
1343 /* since we truncate the boundary data, we need at least 1 char here spare,
1344 to remain in the same state */
1345 if ( (inptr-start) > 1)
1348 /* otherwise, jump to the state of the boundary we actually found */
1352 /* goto the next line */
1353 while ((*inptr++)!='\n')
1356 /* check the sentinal, if we went past the atleast limit, and reset it to there */
1357 if (inptr > inend) {
1365 c(printf("ran out of input, dumping what i have (%d) bytes midline = %s\n",
1366 inptr-start, s->midline?"TRUE":"FALSE"));
1370 } while (s->atleast > 1);
1372 c(printf("length read = %d\n", len));
1374 if (s->inend > s->inptr) {
1382 s->atleast = atleast;
1386 /* treat eof as the last boundary in From mode */
1387 if (s->scan_from && s->eof && s->atleast <= 1) {
1394 s->atleast = atleast;
1398 /* if we hit a boundary, we should not include the closing \n */
1399 if (onboundary && (inptr-start)>0)
1400 *length = inptr-start-1;
1402 *length = inptr-start;
1404 /*printf("got %scontent: '%.*s'\n", s->midline?"partial ":"", inptr-start, start);*/
1411 folder_scan_close(struct _header_scan_state *s)
1416 folder_pull_part(s);
1420 camel_object_unref((CamelObject *)s->stream);
1426 static struct _header_scan_state *
1427 folder_scan_init(void)
1429 struct _header_scan_state *s;
1431 s = g_malloc(sizeof(*s));
1437 s->outbuf = g_malloc(1024);
1438 s->outptr = s->outbuf;
1439 s->outend = s->outbuf+1024;
1441 s->realbuf = g_malloc(SCAN_BUF + SCAN_HEAD*2);
1442 s->inbuf = s->realbuf + SCAN_HEAD;
1443 s->inptr = s->inbuf;
1444 s->inend = s->inbuf;
1447 s->seek = 0; /* current character position in file of the last read block */
1450 s->header_start = -1;
1452 s->start_of_from = -1;
1453 s->start_of_headers = -1;
1454 s->start_of_boundary = -1;
1457 s->scan_from = FALSE;
1458 s->scan_pre_from = FALSE;
1466 s->state = CAMEL_MIME_PARSER_STATE_INITIAL;
1471 drop_states(struct _header_scan_state *s)
1474 folder_scan_drop_step(s);
1477 s->state = CAMEL_MIME_PARSER_STATE_INITIAL;
1481 folder_scan_reset(struct _header_scan_state *s)
1484 s->inend = s->inbuf;
1485 s->inptr = s->inbuf;
1492 camel_object_unref((CamelObject *)s->stream);
1500 folder_scan_init_with_fd(struct _header_scan_state *s, int fd)
1502 folder_scan_reset(s);
1509 folder_scan_init_with_stream(struct _header_scan_state *s, CamelStream *stream)
1511 folder_scan_reset(s);
1513 camel_object_ref((CamelObject *)stream);
1521 folder_scan_step(struct _header_scan_state *s, char **databuffer, size_t *datalength)
1523 struct _header_scan_stack *h, *hb;
1524 const char *content;
1526 int type, state, seenlast;
1527 CamelContentType *ct = NULL;
1528 struct _header_scan_filter *f;
1531 /* printf("\nSCAN PASS: state = %d '%s'\n", s->state, states[s->state]);*/
1535 printf("\nSCAN STACK:\n");
1536 printf(" '%s' :\n", states[s->state]);
1539 printf(" '%s' : %s ", states[hb->savestate], hb->boundary);
1540 if (hb->content_type) {
1541 printf("(%s/%s)", hb->content_type->type, hb->content_type->subtype);
1543 printf("(default)");
1554 case CAMEL_MIME_PARSER_STATE_INITIAL:
1556 h = g_malloc0(sizeof(*h));
1557 h->boundary = g_strdup("From ");
1558 h->boundarylen = strlen(h->boundary);
1559 h->boundarylenfinal = h->boundarylen;
1560 h->from_line = g_byte_array_new();
1561 folder_push_part(s, h);
1562 s->state = CAMEL_MIME_PARSER_STATE_PRE_FROM;
1564 s->start_of_from = -1;
1568 case CAMEL_MIME_PARSER_STATE_PRE_FROM:
1572 hb = folder_scan_content(s, &state, databuffer, datalength);
1573 if (s->scan_pre_from && *datalength > 0) {
1574 d(printf("got pre-from content %d bytes\n", *datalength));
1577 } while (hb==h && *datalength>0);
1579 if (*datalength==0 && hb==h) {
1580 d(printf("found 'From '\n"));
1581 s->start_of_from = folder_tell(s);
1582 folder_scan_skip_line(s, h->from_line);
1583 h->savestate = CAMEL_MIME_PARSER_STATE_INITIAL;
1584 s->state = CAMEL_MIME_PARSER_STATE_FROM;
1586 folder_pull_part(s);
1587 s->state = CAMEL_MIME_PARSER_STATE_EOF;
1591 case CAMEL_MIME_PARSER_STATE_INITIAL:
1592 case CAMEL_MIME_PARSER_STATE_PRE_FROM:
1593 #endif /* !USE_FROM */
1596 case CAMEL_MIME_PARSER_STATE_FROM:
1597 s->start_of_headers = folder_tell(s);
1598 h = folder_scan_header(s, &state);
1601 h->savestate = CAMEL_MIME_PARSER_STATE_FROM_END;
1604 h->savestate = CAMEL_MIME_PARSER_STATE_EOF;
1606 /* FIXME: should this check for MIME-Version: 1.0 as well? */
1608 type = CAMEL_MIME_PARSER_STATE_HEADER;
1609 if ( (content = camel_header_raw_find(&h->headers, "Content-Type", NULL))
1610 && (ct = camel_content_type_decode(content))) {
1611 if (!g_ascii_strcasecmp(ct->type, "multipart")) {
1612 if (!camel_content_type_is(ct, "multipart", "signed")
1613 && (bound = camel_content_type_param(ct, "boundary"))) {
1614 d(printf("multipart, boundary = %s\n", bound));
1615 h->boundarylen = strlen(bound)+2;
1616 h->boundarylenfinal = h->boundarylen+2;
1617 h->boundary = g_malloc(h->boundarylen+3);
1618 sprintf(h->boundary, "--%s--", bound);
1619 type = CAMEL_MIME_PARSER_STATE_MULTIPART;
1621 /*camel_content_type_unref(ct);
1622 ct = camel_content_type_decode("text/plain");*/
1623 /* We can't quite do this, as it will mess up all the offsets ... */
1624 /* camel_header_raw_replace(&h->headers, "Content-Type", "text/plain", offset);*/
1625 /*g_warning("Multipart with no boundary, treating as text/plain");*/
1627 } else if (!g_ascii_strcasecmp(ct->type, "message")) {
1628 if (!g_ascii_strcasecmp(ct->subtype, "rfc822")
1629 || !g_ascii_strcasecmp(ct->subtype, "news")
1630 /*|| !g_ascii_strcasecmp(ct->subtype, "partial")*/) {
1631 type = CAMEL_MIME_PARSER_STATE_MESSAGE;
1635 /* make the default type for multipart/digest be message/rfc822 */
1637 && camel_content_type_is(s->parts->content_type, "multipart", "digest"))) {
1638 ct = camel_content_type_decode("message/rfc822");
1639 type = CAMEL_MIME_PARSER_STATE_MESSAGE;
1640 d(printf("parent was multipart/digest, autoupgrading to message/rfc822?\n"));
1641 /* maybe we should do this too?
1642 header_raw_append_parse(&h->headers, "Content-Type: message/rfc822", -1);*/
1644 ct = camel_content_type_decode("text/plain");
1647 h->content_type = ct;
1648 folder_push_part(s, h);
1652 case CAMEL_MIME_PARSER_STATE_HEADER:
1653 s->state = CAMEL_MIME_PARSER_STATE_BODY;
1655 case CAMEL_MIME_PARSER_STATE_BODY:
1658 presize = SCAN_HEAD;
1662 hb = folder_scan_content (s, &state, databuffer, datalength);
1664 d(printf ("\n\nOriginal content: '"));
1665 d(fwrite(*databuffer, sizeof(char), *datalength, stdout));
1668 if (*datalength > 0) {
1670 camel_mime_filter_filter(f->filter, *databuffer, *datalength, presize,
1671 databuffer, datalength, &presize);
1672 d(printf("Filtered content (%s): '", ((CamelObject *)f->filter)->klass->name));
1673 d(fwrite(*databuffer, sizeof(char), *datalength, stdout));
1679 } while (hb == h && *datalength > 0);
1681 /* check for any filter completion data */
1683 camel_mime_filter_complete(f->filter, *databuffer, *datalength, presize,
1684 databuffer, datalength, &presize);
1688 if (*datalength > 0)
1691 s->state = CAMEL_MIME_PARSER_STATE_BODY_END;
1694 case CAMEL_MIME_PARSER_STATE_MULTIPART:
1696 /* This mess looks for the next boundary on this
1697 level. Once it finds the last one, it keeps going,
1698 looking for post-multipart content ('postface').
1699 Because messages might have duplicate boundaries for
1700 different parts, it makes sure it stops if its already
1701 found an end boundary for this part. It handles
1702 truncated and missing boundaries appropriately too. */
1706 hb = folder_scan_content(s, &state, databuffer, datalength);
1707 if (*datalength>0) {
1708 /* instead of a new state, we'll just store it locally and provide
1709 an accessor function */
1710 d(printf("Multipart %s Content %p: '%.*s'\n",
1711 h->prestage>0?"post":"pre", h, *datalength, *databuffer));
1712 if (h->prestage > 0) {
1713 if (h->posttext == NULL)
1714 h->posttext = g_byte_array_new();
1715 g_byte_array_append(h->posttext, *databuffer, *datalength);
1717 if (h->pretext == NULL)
1718 h->pretext = g_byte_array_new();
1719 g_byte_array_append(h->pretext, *databuffer, *datalength);
1722 } while (hb==h && *datalength>0);
1724 if (*datalength==0 && hb==h && !seenlast) {
1725 d(printf("got boundary: %s last=%d\n", hb->boundary, state));
1726 s->start_of_boundary = folder_tell(s);
1727 folder_scan_skip_line(s, NULL);
1729 s->state = CAMEL_MIME_PARSER_STATE_FROM;
1730 folder_scan_step(s, databuffer, datalength);
1731 s->parts->savestate = CAMEL_MIME_PARSER_STATE_MULTIPART; /* set return state for the new head part */
1740 s->state = CAMEL_MIME_PARSER_STATE_MULTIPART_END;
1743 case CAMEL_MIME_PARSER_STATE_MESSAGE:
1744 s->state = CAMEL_MIME_PARSER_STATE_FROM;
1745 folder_scan_step(s, databuffer, datalength);
1746 s->parts->savestate = CAMEL_MIME_PARSER_STATE_MESSAGE_END;
1749 case CAMEL_MIME_PARSER_STATE_FROM_END:
1750 case CAMEL_MIME_PARSER_STATE_BODY_END:
1751 case CAMEL_MIME_PARSER_STATE_MULTIPART_END:
1752 case CAMEL_MIME_PARSER_STATE_MESSAGE_END:
1753 s->state = s->parts->savestate;
1754 folder_pull_part(s);
1755 if (s->state & CAMEL_MIME_PARSER_STATE_END)
1759 case CAMEL_MIME_PARSER_STATE_EOF:
1763 g_warning ("Invalid state in camel-mime-parser: %u", s->state);
1770 /* drops the current state back one */
1772 folder_scan_drop_step(struct _header_scan_state *s)
1775 case CAMEL_MIME_PARSER_STATE_EOF:
1776 s->state = CAMEL_MIME_PARSER_STATE_INITIAL;
1777 case CAMEL_MIME_PARSER_STATE_INITIAL:
1780 case CAMEL_MIME_PARSER_STATE_FROM:
1781 case CAMEL_MIME_PARSER_STATE_PRE_FROM:
1782 s->state = CAMEL_MIME_PARSER_STATE_INITIAL;
1783 folder_pull_part(s);
1786 case CAMEL_MIME_PARSER_STATE_MESSAGE:
1787 case CAMEL_MIME_PARSER_STATE_HEADER:
1788 case CAMEL_MIME_PARSER_STATE_MULTIPART:
1790 case CAMEL_MIME_PARSER_STATE_FROM_END:
1791 case CAMEL_MIME_PARSER_STATE_BODY_END:
1792 case CAMEL_MIME_PARSER_STATE_MULTIPART_END:
1793 case CAMEL_MIME_PARSER_STATE_MESSAGE_END:
1795 s->state = s->parts->savestate;
1796 folder_pull_part(s);
1797 if (s->state & CAMEL_MIME_PARSER_STATE_END) {
1798 s->state &= ~CAMEL_MIME_PARSER_STATE_END;
1802 /* FIXME: not sure if this is entirely right */
1808 int main(int argc, char **argv)
1811 struct _header_scan_state *s;
1815 char *name = "/tmp/evmail/Inbox";
1816 struct _header_scan_stack *h;
1823 printf("opening: %s", name);
1825 for (i=1;i<argc;i++) {
1826 const char *encoding = NULL, *charset = NULL;
1830 printf("opening: %s", name);
1832 fd = g_open(name, O_RDONLY|O_BINARY, 0);
1834 perror("Cannot open mailbox");
1837 s = folder_scan_init();
1838 folder_scan_init_with_fd(s, fd);
1839 s->scan_from = FALSE;
1841 h = g_malloc0(sizeof(*h));
1842 h->savestate = CAMEL_MIME_PARSER_STATE_EOF;
1843 folder_push_part(s, h);
1845 while (s->state != CAMEL_MIME_PARSER_STATE_EOF) {
1846 folder_scan_step(s, &data, &len);
1847 printf("\n -- PARSER STEP RETURN -- %d '%s'\n\n", s->state, states[s->state]);
1849 case CAMEL_MIME_PARSER_STATE_HEADER:
1850 if (s->parts->content_type
1851 && (charset = camel_content_type_param(s->parts->content_type, "charset"))) {
1852 if (g_ascii_strcasecmp(charset, "us-ascii")) {
1854 folder_push_filter_charset(s, "UTF-8", charset);
1863 encoding = camel_header_raw_find(&s->parts->headers, "Content-transfer-encoding", 0);
1864 printf("encoding = '%s'\n", encoding);
1865 if (encoding && !g_ascii_strncasecmp(encoding, " base64", 7)) {
1866 printf("adding base64 filter\n");
1867 attachname = g_strdup_printf("attach.%d.%d", i, attach++);
1869 folder_push_filter_save(s, attachname);
1873 folder_push_filter_mime(s, 0);
1876 if (encoding && !g_ascii_strncasecmp(encoding, " quoted-printable", 17)) {
1877 printf("adding quoted-printable filter\n");
1878 attachname = g_strdup_printf("attach.%d.%d", i, attach++);
1880 folder_push_filter_save(s, attachname);
1884 folder_push_filter_mime(s, 1);
1889 case CAMEL_MIME_PARSER_STATE_BODY:
1890 printf("got body %d '%.*s'\n", len, len, data);
1892 case CAMEL_MIME_PARSER_STATE_BODY_END:
1893 printf("end body %d '%.*s'\n", len, len, data);
1894 if (encoding && !g_ascii_strncasecmp(encoding, " base64", 7)) {
1895 printf("removing filters\n");
1897 folder_filter_pull(s);
1898 folder_filter_pull(s);
1901 if (encoding && !g_ascii_strncasecmp(encoding, " quoted-printable", 17)) {
1902 printf("removing filters\n");
1904 folder_filter_pull(s);
1905 folder_filter_pull(s);
1910 folder_filter_pull(s);
1920 folder_scan_close(s);
1926 #endif /* STANDALONE */