bbed98eef56a756faf2447e87e77c8d5478de279
[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.  Just to
127          * be safe, we still pad the buffer a bit.
128          */
129         decoded = mallok(length + 1024);
130         if (decoded == NULL) {
131                 lprintf(5, "ERROR: cannot allocate memory.\n");
132                 return;
133                 }
134         if (pipe(sendpipe) != 0) return;
135         if (pipe(recvpipe) != 0) return;
136
137         childpid = fork();
138         if (childpid < 0) {
139                 phree(decoded);
140                 return;
141                 }
142
143         if (childpid == 0) {
144                 close(2);
145                 /* send stdio to the pipes */
146                 if (dup2(sendpipe[0], 0)<0) lprintf(5, "ERROR dup2()\n");
147                 if (dup2(recvpipe[1], 1)<0) lprintf(5, "ERROR dup2()\n");
148                 close(sendpipe[1]);      /* Close the ends we're not using */
149                 close(recvpipe[0]);
150                 if (!strcasecmp(encoding, "base64"))
151                    execlp("./base64", "base64", "-d", NULL);
152                 else if (!strcasecmp(encoding, "quoted-printable"))
153                    execlp("./qpdecode", "qpdecode", NULL);
154                 lprintf(5, "ERROR: cannot exec decoder for %s\n", encoding);
155                 exit(1);
156                 }
157
158         close(sendpipe[0]);      /* Close the ends we're not using  */
159         close(recvpipe[1]);
160
161         while ( (bytes_sent < length) && (write_error == 0) ) {
162                 /* Empty the input pipe FIRST */
163                 while (fstat(recvpipe[0], &statbuf), (statbuf.st_size > 0) ) {
164                         blocksize = read(recvpipe[0], &decoded[bytes_recv],
165                                 statbuf.st_size);
166                         if (blocksize < 0) 
167                                 lprintf(5, "ERROR: cannot read from pipe\n");
168                         else
169                                 bytes_recv = bytes_recv + blocksize;
170                         }
171                 /* Then put some data into the output pipe */
172                 blocksize = length - bytes_sent;
173                 if (blocksize > 2048) blocksize = 2048;
174                 if (write(sendpipe[1], &part_start[bytes_sent], blocksize) <0) {
175                         lprintf(5, "ERROR: cannot write to pipe: %s\n",
176                                 strerror(errno));
177                         write_error = 1;
178                         }
179                 bytes_sent = bytes_sent + blocksize;
180                 }
181         close(sendpipe[1]);
182         /* Empty the input pipe */
183         while ( (blocksize = read(recvpipe[0], &decoded[bytes_recv], 1)),
184               (blocksize > 0) )  {
185                 bytes_recv = bytes_recv + blocksize;
186                 }
187
188         if (bytes_recv > 0)
189                 CallBack(name, filename, partnum, disposition, decoded,
190                         content_type, bytes_recv);
191
192         phree(decoded);
193         }
194
195 /*
196  * Break out the components of a multipart message
197  * (This function expects to be fed HEADERS + CONTENT)
198  * Note: NULL can be supplied as content_end; in this case, the message is
199  * considered to have ended when the parser encounters a 0x00 byte.
200  */
201 void the_mime_parser(char *partnum,
202                 char *content_start, char *content_end,
203                 void (*CallBack)
204                         (char *cbname,
205                         char *cbfilename,
206                         char *cbpartnum,
207                         char *cbdisp,
208                         void *cbcontent,
209                         char *cbtype,
210                         size_t cblength)
211                 ) {
212
213         char *ptr;
214         char *part_start, *part_end;
215         char buf[256];
216         char header[256];
217         char boundary[256];
218         char startary[256];
219         char endary[256];
220         char content_type[256];
221         char encoding[256];
222         char disposition[256];
223         char name[256];
224         char filename[256];
225         int is_multipart;
226         int part_seq = 0;
227         int i;
228         size_t length;
229         char nested_partnum[256];
230
231         lprintf(9, "the_mime_parser() called\n");
232         ptr = content_start;
233         memset(boundary, 0, sizeof boundary);
234         memset(content_type, 0, sizeof content_type);
235         memset(encoding, 0, sizeof encoding);
236         memset(name, 0, sizeof name);
237         memset(filename, 0, sizeof filename);
238
239         /* Learn interesting things from the headers */
240         strcpy(header, "");
241         do {
242                 ptr = memreadline(ptr, buf, sizeof buf);
243                 if (*ptr == 0) return; /* premature end of message */
244                 if (content_end != NULL)
245                         if (ptr >= content_end) return;
246
247                 for (i=0; i<strlen(buf); ++i)
248                         if (isspace(buf[i])) buf[i]=' ';
249                 if (!isspace(buf[0])) {
250                         if (!strncasecmp(header, "Content-type: ", 14)) {
251                                 strcpy(content_type, &header[14]);
252                                 extract_key(name, content_type, "name");
253                                 }
254                         if (!strncasecmp(header, "Content-Disposition: ", 21)) {
255                                 strcpy(disposition, &header[21]);
256                                 extract_key(filename, disposition, "filename");
257                                 }
258                         if (!strncasecmp(header,
259                                 "Content-transfer-encoding: ", 27))
260                                         strcpy(encoding, &header[27]);
261                         if (strlen(boundary)==0)
262                                 extract_key(boundary, header, "boundary");
263                         strcpy(header, "");
264                         }
265                 if ((strlen(header)+strlen(buf)+2)<sizeof(header))
266                         strcat(header, buf);
267                 } while ((strlen(buf) > 0) && (*ptr != 0));
268
269         for (i=0; i<strlen(disposition); ++i) 
270                 if (disposition[i]==';') disposition[i] = 0;
271         for (i=0; i<strlen(content_type); ++i) 
272                 if (content_type[i]==';') content_type[i] = 0;
273
274         if (strlen(boundary) > 0) {
275                 is_multipart = 1;
276                 }
277         else {
278                 is_multipart = 0;
279                 }
280
281         /* If this is a multipart message, then recursively process it */
282         part_start = NULL;
283         if (is_multipart) {
284                 sprintf(startary, "--%s", boundary);
285                 sprintf(endary, "--%s--", boundary);
286                 do {
287                         part_end = ptr;
288                         ptr = memreadline(ptr, buf, sizeof buf);
289                         if (*ptr == 0) return; /* premature end of message */
290                         if (content_end != NULL)
291                                 if (ptr >= content_end) return;
292                         if ((!strcasecmp(buf, startary))
293                             ||(!strcasecmp(buf, endary))) {
294                                 if (part_start != NULL) {
295                                         sprintf(nested_partnum, "%s.%d",
296                                                 partnum, ++part_seq);
297                                         the_mime_parser(nested_partnum,
298                                                         part_start, part_end,
299                                                         CallBack);
300                                         }
301                                 part_start = ptr;
302                                 }
303                         } while (strcasecmp(buf, endary));
304                 }
305
306         /* If it's not a multipart message, then do something with it */
307         if (!is_multipart) {
308                 part_start = ptr;
309                 length = 0;
310                 while ((*ptr != 0)&&((content_end==NULL)||(ptr<content_end))) {
311                         ++length;
312                         part_end = ptr++;
313                         }
314                 mime_decode(partnum,
315                                 part_start, length,
316                                 content_type, encoding, disposition,
317                                 name, filename, CallBack);
318                 }
319         
320         }
321
322 /*
323  * Entry point for the MIME parser.
324  * (This function expects to be fed HEADERS + CONTENT)
325  * Note: NULL can be supplied as content_end; in this case, the message is
326  * considered to have ended when the parser encounters a 0x00 byte.
327  */
328 void mime_parser(char *content_start, char *content_end,
329                 void (*CallBack)
330                         (char *cbname,
331                         char *cbfilename,
332                         char *cbpartnum,
333                         char *cbdisp,
334                         void *cbcontent,
335                         char *cbtype,
336                         size_t cblength)
337                 ) {
338
339         lprintf(9, "mime_parser() called\n");
340         the_mime_parser("1", content_start, content_end, CallBack);
341         }