]> code.citadel.org Git - citadel.git/blob - citadel/mime_parser.c
Finished the mime parser, but there's a bug in it somewhere...
[citadel.git] / citadel / mime_parser.c
1 /*
2  * mime_parser.c
3  *
4  * This is a really bad attempt at writing a parser to handle MIME-encoded
5  * messages.
6  *
7  * Copyright (c) 1998-1999 by Art Cancro
8  * This code is distributed under the terms of the GNU General Public License.
9  *
10  */
11
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <stdio.h>
15 #include <signal.h>
16 #include <sys/types.h>
17 #include <ctype.h>
18 #include <string.h>
19 #include <sys/stat.h>
20 #include <pthread.h>
21 #include "citadel.h"
22 #include "mime_parser.h"
23 #include "sysdep_decls.h"
24 #include "server.h"
25
26
27
28 void extract_key(char *target, char *source, char *key) {
29         int a, b;
30
31         strcpy(target, source);
32         for (a=0; a<strlen(target); ++a) {
33                 if ((!strncasecmp(&target[a], key, strlen(key)))
34                    && (target[a+strlen(key)]=='=')) {
35                         strcpy(target, &target[a+strlen(key)+1]);
36                         if (target[0]==34) strcpy(target, &target[1]);
37                         for (b=0; b<strlen(target); ++b)
38                                 if (target[b]==34) target[b]=0;
39                         return;
40                         }
41                 }
42         strcpy(target, "");
43         }
44
45
46
47 /* 
48  * Utility function to "readline" from memory
49  * (returns new pointer)
50  */
51 char *memreadline(char *start, char *buf, int maxlen) {
52         char ch;
53         char *ptr;
54
55         ptr = start;
56         memset(buf, 0, maxlen);
57
58         while(1) {
59                 ch = *ptr++;
60                 if ((ch==10)||(ch==0)) {
61                         if (strlen(buf)>0)
62                                 if (buf[strlen(buf)-1]==13)
63                                         buf[strlen(buf)-1] = 0;
64                         return ptr;
65                         }
66                 if (strlen(buf) < (maxlen-1)) {
67                         buf[strlen(buf)+1] = 0;
68                         buf[strlen(buf)] = ch;
69                         }
70                 }
71         }
72
73 /*
74  * Given a message or message-part body and a length, handle any necessary
75  * decoding and pass the request up the stack.
76  */
77 void mime_decode(char *partnum,
78                 char *part_start, size_t length,
79                 char *content_type, char *encoding,
80                 char *disposition,
81                 char *name, char *filename,
82                 void (*CallBack)
83                         (char *cbname,
84                         char *cbfilename,
85                         char *cbpartnum,
86                         char *cbdisp,
87                         void *cbcontent,
88                         char *cbtype,
89                         size_t cblength)
90                 ) {
91
92         char *decoded;
93         struct stat statbuf;
94         int sendpipe[2];
95         int recvpipe[2];
96         int childpid;
97         size_t bytes_sent = 0;
98         size_t bytes_recv = 0;
99         size_t blocksize;
100         int write_error = 0;
101
102         lprintf(9, "mime_decode() called\n");
103
104         /* Some encodings aren't really encodings */
105         if (!strcasecmp(encoding, "7bit"))      strcpy(encoding, "");
106         if (!strcasecmp(encoding, "8bit"))      strcpy(encoding, "");
107         if (!strcasecmp(encoding, "binary"))    strcpy(encoding, "");
108
109         /* If this part is not encoded, send as-is */
110         if (strlen(encoding)==0) {
111                 CallBack(name, filename, partnum, disposition, part_start,
112                         content_type, length);
113                 return;
114                 }
115
116         if ( (strcasecmp(encoding, "base64"))
117              && (strcasecmp(encoding, "quoted-printable"))  ) {
118                 lprintf(5, "ERROR: unknown MIME encoding '%s'\n", encoding);
119                 return;
120                 }
121
122         /*
123          * Allocate a buffer for the decoded data.  The output buffer is the
124          * same size as the input buffer; this assumes that the decoded data
125          * will never be larger than the encoded data.  This is a safe
126          * assumption with base64, uuencode, and quoted-printable.
127          */
128         decoded = mallok(length);
129         if (decoded == NULL) {
130                 lprintf(5, "ERROR: cannot allocate memory.\n");
131                 return;
132                 }
133         if (pipe(sendpipe) != 0) return;
134         if (pipe(recvpipe) != 0) return;
135
136         childpid = fork();
137         if (childpid < 0) {
138                 phree(decoded);
139                 return;
140                 }
141
142         if (childpid == 0) {
143                 /* close(2); FIX uncomment this when solid */
144                 /* send stdio to the pipes */
145                 if (dup2(sendpipe[0], 0)<0) lprintf(5, "ERROR dup2()\n");
146                 if (dup2(recvpipe[1], 1)<0) lprintf(5, "ERROR dup2()\n");
147                 close(sendpipe[1]);      /* Close the ends we're not using */
148                 close(recvpipe[0]);
149                 if (!strcasecmp(encoding, "base64"))
150                    execlp("./base64", "base64", "-d", NULL);
151                 else if (!strcasecmp(encoding, "quoted-printable"))
152                    execlp("./qpdecode", "qpdecode", NULL);
153                 lprintf(5, "ERROR: cannot exec decoder for %s\n", encoding);
154                 exit(1);
155                 }
156
157         close(sendpipe[0]);      /* Close the ends we're not using  */
158         close(recvpipe[1]);
159
160         while ( (bytes_sent < length) && (write_error == 0) ) {
161                 /* Empty the input pipe FIRST */
162                 while (fstat(recvpipe[0], &statbuf), (statbuf.st_size > 0) ) {
163                         blocksize = read(recvpipe[0], &decoded[bytes_recv],
164                                 statbuf.st_size);
165                         if (blocksize < 0) 
166                                 lprintf(5, "ERROR: cannot read from pipe\n");
167                         else
168                                 bytes_recv = bytes_recv + blocksize;
169                         }
170                 /* Then put some data into the output pipe */
171                 blocksize = length - bytes_sent;
172                 if (blocksize > 2048) blocksize = 2048;
173                 if (write(sendpipe[1], &part_start[bytes_sent], blocksize) <0) {
174                         lprintf(5, "ERROR: cannot write to pipe: %s\n",
175                                 strerror(errno));
176                         write_error = 1;
177                         }
178                 bytes_sent = bytes_sent + blocksize;
179                 }
180         close(sendpipe[1]);
181         /* Empty the input pipe */
182         while ( (blocksize = read(recvpipe[0], &decoded[bytes_recv], 1)),
183               (blocksize > 0) )  {
184                 bytes_recv = bytes_recv + blocksize;
185                 }
186
187         if (bytes_recv > 0)
188                 CallBack(name, filename, partnum, disposition, decoded,
189                         content_type, bytes_recv);
190         phree(decoded);
191         }
192
193 /*
194  * Break out the components of a multipart message
195  * (This function expects to be fed HEADERS + CONTENT)
196  * Note: NULL can be supplied as content_end; in this case, the message is
197  * considered to have ended when the parser encounters a 0x00 byte.
198  */
199 void the_mime_parser(char *partnum,
200                 char *content_start, char *content_end,
201                 void (*CallBack)
202                         (char *cbname,
203                         char *cbfilename,
204                         char *cbpartnum,
205                         char *cbdisp,
206                         void *cbcontent,
207                         char *cbtype,
208                         size_t cblength)
209                 ) {
210
211         char *ptr;
212         char *part_start, *part_end;
213         char buf[256];
214         char header[256];
215         char boundary[256];
216         char startary[256];
217         char endary[256];
218         char content_type[256];
219         char encoding[256];
220         char disposition[256];
221         char name[256];
222         char filename[256];
223         int is_multipart;
224         int part_seq = 0;
225         int i;
226         size_t length;
227         char nested_partnum[256];
228
229         lprintf(9, "the_mime_parser() called\n");
230         ptr = content_start;
231         memset(boundary, 0, sizeof boundary);
232         memset(content_type, 0, sizeof content_type);
233         memset(encoding, 0, sizeof encoding);
234         memset(name, 0, sizeof name);
235         memset(filename, 0, sizeof filename);
236
237         /* Learn interesting things from the headers */
238         strcpy(header, "");
239         do {
240                 ptr = memreadline(ptr, buf, sizeof buf);
241                 if (*ptr == 0) return; /* premature end of message */
242                 if (content_end != NULL)
243                         if (ptr >= content_end) return;
244
245                 for (i=0; i<strlen(buf); ++i)
246                         if (isspace(buf[i])) buf[i]=' ';
247                 if (!isspace(buf[0])) {
248                         if (!strncasecmp(header, "Content-type: ", 14)) {
249                                 strcpy(content_type, &header[14]);
250                                 extract_key(name, content_type, "name");
251                                 }
252                         if (!strncasecmp(header, "Content-Disposition: ", 21)) {
253                                 strcpy(disposition, &header[21]);
254                                 extract_key(filename, disposition, "filename");
255                                 }
256                         if (!strncasecmp(header,
257                                 "Content-transfer-encoding: ", 27))
258                                         strcpy(encoding, &header[27]);
259                         if (strlen(boundary)==0)
260                                 extract_key(boundary, header, "boundary");
261                         strcpy(header, "");
262                         }
263                 if ((strlen(header)+strlen(buf)+2)<sizeof(header))
264                         strcat(header, buf);
265                 } while ((strlen(buf) > 0) && (*ptr != 0));
266
267         for (i=0; i<strlen(disposition); ++i) 
268                 if (disposition[i]==';') disposition[i] = 0;
269         for (i=0; i<strlen(content_type); ++i) 
270                 if (content_type[i]==';') content_type[i] = 0;
271
272         if (strlen(boundary) > 0) {
273                 is_multipart = 1;
274                 }
275         else {
276                 is_multipart = 0;
277                 }
278
279         /* If this is a multipart message, then recursively process it */
280         part_start = NULL;
281         if (is_multipart) {
282                 sprintf(startary, "--%s", boundary);
283                 sprintf(endary, "--%s--", boundary);
284                 do {
285                         part_end = ptr;
286                         ptr = memreadline(ptr, buf, sizeof buf);
287                         if (*ptr == 0) return; /* premature end of message */
288                         if (content_end != NULL)
289                                 if (ptr >= content_end) return;
290                         if ((!strcasecmp(buf, startary))
291                             ||(!strcasecmp(buf, endary))) {
292                                 if (part_start != NULL) {
293                                         sprintf(nested_partnum, "%s.%d",
294                                                 partnum, ++part_seq);
295                                         the_mime_parser(nested_partnum,
296                                                         part_start, part_end,
297                                                         CallBack);
298                                         }
299                                 part_start = ptr;
300                                 }
301                         } while (strcasecmp(buf, endary));
302                 }
303
304         /* If it's not a multipart message, then do something with it */
305         if (!is_multipart) {
306                 part_start = ptr;
307                 length = 0;
308                 while ((*ptr != 0)&&((content_end==NULL)||(ptr<content_end))) {
309                         ++length;
310                         part_end = ptr++;
311                         }
312                 mime_decode(partnum,
313                                 part_start, length,
314                                 content_type, encoding, disposition,
315                                 name, filename, CallBack);
316                 }
317         
318         }
319
320 /*
321  * Entry point for the MIME parser.
322  * (This function expects to be fed HEADERS + CONTENT)
323  * Note: NULL can be supplied as content_end; in this case, the message is
324  * considered to have ended when the parser encounters a 0x00 byte.
325  */
326 void mime_parser(char *content_start, char *content_end,
327                 void (*CallBack)
328                         (char *cbname,
329                         char *cbfilename,
330                         char *cbpartnum,
331                         char *cbdisp,
332                         void *cbcontent,
333                         char *cbtype,
334                         size_t cblength)
335                 ) {
336
337         lprintf(9, "mime_parser() called\n");
338         the_mime_parser("1", content_start, content_end, CallBack);
339         }