1 |
edhill |
1.1 |
/* (C) Copyright 1993,1994 by Carnegie Mellon University |
2 |
|
|
* All Rights Reserved. |
3 |
|
|
* |
4 |
|
|
* Permission to use, copy, modify, distribute, and sell this software |
5 |
|
|
* and its documentation for any purpose is hereby granted without |
6 |
|
|
* fee, provided that the above copyright notice appear in all copies |
7 |
|
|
* and that both that copyright notice and this permission notice |
8 |
|
|
* appear in supporting documentation, and that the name of Carnegie |
9 |
|
|
* Mellon University not be used in advertising or publicity |
10 |
|
|
* pertaining to distribution of the software without specific, |
11 |
|
|
* written prior permission. Carnegie Mellon University makes no |
12 |
|
|
* representations about the suitability of this software for any |
13 |
|
|
* purpose. It is provided "as is" without express or implied |
14 |
|
|
* warranty. |
15 |
|
|
* |
16 |
|
|
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO |
17 |
|
|
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY |
18 |
|
|
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE |
19 |
|
|
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
20 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN |
21 |
|
|
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
22 |
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS |
23 |
|
|
* SOFTWARE. |
24 |
|
|
*/ |
25 |
|
|
#include <stdio.h> |
26 |
jmc |
1.4 |
#include <stdlib.h> |
27 |
edhill |
1.1 |
#include <ctype.h> |
28 |
|
|
#include <string.h> |
29 |
jmc |
1.4 |
#include <unistd.h> |
30 |
edhill |
1.1 |
#include "xmalloc.h" |
31 |
|
|
#include "common.h" |
32 |
|
|
#include "part.h" |
33 |
|
|
|
34 |
|
|
extern char *os_idtodir(char *id); |
35 |
|
|
extern FILE *os_newtypedfile(char *fname, char *contentType, int flags, params contentParams); |
36 |
|
|
extern FILE *os_createnewfile(char *fname); |
37 |
jmc |
1.4 |
extern int os_binhex(struct part *inpart, int part, int nparts); |
38 |
|
|
extern void os_closetypedfile(FILE *outfile); |
39 |
|
|
extern void os_donewithdir(char *dir); |
40 |
|
|
extern void os_perror(char *str); |
41 |
|
|
extern void chat(char *s); |
42 |
|
|
|
43 |
|
|
extern void part_ungets(char *s, struct part *part); |
44 |
|
|
extern void part_close(struct part *part); |
45 |
|
|
extern int handleMessage(struct part *inpart, char *defaultContentType, |
46 |
|
|
int inAppleDouble, int extractText); |
47 |
edhill |
1.1 |
|
48 |
|
|
static FILE *startDescFile(char *fname); |
49 |
jmc |
1.4 |
static void uudecodeline(char *line, FILE *outfile); |
50 |
edhill |
1.1 |
|
51 |
jmc |
1.4 |
int parseSubject(char *subject, char **fnamep, int *partp, int *npartsp); |
52 |
|
|
int saveUuFile(struct part *inpart, char *fname, int part, int nparts, |
53 |
|
|
char *firstline); |
54 |
|
|
int descEnd(char *line); |
55 |
|
|
int uudecodefiles(char *dir, int nparts); |
56 |
edhill |
1.1 |
|
57 |
|
|
/* Length of a normal uuencoded line, including newline */ |
58 |
|
|
#define UULENGTH 62 |
59 |
|
|
|
60 |
|
|
/* |
61 |
|
|
* Table of valid boundary characters |
62 |
|
|
* |
63 |
|
|
* XXX: Old versions of Mark Crispin's c-client library |
64 |
|
|
* generate boundaries which contain the syntactically |
65 |
|
|
* illegal character '#'. It is marked in this table with |
66 |
|
|
* a 2 in case we want to use this table in the future to |
67 |
|
|
* complain about bad syntax. |
68 |
|
|
* |
69 |
|
|
*/ |
70 |
|
|
static char bchar[256] = { |
71 |
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
72 |
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
73 |
|
|
1, 0, 0, 2, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, |
74 |
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, |
75 |
|
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
76 |
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, |
77 |
|
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
78 |
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, |
79 |
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
80 |
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
81 |
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
82 |
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
83 |
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
84 |
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
85 |
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
86 |
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
87 |
|
|
}; |
88 |
|
|
|
89 |
|
|
/* |
90 |
|
|
* Read an input file, looking for data in split-uuencode format |
91 |
|
|
*/ |
92 |
|
|
int handleUuencode(struct part *inpart, char *subject, int extractText) |
93 |
|
|
{ |
94 |
|
|
char *fname = 0, *tmpfname; |
95 |
|
|
int part, nparts; |
96 |
|
|
int tmppart, tmpnparts; |
97 |
|
|
char buf[1024], buf2[1024]; |
98 |
|
|
char fnamebuf[80]; |
99 |
|
|
char *boundary_end, *p; |
100 |
|
|
int wantdescfile = 0; |
101 |
|
|
FILE *descfile = 0; |
102 |
|
|
|
103 |
|
|
/* Scan "Subject:" header for filename/part information */ |
104 |
|
|
if (parseSubject(subject, &fname, &part, &nparts) != 0) { |
105 |
|
|
part = -1; |
106 |
|
|
} |
107 |
|
|
if (part == 0) { |
108 |
|
|
return saveUuFile(inpart, fname, part, nparts, (char *)0); |
109 |
|
|
} |
110 |
|
|
if (part == 1) { |
111 |
|
|
wantdescfile = 1; |
112 |
|
|
} |
113 |
|
|
|
114 |
|
|
/* Scan body for interesting lines */ |
115 |
|
|
while (part_gets(buf, sizeof(buf), inpart)) { |
116 |
|
|
/* Uuencode "begin" line */ |
117 |
|
|
if (!strncmp(buf, "begin ", 6) && |
118 |
|
|
isdigit(buf[6]) && isdigit(buf[7]) && isdigit(buf[8]) && |
119 |
|
|
buf[9] == ' ') { |
120 |
|
|
if (part == -1) { |
121 |
|
|
/* |
122 |
|
|
* We have no part N of M information. Perhaps it is |
123 |
|
|
* a single-part uuencoded file. |
124 |
|
|
*/ |
125 |
|
|
return saveUuFile(inpart, (char *)0, 1, 0, buf); |
126 |
|
|
} |
127 |
|
|
else { |
128 |
|
|
if (descfile) fclose(descfile); |
129 |
|
|
return saveUuFile(inpart, fname, part, nparts, buf); |
130 |
|
|
} |
131 |
|
|
} |
132 |
|
|
else if (!strncmp(buf, "section ", 8) && isdigit(buf[8])) { |
133 |
|
|
tmppart = 0; |
134 |
|
|
for (p = buf+8; isdigit(*p); p++) tmppart = tmppart*10 + *p - '0'; |
135 |
|
|
if (tmppart == 0) continue; |
136 |
|
|
if (strncmp(p, " of ", 4) == 0) { |
137 |
|
|
/* |
138 |
|
|
* "section N of ... of file F ..." |
139 |
|
|
*/ |
140 |
|
|
for (p += 4; *p && strncmp(p, " of file ", 9) != 0; p++); |
141 |
|
|
if (!*p) continue; |
142 |
|
|
p += 9; |
143 |
|
|
tmpfname = p; |
144 |
|
|
p = strchr(p, ' '); |
145 |
|
|
if (!p) continue; |
146 |
|
|
*p = '\0'; |
147 |
|
|
if (descfile) fclose(descfile); |
148 |
|
|
return saveUuFile(inpart, tmpfname, tmppart, 0, (char *)0); |
149 |
|
|
} |
150 |
|
|
else if (*p == '/' && isdigit(p[1])) { |
151 |
|
|
/* |
152 |
|
|
* "section N/M file F ..." |
153 |
|
|
*/ |
154 |
|
|
tmpnparts = 0; |
155 |
|
|
for (p++; isdigit(*p); p++) { |
156 |
|
|
tmpnparts = tmpnparts*10 + *p - '0'; |
157 |
|
|
} |
158 |
|
|
while (*p && isspace(*p)) p++; |
159 |
|
|
if (tmppart > tmpnparts || strncmp(p, "file ", 5) != 0) { |
160 |
|
|
continue; |
161 |
|
|
} |
162 |
|
|
tmpfname = p+5; |
163 |
|
|
p = strchr(tmpfname, ' '); |
164 |
|
|
if (!p) continue; |
165 |
|
|
*p = '\0'; |
166 |
|
|
if (descfile) fclose(descfile); |
167 |
|
|
return saveUuFile(inpart, tmpfname, tmppart, tmpnparts, |
168 |
|
|
(char *)0); |
169 |
|
|
} |
170 |
|
|
} |
171 |
|
|
else if (!strncmp(buf, "POST V", 6)) { |
172 |
|
|
/* |
173 |
|
|
* "POST Vd.d.d F (Part N/M)" |
174 |
|
|
*/ |
175 |
|
|
p = strchr(buf+6, ' '); |
176 |
|
|
if (!p) continue; |
177 |
|
|
tmpfname = p+1; |
178 |
|
|
p = strchr(tmpfname, ' '); |
179 |
|
|
if (!p || strncmp(p, " (Part ", 7) != 0) continue; |
180 |
|
|
*p = '\0'; |
181 |
|
|
p += 7; |
182 |
|
|
tmppart = 0; |
183 |
|
|
while (isdigit(*p)) tmppart = tmppart*10 + *p++ - '0'; |
184 |
|
|
if (tmppart == 0 || *p++ != '/') continue; |
185 |
|
|
tmpnparts = 0; |
186 |
|
|
while (isdigit(*p)) tmpnparts = tmpnparts*10 + *p++ - '0'; |
187 |
|
|
if (tmppart > tmpnparts || *p != ')') continue; |
188 |
|
|
if (descfile) fclose(descfile); |
189 |
|
|
return saveUuFile(inpart, tmpfname, tmppart, tmpnparts, (char *)0); |
190 |
|
|
} |
191 |
|
|
else if (!strncmp(buf, "File: ", 6)) { |
192 |
|
|
/* |
193 |
|
|
* "File: F -- part N of M -- ... |
194 |
|
|
*/ |
195 |
|
|
tmpfname = buf+6; |
196 |
|
|
p = strchr(tmpfname, ' '); |
197 |
|
|
if (!p || strncmp(p, " -- part ", 9) != 0) continue; |
198 |
|
|
*p = '\0'; |
199 |
|
|
p += 9; |
200 |
|
|
tmppart = 0; |
201 |
|
|
while (isdigit(*p)) tmppart = tmppart*10 + *p++ - '0'; |
202 |
|
|
if (tmppart == 0 || strncmp(p, " of ", 4) != 0) continue; |
203 |
|
|
p += 4; |
204 |
|
|
tmpnparts = 0; |
205 |
|
|
while (isdigit(*p)) tmpnparts = tmpnparts*10 + *p++ - '0'; |
206 |
|
|
if (tmppart > tmpnparts || strncmp(p, " -- ", 4) != 0) continue; |
207 |
|
|
if (descfile) fclose(descfile); |
208 |
|
|
return saveUuFile(inpart, tmpfname, tmppart, tmpnparts, (char *)0); |
209 |
|
|
} |
210 |
|
|
else if (!strncmp(buf, "[Section: ", 10)) { |
211 |
|
|
/* |
212 |
|
|
* "[Section: N/M File: F ..." |
213 |
|
|
*/ |
214 |
|
|
tmppart = 0; |
215 |
|
|
for (p = buf+10; isdigit(*p); p++) tmppart = tmppart*10 + *p - '0'; |
216 |
|
|
if (tmppart == 0) continue; |
217 |
|
|
tmpnparts = 0; |
218 |
|
|
for (p++; isdigit(*p); p++) { |
219 |
|
|
tmpnparts = tmpnparts*10 + *p - '0'; |
220 |
|
|
} |
221 |
|
|
while (*p && isspace(*p)) p++; |
222 |
|
|
if (tmppart > tmpnparts || strncmp(p, "File: ", 6) != 0) { |
223 |
|
|
continue; |
224 |
|
|
} |
225 |
|
|
tmpfname = p+6; |
226 |
|
|
p = strchr(tmpfname, ' '); |
227 |
|
|
if (!p) continue; |
228 |
|
|
*p = '\0'; |
229 |
|
|
if (descfile) fclose(descfile); |
230 |
|
|
return saveUuFile(inpart, tmpfname, tmppart, tmpnparts, (char *)0); |
231 |
|
|
} |
232 |
|
|
else if (*buf == '[') { |
233 |
|
|
/* |
234 |
|
|
* "[F ... - part N of M]" |
235 |
|
|
* (usual BinHex practice) |
236 |
|
|
*/ |
237 |
|
|
tmpfname = buf+1; |
238 |
|
|
p = strchr(tmpfname, ' '); |
239 |
|
|
if (!p) continue; |
240 |
|
|
*p++ = '\0'; |
241 |
|
|
while (p && strncmp(p, "- part ", 7) != 0) { |
242 |
|
|
p = strchr(p+1, '-'); |
243 |
|
|
} |
244 |
|
|
if (!p) continue; |
245 |
|
|
p += 7; |
246 |
|
|
tmppart = 0; |
247 |
|
|
while (isdigit(*p)) tmppart = tmppart*10 + *p++ - '0'; |
248 |
|
|
if (tmppart == 0 || strncmp(p, " of ", 4) != 0) continue; |
249 |
|
|
p += 4; |
250 |
|
|
tmpnparts = 0; |
251 |
|
|
while (isdigit(*p)) tmpnparts = tmpnparts*10 + *p++ - '0'; |
252 |
|
|
if (tmppart > tmpnparts || *p != ']') continue; |
253 |
|
|
if (descfile) fclose(descfile); |
254 |
|
|
return saveUuFile(inpart, tmpfname, tmppart, tmpnparts, (char *)0); |
255 |
|
|
} |
256 |
|
|
else if (fname && part > 0 && nparts > 0 && part <= nparts && |
257 |
|
|
(!strncmp(buf, "BEGIN", 5) || |
258 |
|
|
!strncmp(buf, "--- BEGIN ---", 12) || |
259 |
|
|
(buf[0] == 'M' && strlen(buf) == UULENGTH))) { |
260 |
|
|
/* |
261 |
|
|
* Found the start of a section of uuencoded data |
262 |
|
|
* and have the part N of M information. |
263 |
|
|
*/ |
264 |
|
|
if (descfile) fclose(descfile); |
265 |
|
|
return saveUuFile(inpart, fname, part, nparts, buf); |
266 |
|
|
} |
267 |
|
|
else if (!strncasecmp(buf, "x-file-name: ", 13)) { |
268 |
|
|
for (p = buf + 13; *p && !isspace(*p); p++); |
269 |
|
|
*p = '\0'; |
270 |
|
|
strncpy(fnamebuf, buf+13, sizeof(fnamebuf)-1); |
271 |
|
|
fnamebuf[sizeof(fnamebuf)-1] = '\0'; |
272 |
|
|
fname = fnamebuf; |
273 |
|
|
continue; |
274 |
|
|
} |
275 |
|
|
else if (!strncasecmp(buf, "x-part: ", 8)) { |
276 |
|
|
tmppart = atoi(buf+8); |
277 |
|
|
if (tmppart > 0) part = tmppart; |
278 |
|
|
continue; |
279 |
|
|
} |
280 |
|
|
else if (!strncasecmp(buf, "x-part-total: ", 14)) { |
281 |
|
|
tmpnparts = atoi(buf+14); |
282 |
|
|
if (tmpnparts > 0) nparts = tmpnparts; |
283 |
|
|
continue; |
284 |
|
|
} |
285 |
|
|
else if (part == 1 && fname && !descfile && |
286 |
|
|
!strncasecmp(buf, "x-file-desc: ", 13)) { |
287 |
jmc |
1.4 |
if ((descfile = startDescFile(fname))) { |
288 |
edhill |
1.1 |
fputs(buf+13, descfile); |
289 |
|
|
fclose(descfile); |
290 |
|
|
descfile = 0; |
291 |
|
|
} |
292 |
|
|
continue; |
293 |
|
|
} |
294 |
|
|
else if (!strcmp(buf, |
295 |
|
|
"(This file must be converted with BinHex 4.0)\n")) { |
296 |
|
|
if (descfile) fclose(descfile); |
297 |
|
|
return os_binhex(inpart, 1, 1); |
298 |
|
|
} |
299 |
|
|
else if (!strncasecmp(buf, "content-", 8)) { |
300 |
|
|
/* |
301 |
|
|
* HEURISTIC: If we see something that looks like a content-* |
302 |
|
|
* header, push it back and call the message parser. |
303 |
|
|
*/ |
304 |
|
|
p = buf+8; |
305 |
|
|
/* Check to see if header's field-name is syntactically valid */ |
306 |
|
|
while (*p) { |
307 |
|
|
if (*p == ':' || *p <= ' ' || *p >= '\177') break; |
308 |
|
|
p++; |
309 |
|
|
} |
310 |
|
|
if (*p == ':') { |
311 |
|
|
part_ungets(buf, inpart); |
312 |
|
|
if (descfile) fclose(descfile); |
313 |
|
|
return handleMessage(inpart, "text/plain", 0, extractText); |
314 |
|
|
} |
315 |
|
|
} |
316 |
|
|
if (buf[0] == '-' && buf[1] == '-') { |
317 |
|
|
/* |
318 |
|
|
* Heuristic: If we see something that looks like a |
319 |
|
|
* multipart boundary, followed by something that looks |
320 |
|
|
* like a header, push them back and parse as a multipart. |
321 |
|
|
*/ |
322 |
|
|
p = buf+2; |
323 |
|
|
while (*p) { |
324 |
|
|
if (!bchar[(unsigned char)*p]) break; |
325 |
|
|
p++; |
326 |
|
|
} |
327 |
|
|
if (*p != '\n') { |
328 |
|
|
/* |
329 |
|
|
* We found an invalid boundary character. |
330 |
|
|
* Move 'p' such that it will fail all subsequent checks. |
331 |
|
|
*/ |
332 |
|
|
p = buf + 2; |
333 |
|
|
} |
334 |
|
|
/* Back up to ignore trailing whitespace */ |
335 |
|
|
while (p > buf+2 && p[-1] == ' ') p--; |
336 |
|
|
|
337 |
|
|
/* |
338 |
|
|
* Check that boundary is within legal size limits |
339 |
|
|
* If so, peek at next line |
340 |
|
|
*/ |
341 |
|
|
if (p - buf > 2 && p - buf <= 72 && |
342 |
|
|
part_gets(buf2, sizeof(buf2), inpart)) { |
343 |
|
|
boundary_end = p; |
344 |
|
|
p = buf2; |
345 |
|
|
/* |
346 |
|
|
* Check to see if a syntactically valid header follows |
347 |
|
|
* what looks to be a boundary. |
348 |
|
|
* |
349 |
|
|
* XXX: Unfortunately, we can't check for "Content-"; |
350 |
|
|
* it is syntactically valid to have a body-part |
351 |
|
|
* header that doesn't start with that and ZMail |
352 |
|
|
* takes advantage of that. If this heuristic starts |
353 |
|
|
* causing problems, we could keep looking ahead until |
354 |
|
|
* we find a "Content-" header or find something that's |
355 |
|
|
* not a header. |
356 |
|
|
*/ |
357 |
|
|
while (*p) { |
358 |
|
|
if (*p == ':' || *p <= ' ' || *p >= '\177') break; |
359 |
|
|
p++; |
360 |
|
|
} |
361 |
|
|
|
362 |
|
|
/* Push back the lookahead line */ |
363 |
|
|
part_ungets(buf2, inpart); |
364 |
|
|
|
365 |
|
|
if (p > buf2 && *p == ':') { |
366 |
|
|
/* Push back the boundary */ |
367 |
|
|
part_ungets(buf, inpart); |
368 |
|
|
|
369 |
|
|
/* |
370 |
|
|
* Generate and push back a header to get us into |
371 |
|
|
* the multipart parser. |
372 |
|
|
*/ |
373 |
|
|
*boundary_end = '\0'; |
374 |
|
|
sprintf(buf2, |
375 |
|
|
"Content-type: multipart/mixed; boundary=\"%s\"\n\n", |
376 |
|
|
buf+2); |
377 |
|
|
part_ungets(buf2, inpart); |
378 |
|
|
|
379 |
|
|
if (descfile) fclose(descfile); |
380 |
|
|
return handleMessage(inpart, "text/plain", 0, extractText); |
381 |
|
|
} |
382 |
|
|
} |
383 |
|
|
} |
384 |
|
|
|
385 |
|
|
/* |
386 |
|
|
* Save useful-looking text that is before a "part 1 of N" |
387 |
|
|
* in a description file. |
388 |
|
|
*/ |
389 |
|
|
if (wantdescfile && !descfile) { |
390 |
|
|
for (p = buf; *p && isspace(*p); p++); |
391 |
|
|
if (*p) { |
392 |
|
|
if (!strncasecmp(p, "x-", 2)) { |
393 |
|
|
/* |
394 |
|
|
* Check for "X-foobar:" |
395 |
|
|
* If so, there probably will be a "X-File-Desc:" line |
396 |
|
|
* later, so ignore this line. |
397 |
|
|
*/ |
398 |
|
|
while (*p != ':' && *p > ' ' && *p < '\177') p++; |
399 |
|
|
if (*p == ':') continue; |
400 |
|
|
} |
401 |
|
|
if (!descEnd(buf) && (descfile = startDescFile(fname))) { |
402 |
|
|
fputs(buf, descfile); |
403 |
|
|
} |
404 |
|
|
wantdescfile = 0; |
405 |
|
|
} |
406 |
|
|
} |
407 |
|
|
else if (descfile) { |
408 |
|
|
if (descEnd(buf)) { |
409 |
|
|
fclose(descfile); |
410 |
|
|
descfile = 0; |
411 |
|
|
} |
412 |
|
|
else { |
413 |
|
|
fputs(buf, descfile); |
414 |
|
|
} |
415 |
|
|
} |
416 |
|
|
} |
417 |
|
|
|
418 |
|
|
if (descfile) fclose(descfile); |
419 |
|
|
return 0; |
420 |
|
|
} |
421 |
|
|
|
422 |
|
|
/* |
423 |
|
|
* Handle a split-uuencode part |
424 |
|
|
* If nparts is 0, then look for an "end" line to detect the last part. |
425 |
|
|
* If fname is null, then we are attempting to decode a single-part message. |
426 |
|
|
* If firstline is non-null, it is written as the first line of the saved part |
427 |
|
|
*/ |
428 |
|
|
int |
429 |
|
|
saveUuFile(struct part *inpart, char *fname, int part, int nparts, char *firstline) |
430 |
|
|
{ |
431 |
|
|
char buf[1024]; |
432 |
|
|
char *dir; |
433 |
|
|
FILE *partfile; |
434 |
|
|
|
435 |
|
|
if (fname) { |
436 |
|
|
sprintf(buf, "Saving part %d ", part); |
437 |
|
|
if (nparts) sprintf(buf+strlen(buf), "of %d ", nparts); |
438 |
|
|
strcat(buf, fname); |
439 |
|
|
chat(buf); |
440 |
|
|
} |
441 |
|
|
else fname = "unknown"; |
442 |
|
|
|
443 |
|
|
/* Create directory to store parts and copy this part there. */ |
444 |
|
|
dir = os_idtodir(fname); |
445 |
|
|
if (!dir) return 1; |
446 |
|
|
sprintf(buf, "%s%d", dir, part); |
447 |
|
|
partfile = os_createnewfile(buf); |
448 |
|
|
if (!partfile) { |
449 |
|
|
os_perror(buf); |
450 |
|
|
return 1; |
451 |
|
|
} |
452 |
|
|
if (firstline) fputs(firstline, partfile); |
453 |
|
|
while (part_gets(buf, sizeof(buf), inpart)) { |
454 |
|
|
fputs(buf, partfile); |
455 |
|
|
if (nparts == 0 && strcmp(buf, "end\n") == 0) { |
456 |
|
|
/* This is the last part. Remember the fact */ |
457 |
|
|
nparts = part; |
458 |
|
|
fclose(partfile); |
459 |
|
|
sprintf(buf, "%sCT", dir); |
460 |
|
|
partfile = os_createnewfile(buf); |
461 |
|
|
if (!partfile) { |
462 |
|
|
os_perror(buf); |
463 |
|
|
} |
464 |
|
|
else { |
465 |
|
|
fprintf(partfile, "%d\n", nparts); |
466 |
|
|
} |
467 |
|
|
break; |
468 |
|
|
} |
469 |
|
|
} |
470 |
|
|
fclose(partfile); |
471 |
|
|
|
472 |
|
|
/* Retrieve any previously saved number of the last part */ |
473 |
|
|
if (nparts == 0) { |
474 |
|
|
sprintf(buf, "%sCT", dir); |
475 |
jmc |
1.4 |
if ((partfile = fopen(buf, "r"))) { |
476 |
edhill |
1.1 |
if (fgets(buf, sizeof(buf), partfile)) { |
477 |
|
|
nparts = atoi(buf); |
478 |
|
|
if (nparts < 0) nparts = 0; |
479 |
|
|
} |
480 |
|
|
fclose(partfile); |
481 |
|
|
} |
482 |
|
|
} |
483 |
|
|
|
484 |
|
|
if (nparts == 0) return 0; |
485 |
|
|
|
486 |
|
|
/* Check to see if we have all parts. Start from the highest numbers |
487 |
|
|
* as we are more likely not to have them. |
488 |
|
|
*/ |
489 |
|
|
for (part = nparts; part; part--) { |
490 |
|
|
sprintf(buf, "%s%d", dir, part); |
491 |
|
|
partfile = fopen(buf, "r"); |
492 |
|
|
if (partfile) { |
493 |
|
|
fclose(partfile); |
494 |
|
|
} |
495 |
|
|
else { |
496 |
|
|
return 0; |
497 |
|
|
} |
498 |
|
|
} |
499 |
|
|
|
500 |
|
|
return uudecodefiles(dir, nparts); |
501 |
|
|
} |
502 |
|
|
|
503 |
|
|
/* |
504 |
|
|
* Parse a Subject: header, looking for clues with which to decode |
505 |
|
|
* split-uuencoded data. |
506 |
|
|
*/ |
507 |
|
|
int |
508 |
|
|
parseSubject(char *subject, char **fnamep, int *partp, int *npartsp) |
509 |
|
|
{ |
510 |
|
|
char *scan, *bak, *start; |
511 |
|
|
int part = -1, nparts = 0, hasdot = 0; |
512 |
|
|
|
513 |
|
|
/* No subject header */ |
514 |
|
|
if (!subject) return 1; |
515 |
|
|
|
516 |
|
|
/* Skip leading whitespace and other garbage */ |
517 |
|
|
scan = subject; |
518 |
|
|
while (*scan == ' ' || *scan == '\t' || *scan == '-') scan++; |
519 |
|
|
if (!strncasecmp(scan, "repost", 6)) { |
520 |
|
|
for (scan += 6; *scan == ' ' || *scan == '\t' |
521 |
|
|
|| *scan == ':' || *scan == '-'; scan++); |
522 |
|
|
} |
523 |
|
|
|
524 |
|
|
/* Replies aren't usually data */ |
525 |
|
|
if (!strncasecmp(scan, "re:", 3)) return 1; |
526 |
|
|
|
527 |
|
|
/* Get filename */ |
528 |
|
|
|
529 |
|
|
/* Grab the first filename-like string. Explicitly ignore strings with |
530 |
|
|
* prefix "v<digit>" ending in ":", since that is a popular volume/issue |
531 |
|
|
* representation syntax |
532 |
|
|
*/ |
533 |
|
|
do { |
534 |
|
|
while (*scan != '\n' && !isalnum(*scan) && *scan != '_') ++scan; |
535 |
|
|
*fnamep = start = scan; |
536 |
|
|
while (isalnum(*scan) || *scan == '-' || *scan == '+' || *scan == '&' |
537 |
|
|
|| *scan == '_' || *scan == '.') { |
538 |
|
|
if (*scan++ == '.') hasdot = 1; |
539 |
|
|
} |
540 |
|
|
if (!*scan || *scan == '\n') return 1; |
541 |
|
|
} while (start == scan |
542 |
|
|
|| (start[0] == 'v' && isdigit(start[1]) && *scan == ':')); |
543 |
|
|
*scan++ = '\0'; |
544 |
|
|
|
545 |
|
|
/* Try looking for a filename with a "." in it later in the subject line. |
546 |
|
|
* Exclude <digit>.<digit>, since that is usually a version number. |
547 |
|
|
*/ |
548 |
|
|
if (!hasdot) { |
549 |
|
|
while (*(start = scan) != '\0' && *scan != '\n') { |
550 |
|
|
while (isspace(*start)) ++start; |
551 |
|
|
for (scan = start; isalnum(*scan) || *scan == '-' || *scan == '+' |
552 |
|
|
|| *scan == '&' || *scan == '_' || *scan == '.'; ++scan) { |
553 |
|
|
if (*scan == '.' && |
554 |
|
|
(!isdigit(scan[-1]) || !isdigit(scan[1]))) { |
555 |
|
|
hasdot = 1; |
556 |
|
|
} |
557 |
|
|
} |
558 |
|
|
if (hasdot && scan > start) { |
559 |
|
|
*fnamep = start; |
560 |
|
|
*scan++ = '\0'; |
561 |
|
|
break; |
562 |
|
|
} |
563 |
|
|
while (*scan && *scan != '\n' && !isalnum(*scan)) ++scan; |
564 |
|
|
} |
565 |
|
|
scan = *fnamep + strlen(*fnamep) + 1; |
566 |
|
|
} |
567 |
|
|
|
568 |
|
|
/* Get part number */ |
569 |
|
|
while (*scan && *scan != '\n') { |
570 |
|
|
/* skip over versioning */ |
571 |
|
|
if (*scan == 'v' && isdigit(scan[1])) { |
572 |
|
|
++scan; |
573 |
|
|
while (isdigit(*scan)) ++scan; |
574 |
|
|
} |
575 |
|
|
/* look for "1/6" or "1 / 6" or "1 of 6" or "1-of-6" or "1o6" */ |
576 |
|
|
if (isdigit(*scan) && |
577 |
|
|
(scan[1] == '/' |
578 |
|
|
|| (scan[1] == ' ' && scan[2] == '/') |
579 |
|
|
|| (scan[1] == ' ' && scan[2] == 'o' && scan[3] == 'f') |
580 |
|
|
|| (scan[1] == '-' && scan[2] == 'o' && scan[3] == 'f') |
581 |
|
|
|| (scan[1] == 'o' && isdigit(scan[2])))) { |
582 |
|
|
while (isdigit(scan[-1])) scan--; |
583 |
|
|
part = 0; |
584 |
|
|
while (isdigit(*scan)) { |
585 |
|
|
part = part * 10 + *scan++ - '0'; |
586 |
|
|
} |
587 |
|
|
while (*scan != '\0' && *scan != '\n' && !isdigit(*scan)) scan++; |
588 |
|
|
if (isdigit(*scan)) { |
589 |
|
|
nparts = 0; |
590 |
|
|
while (isdigit(*scan)) { |
591 |
|
|
nparts = nparts * 10 + *scan++ - '0'; |
592 |
|
|
} |
593 |
|
|
} |
594 |
|
|
break; |
595 |
|
|
} |
596 |
|
|
|
597 |
|
|
/* look for "6 parts" or "part 1" */ |
598 |
|
|
if (!strncasecmp("part", scan, 4)) { |
599 |
|
|
if (scan[4] == 's') { |
600 |
|
|
for (bak = scan; bak >= subject && !isdigit(*bak); bak--); |
601 |
|
|
if (bak > subject) { |
602 |
|
|
while (bak > subject && isdigit(bak[-1])) bak--; |
603 |
|
|
nparts = 0; |
604 |
|
|
while (isdigit(*bak)) { |
605 |
|
|
nparts = nparts * 10 + *bak++ - '0'; |
606 |
|
|
} |
607 |
|
|
} |
608 |
|
|
} else { |
609 |
|
|
while (*scan && *scan != '\n' && !isdigit(*scan)) scan++; |
610 |
|
|
bak = scan - 1; |
611 |
|
|
if (isdigit(*scan)) { |
612 |
|
|
part = 0; |
613 |
|
|
do { |
614 |
|
|
part = part * 10 + *scan++ - '0'; |
615 |
|
|
} while (isdigit(*scan)); |
616 |
|
|
} |
617 |
|
|
scan = bak; |
618 |
|
|
} |
619 |
|
|
} |
620 |
|
|
scan++; |
621 |
|
|
} |
622 |
|
|
|
623 |
|
|
if (nparts == 0 || part == -1 || part > nparts) return 1; |
624 |
|
|
*partp = part; |
625 |
|
|
*npartsp = nparts; |
626 |
|
|
return 0; |
627 |
|
|
} |
628 |
|
|
|
629 |
|
|
/* |
630 |
|
|
* Return nonzero if 'line' should mark the end of a part-1 description |
631 |
|
|
*/ |
632 |
|
|
int |
633 |
|
|
descEnd(char *line) |
634 |
|
|
{ |
635 |
|
|
return !strncmp(line, "---", 3) || |
636 |
|
|
!strncmp(line, "#!", 2) || |
637 |
|
|
!strncasecmp(line, "part=", 5) || |
638 |
|
|
!strncasecmp(line, "begin", 5); |
639 |
|
|
} |
640 |
|
|
|
641 |
|
|
/* |
642 |
|
|
* Open and return a file pointer for a description file for 'fname'. |
643 |
|
|
* If a description file for 'fname' already exists, or if there is an |
644 |
|
|
* error, return a null pointer. |
645 |
|
|
*/ |
646 |
|
|
static FILE *startDescFile(char *fname) |
647 |
|
|
{ |
648 |
|
|
char buf[1024]; |
649 |
|
|
char *dir; |
650 |
|
|
FILE *descfile; |
651 |
|
|
|
652 |
|
|
/* Create directory to store parts and copy this part there. */ |
653 |
|
|
dir = os_idtodir(fname); |
654 |
|
|
if (!dir) return 0; |
655 |
|
|
sprintf(buf, "%s0", dir); |
656 |
|
|
|
657 |
|
|
/* See if part 0 already exists, return failure if so */ |
658 |
|
|
descfile = fopen(buf, "r"); |
659 |
|
|
if (descfile) { |
660 |
|
|
fclose(descfile); |
661 |
|
|
return 0; |
662 |
|
|
} |
663 |
|
|
|
664 |
|
|
descfile = os_createnewfile(buf); |
665 |
|
|
if (!descfile) { |
666 |
|
|
os_perror(buf); |
667 |
|
|
return 0; |
668 |
|
|
} |
669 |
|
|
return descfile; |
670 |
|
|
} |
671 |
|
|
|
672 |
|
|
/* |
673 |
|
|
* Decode the uuencoded file that is in 'nparts' pieces in 'dir'. |
674 |
|
|
*/ |
675 |
|
|
int |
676 |
|
|
uudecodefiles(char *dir, int nparts) |
677 |
|
|
{ |
678 |
|
|
int part; |
679 |
|
|
enum {st_start, st_inactive, st_decode, st_nextlast, st_last, |
680 |
|
|
st_binhex} state; |
681 |
|
|
FILE *infile; |
682 |
|
|
FILE *outfile = NULL; |
683 |
|
|
struct part *inpart; |
684 |
|
|
char buf[1024]; |
685 |
|
|
char lastline[UULENGTH+1]; |
686 |
|
|
char *fname, *p; |
687 |
|
|
char *contentType = "application/octet-stream"; |
688 |
|
|
int line_length = 0; |
689 |
|
|
|
690 |
|
|
/* If a part 0, copy to description filename */ |
691 |
|
|
sprintf(buf, "%s0", dir); |
692 |
|
|
infile = fopen(buf, "r"); |
693 |
|
|
if (infile) { |
694 |
|
|
outfile = os_createnewfile(TEMPFILENAME); |
695 |
|
|
if (outfile) { |
696 |
|
|
while (fgets(buf, sizeof(buf), infile)) { |
697 |
|
|
fputs(buf, outfile); |
698 |
|
|
} |
699 |
|
|
fclose(outfile); |
700 |
|
|
outfile = NULL; |
701 |
|
|
} |
702 |
|
|
fclose(infile); |
703 |
|
|
sprintf(buf, "%s0", dir); |
704 |
|
|
remove(buf); |
705 |
|
|
} |
706 |
|
|
|
707 |
|
|
state = st_start; |
708 |
|
|
|
709 |
|
|
/* Handle each part in order */ |
710 |
|
|
for (part = 1; part <= nparts; part++) { |
711 |
|
|
sprintf(buf, "%s%d", dir, part); |
712 |
|
|
infile = fopen(buf, "r"); |
713 |
|
|
if (!infile) { |
714 |
|
|
os_perror(buf); |
715 |
|
|
if (outfile) fclose(outfile); |
716 |
|
|
remove(TEMPFILENAME); |
717 |
|
|
return 1; |
718 |
|
|
} |
719 |
|
|
|
720 |
|
|
while (fgets(buf, sizeof(buf), infile)) { |
721 |
|
|
switch (state) { |
722 |
|
|
case st_start: /* Looking for start of uuencoded |
723 |
|
|
* or binhex'ed file */ |
724 |
|
|
if (!strcmp(buf, |
725 |
|
|
"(This file must be converted with BinHex 4.0)\n")) { |
726 |
|
|
state = st_binhex; |
727 |
|
|
inpart = part_init(infile); |
728 |
|
|
os_binhex(inpart, part, nparts); |
729 |
|
|
part_close(inpart); |
730 |
|
|
goto endbinhex; |
731 |
|
|
} |
732 |
|
|
if (strncmp(buf, "begin ", 6)) break; |
733 |
|
|
/* skip mode */ |
734 |
|
|
p = buf + 6; |
735 |
|
|
while (*p && !isspace(*p)) p++; |
736 |
|
|
while (*p && isspace(*p)) p++; |
737 |
|
|
fname = p; |
738 |
|
|
while (*p && !isspace(*p)) p++; |
739 |
|
|
*p = '\0'; |
740 |
|
|
if (!*fname) return 1; |
741 |
|
|
|
742 |
|
|
/* Guess the content-type of common filename extensions */ |
743 |
jmc |
1.4 |
if ((p = strrchr(fname, '.'))) { |
744 |
edhill |
1.1 |
if (!strcasecmp(p, ".gif")) contentType = "image/gif"; |
745 |
|
|
if (!strcasecmp(p, ".jpg")) contentType = "image/jpeg"; |
746 |
|
|
if (!strcasecmp(p, ".jpeg")) contentType = "image/jpeg"; |
747 |
|
|
if (!strcasecmp(p, ".mpg")) contentType = "video/mpeg"; |
748 |
|
|
if (!strcasecmp(p, ".mpeg")) contentType = "video/mpeg"; |
749 |
|
|
} |
750 |
|
|
|
751 |
|
|
/* Create output file and start decoding */ |
752 |
|
|
outfile = os_newtypedfile(fname, contentType, FILE_BINARY, |
753 |
|
|
(params) 0); |
754 |
|
|
if (!outfile) { |
755 |
|
|
fclose(infile); |
756 |
|
|
return 1; |
757 |
|
|
} |
758 |
|
|
state = st_decode; |
759 |
|
|
break; |
760 |
|
|
|
761 |
|
|
case st_inactive: /* Looking for uuencoded data to resume */ |
762 |
|
|
if (*buf != 'M' || strlen(buf) != line_length) { |
763 |
|
|
if (*buf == 'B' && !strncmp(buf, "BEGIN", 5)) { |
764 |
|
|
state = st_decode; |
765 |
|
|
} |
766 |
|
|
break; |
767 |
|
|
} |
768 |
|
|
state = st_decode; |
769 |
|
|
/* FALL THROUGH */ |
770 |
|
|
case st_decode: /* Decoding data */ |
771 |
|
|
if (line_length == 0) line_length = strlen(buf); |
772 |
|
|
if (*buf == 'M' && strlen(buf) == line_length) { |
773 |
|
|
uudecodeline(buf, outfile); |
774 |
|
|
break; |
775 |
|
|
} |
776 |
|
|
if (strlen(buf) > line_length) { |
777 |
|
|
state = st_inactive; |
778 |
|
|
break; |
779 |
|
|
} |
780 |
|
|
/* |
781 |
|
|
* May be on nearing end of file. |
782 |
|
|
* Save this line in case we are. |
783 |
|
|
*/ |
784 |
|
|
strcpy(lastline, buf); |
785 |
|
|
if (*buf == ' ' || *buf == '`') { |
786 |
|
|
state = st_last; |
787 |
|
|
} |
788 |
|
|
else { |
789 |
|
|
state = st_nextlast; |
790 |
|
|
} |
791 |
|
|
break; |
792 |
|
|
|
793 |
|
|
case st_nextlast: /* May be nearing end of file */ |
794 |
|
|
if (*buf == ' ' || *buf == '`') { |
795 |
|
|
state = st_last; |
796 |
|
|
} |
797 |
|
|
else { |
798 |
|
|
state = st_inactive; |
799 |
|
|
} |
800 |
|
|
break; |
801 |
|
|
|
802 |
|
|
case st_last: /* Should be at end of file */ |
803 |
|
|
if (!strncmp(buf, "end", 3) && isspace(buf[3])) { |
804 |
|
|
/* Handle that last line we saved */ |
805 |
|
|
uudecodeline(lastline, outfile); |
806 |
|
|
fclose(infile); |
807 |
|
|
os_closetypedfile(outfile); |
808 |
|
|
for (;part <= nparts; part++) { |
809 |
|
|
sprintf(buf, "%s%d", dir, part); |
810 |
|
|
remove(buf); |
811 |
|
|
} |
812 |
|
|
sprintf(buf, "%sCT", dir); |
813 |
|
|
remove(buf); |
814 |
|
|
os_donewithdir(dir); |
815 |
|
|
return 0; |
816 |
|
|
} |
817 |
|
|
state = st_inactive; |
818 |
|
|
break; |
819 |
|
|
|
820 |
|
|
case st_binhex: |
821 |
|
|
if (strncmp(buf, "---", 3)) break; |
822 |
|
|
inpart = part_init(infile); |
823 |
|
|
os_binhex(inpart, part, nparts); |
824 |
|
|
part_close(inpart); |
825 |
|
|
goto endbinhex; |
826 |
|
|
} |
827 |
|
|
} |
828 |
|
|
if (state != st_binhex) state = st_inactive; |
829 |
|
|
fclose(infile); |
830 |
|
|
endbinhex: |
831 |
|
|
sprintf(buf, "%s%d", dir, part); |
832 |
|
|
remove(buf); |
833 |
|
|
} |
834 |
|
|
if (outfile) os_closetypedfile(outfile); |
835 |
|
|
if (state == st_binhex) os_binhex(0, 0, 0); |
836 |
|
|
sprintf(buf, "%sCT", dir); |
837 |
|
|
remove(buf); |
838 |
|
|
os_donewithdir(dir); |
839 |
|
|
return 0; |
840 |
|
|
} |
841 |
|
|
|
842 |
|
|
#define DEC(c) (((c) - ' ') & 077) |
843 |
|
|
|
844 |
|
|
/* |
845 |
|
|
* Decode a uuencoded line to 'outfile' |
846 |
|
|
*/ |
847 |
jmc |
1.4 |
static void uudecodeline(char *line, FILE *outfile) |
848 |
edhill |
1.1 |
{ |
849 |
|
|
int c, len; |
850 |
|
|
|
851 |
|
|
len = DEC(*line++); |
852 |
|
|
while (len) { |
853 |
|
|
c = DEC(*line) << 2 | DEC(line[1]) >> 4; |
854 |
|
|
putc(c, outfile); |
855 |
|
|
if (--len) { |
856 |
|
|
c = DEC(line[1]) << 4 | DEC(line[2]) >> 2; |
857 |
|
|
putc(c, outfile); |
858 |
|
|
if (--len) { |
859 |
|
|
c = DEC(line[2]) << 6 | DEC(line[3]); |
860 |
|
|
putc(c, outfile); |
861 |
|
|
len--; |
862 |
|
|
} |
863 |
|
|
} |
864 |
|
|
line += 4; |
865 |
|
|
} |
866 |
|
|
} |
867 |
|
|
|
868 |
|
|
|