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