]> code.citadel.org Git - citadel.git/blob - citadel/mime_parser.c
Added base64.c to the distribution
[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 *name, char *filename,
81                 void (*CallBack)
82                         (char *cbname,
83                         char *cbfilename,
84                         char *cbpartnum,
85                         void *cbcontent,
86                         char *cbtype,
87                         size_t cblength)
88                 ) {
89
90         char *decoded;
91         struct stat statbuf;
92         int sendpipe[2];
93         int recvpipe[2];
94         int childpid;
95         size_t bytes_sent = 0;
96         size_t bytes_recv = 0;
97         size_t blocksize;
98         int write_error = 0;
99
100         /* If this part is not encoded, send as-is */
101         if (strlen(encoding)==0) {
102                 CallBack(name, filename, partnum, part_start,
103                         content_type, length);
104                 return;
105                 }
106
107         if ( (strcasecmp(encoding, "base64"))
108              && (strcasecmp(encoding, "7bit"))  ) {
109                 lprintf(5, "ERROR: unknown MIME encoding '%s'\n", encoding);
110                 return;
111                 }
112
113         /*
114          * Allocate a buffer for the decoded data.  The output buffer is the
115          * same size as the input buffer; this assumes that the decoded data
116          * will never be larger than the encoded data.  This is a safe
117          * assumption with base64, uuencode, and quoted-printable.
118          */
119         decoded = mallok(length);
120         if (decoded == NULL) {
121                 lprintf(5, "ERROR: cannot allocate memory.\n");
122                 return;
123                 }
124         if (pipe(sendpipe) != 0) return;
125         if (pipe(recvpipe) != 0) return;
126
127         childpid = fork();
128         lprintf(9, "fork() returned %d\n", childpid);
129         if (childpid < 0) {
130                 phree(decoded);
131                 return;
132                 }
133
134         if (childpid == 0) {
135                 /* close(2); FIX uncomment this when solid */
136                 /* send stdio to the pipes */
137                 if (dup2(sendpipe[0], 0)<0) lprintf(5, "ERROR dup2()\n");
138                 if (dup2(recvpipe[1], 1)<0) lprintf(5, "ERROR dup2()\n");
139                 close(sendpipe[1]);      /* Close the ends we're not using */
140                 close(recvpipe[0]);
141                 if (!strcasecmp(encoding, "base64"))
142                    execlp("./base64", "base64", "-d", NULL);
143                 else if (!strcasecmp(encoding, "7bit"))
144                    execlp("/bin/dd", "dd", NULL);
145                 lprintf(5, "ERROR: cannot exec decoder for %s\n", encoding);
146                 exit(1);
147                 }
148
149         close(sendpipe[0]);      /* Close the ends we're not using  */
150         close(recvpipe[1]);
151
152         lprintf(9, "ready to send %d bytes\n", length);
153
154         while ( (bytes_sent < length) && (write_error == 0) ) {
155                 /* Empty the input pipe FIRST */
156                 while (fstat(recvpipe[0], &statbuf), (statbuf.st_size > 0) ) {
157                         blocksize = read(recvpipe[0], &decoded[bytes_recv],
158                                 statbuf.st_size);
159                         if (blocksize < 0) 
160                                 lprintf(5, "ERROR: cannot read from pipe\n");
161                         else
162                                 bytes_recv = bytes_recv + blocksize;
163                         }
164                 /* Then put some data into the output pipe */
165                 blocksize = length - bytes_sent;
166                 if (blocksize > 2048) blocksize = 2048;
167                 if (write(sendpipe[1], &part_start[bytes_sent], blocksize) <0) {
168                         lprintf(5, "ERROR: cannot write to pipe: %s\n",
169                                 strerror(errno));
170                         write_error = 1;
171                         }
172                 bytes_sent = bytes_sent + blocksize;
173                 }
174         close(sendpipe[1]);
175         /* Empty the input pipe */
176         while ( (blocksize = read(recvpipe[0], &decoded[bytes_recv], 1)),
177               (blocksize > 0) )  {
178                 bytes_recv = bytes_recv + blocksize;
179                 }
180
181         lprintf(9, "Decoded length = %d\n", bytes_recv);
182
183         if (bytes_recv > 0)
184                 CallBack(name, filename, partnum, decoded,
185                         content_type, bytes_recv);
186         phree(decoded);
187         }
188
189 /*
190  * Break out the components of a multipart message
191  * (This function expects to be fed HEADERS + CONTENT)
192  * Note: NULL can be supplied as content_end; in this case, the message is
193  * considered to have ended when the parser encounters a 0x00 byte.
194  */
195 void the_mime_parser(char *partnum,
196                 char *content_start, char *content_end,
197                 void (*CallBack)
198                         (char *cbname,
199                         char *cbfilename,
200                         char *cbpartnum,
201                         void *cbcontent,
202                         char *cbtype,
203                         size_t cblength)
204                 ) {
205
206         char *ptr;
207         char *part_start, *part_end;
208         char buf[256];
209         char header[256];
210         char boundary[256];
211         char startary[256];
212         char endary[256];
213         char content_type[256];
214         char encoding[256];
215         char name[256];
216         char filename[256];
217         int is_multipart;
218         int part_seq = 0;
219         int i;
220         size_t length;
221         char nested_partnum[256];
222
223         ptr = content_start;
224         memset(boundary, 0, sizeof boundary);
225         memset(content_type, 0, sizeof content_type);
226         memset(encoding, 0, sizeof encoding);
227         memset(name, 0, sizeof name);
228         memset(filename, 0, sizeof filename);
229
230         /* Learn interesting things from the headers */
231         strcpy(header, "");
232         do {
233                 ptr = memreadline(ptr, buf, sizeof buf);
234                 if (*ptr == 0) return; /* premature end of message */
235                 if (content_end != NULL)
236                         if (ptr >= content_end) return;
237
238                 for (i=0; i<strlen(buf); ++i)
239                         if (isspace(buf[i])) buf[i]=' ';
240                 if (!isspace(buf[0])) {
241                         if (!strncasecmp(header, "Content-type: ", 14)) {
242                                 strcpy(content_type, &header[14]);
243                                 extract_key(name, content_type, "name");
244                                 }
245                         if (!strncasecmp(header, "Content-Disposition: ", 21)) {
246                                 extract_key(filename, header, "filename");
247                                 }
248                         if (!strncasecmp(header,
249                                 "Content-transfer-encoding: ", 27))
250                                         strcpy(encoding, &header[27]);
251                         if (strlen(boundary)==0)
252                                 extract_key(boundary, header, "boundary");
253                         strcpy(header, "");
254                         }
255                 if ((strlen(header)+strlen(buf)+2)<sizeof(header))
256                         strcat(header, buf);
257                 } while ((strlen(buf) > 0) && (*ptr != 0));
258
259         for (i=0; i<strlen(content_type); ++i) 
260                 if (content_type[i]==';') content_type[i] = 0;
261
262         if (strlen(boundary) > 0) {
263                 is_multipart = 1;
264                 }
265         else {
266                 is_multipart = 0;
267                 }
268
269         /* If this is a multipart message, then recursively process it */
270         part_start = NULL;
271         if (is_multipart) {
272                 sprintf(startary, "--%s", boundary);
273                 sprintf(endary, "--%s--", boundary);
274                 do {
275                         part_end = ptr;
276                         ptr = memreadline(ptr, buf, sizeof buf);
277                         if (*ptr == 0) return; /* premature end of message */
278                         if (content_end != NULL)
279                                 if (ptr >= content_end) return;
280                         if ((!strcasecmp(buf, startary))
281                             ||(!strcasecmp(buf, endary))) {
282                                 if (part_start != NULL) {
283                                         sprintf(nested_partnum, "%s.%d",
284                                                 partnum, ++part_seq);
285                                         the_mime_parser(nested_partnum,
286                                                         part_start, part_end,
287                                                         CallBack);
288                                         }
289                                 part_start = ptr;
290                                 }
291                         } while (strcasecmp(buf, endary));
292                 }
293
294         /* If it's not a multipart message, then do something with it */
295         if (!is_multipart) {
296                 part_start = ptr;
297                 length = 0;
298                 while ((*ptr != 0)&&((content_end==NULL)||(ptr<content_end))) {
299                         ++length;
300                         part_end = ptr++;
301                         }
302                 mime_decode(partnum,
303                                 part_start, length,
304                                 content_type, encoding,
305                                 name, filename, CallBack);
306                 }
307         
308         }
309
310 /*
311  * Entry point for the MIME parser.
312  * (This function expects to be fed HEADERS + CONTENT)
313  * Note: NULL can be supplied as content_end; in this case, the message is
314  * considered to have ended when the parser encounters a 0x00 byte.
315  */
316 void mime_parser(char *content_start, char *content_end,
317                 void (*CallBack)
318                         (char *cbname,
319                         char *cbfilename,
320                         char *cbpartnum,
321                         void *cbcontent,
322                         char *cbtype,
323                         size_t cblength)
324                 ) {
325
326         the_mime_parser("1", content_start, content_end, CallBack);
327         }