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