xref: /plan9/sys/src/cmd/ip/imap4d/fetch.c (revision becaf2ab37b54217d3c714aa13bc7d74eeebce41)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <auth.h>
5 #include "imap4d.h"
6 
7 char *fetchPartNames[FPMax] =
8 {
9 	"",
10 	"HEADER",
11 	"HEADER.FIELDS",
12 	"HEADER.FIELDS.NOT",
13 	"MIME",
14 	"TEXT",
15 };
16 
17 /*
18  * implicitly set the \seen flag.  done in a separate pass
19  * so the .imp file doesn't need to be open while the
20  * messages are sent to the client.
21  */
22 int
23 fetchSeen(Box *box, Msg *m, int uids, void *vf)
24 {
25 	Fetch *f;
26 
27 	USED(uids);
28 
29 	if(m->expunged)
30 		return uids;
31 	for(f = vf; f != nil; f = f->next){
32 		switch(f->op){
33 		case FRfc822:
34 		case FRfc822Text:
35 		case FBodySect:
36 			msgSeen(box, m);
37 			goto breakout;
38 		}
39 	}
40 breakout:
41 
42 	return 1;
43 }
44 
45 /*
46  * fetch messages
47  *
48  * imap4 body[] requestes get translated to upas/fs files as follows
49  *	body[id.header] == id/rawheader file + extra \r\n
50  *	body[id.text] == id/rawbody
51  *	body[id.mime] == id/mimeheader + extra \r\n
52  *	body[id] === body[id.header] + body[id.text]
53 */
54 int
55 fetchMsg(Box *, Msg *m, int uids, void *vf)
56 {
57 	Tm tm;
58 	Fetch *f;
59 	char *sep;
60 	int todo;
61 
62 	if(m->expunged)
63 		return uids;
64 
65 	todo = 0;
66 	for(f = vf; f != nil; f = f->next){
67 		switch(f->op){
68 		case FFlags:
69 			break;
70 		case FUid:
71 			todo = 1;
72 			break;
73 		case FInternalDate:
74 		case FEnvelope:
75 		case FRfc822:
76 		case FRfc822Head:
77 		case FRfc822Size:
78 		case FRfc822Text:
79 		case FBodySect:
80 		case FBodyPeek:
81 		case FBody:
82 		case FBodyStruct:
83 			todo = 1;
84 			if(!msgStruct(m, 1)){
85 				msgDead(m);
86 				return uids;
87 			}
88 			break;
89 		default:
90 			bye("bad implementation of fetch");
91 			return 0;
92 		}
93 	}
94 
95 	if(m->expunged)
96 		return uids;
97 	if(!todo)
98 		return 1;
99 
100 	/*
101 	 * note: it is allowed to send back the responses one at a time
102 	 * rather than all together.  this is exploited to send flags elsewhere.
103 	 */
104 	Bprint(&bout, "* %lud FETCH (", m->seq);
105 	sep = "";
106 	if(uids){
107 		Bprint(&bout, "uid %lud", m->uid);
108 		sep = " ";
109 	}
110 	for(f = vf; f != nil; f = f->next){
111 		switch(f->op){
112 		default:
113 			bye("bad implementation of fetch");
114 			break;
115 		case FFlags:
116 			Bprint(&bout, "%sflags (", sep);
117 			writeFlags(&bout, m, 1);
118 			Bprint(&bout, ")");
119 			break;
120 		case FUid:
121 			if(uids)
122 				continue;
123 			Bprint(&bout, "%suid %lud", sep, m->uid);
124 			break;
125 		case FEnvelope:
126 			Bprint(&bout, "%senvelope ", sep);
127 			fetchEnvelope(m);
128 			break;
129 		case FInternalDate:
130 			Bprint(&bout, "%sinternaldate ", sep);
131 			Bimapdate(&bout, date2tm(&tm, m->unixDate));
132 			break;
133 		case FBody:
134 			Bprint(&bout, "%sbody ", sep);
135 			fetchBodyStruct(m, &m->head, 0);
136 			break;
137 		case FBodyStruct:
138 			Bprint(&bout, "%sbodystructure ", sep);
139 			fetchBodyStruct(m, &m->head, 1);
140 			break;
141 		case FRfc822Size:
142 			Bprint(&bout, "%sRFC822.SIZE %lud", sep, msgSize(m));
143 			break;
144 		case FRfc822:
145 			f->part = FPAll;
146 			Bprint(&bout, "%sRFC822", sep);
147 			fetchBody(m, f);
148 			break;
149 		case FRfc822Head:
150 			f->part = FPHead;
151 			Bprint(&bout, "%srfc822.header", sep);
152 			fetchBody(m, f);
153 			break;
154 		case FRfc822Text:
155 			f->part = FPText;
156 			Bprint(&bout, "%srfc822.text", sep);
157 			fetchBody(m, f);
158 			break;
159 		case FBodySect:
160 		case FBodyPeek:
161 			Bprint(&bout, "%sbody", sep);
162 			fetchBody(fetchSect(m, f), f);
163 			break;
164 		}
165 		sep = " ";
166 	}
167 	Bprint(&bout, ")\r\n");
168 
169 	return 1;
170 }
171 
172 /*
173  * print out section, part, headers;
174  * find and return message section
175  */
176 Msg *
177 fetchSect(Msg *m, Fetch *f)
178 {
179 	Bputc(&bout, '[');
180 	BNList(&bout, f->sect, ".");
181 	if(f->part != FPAll){
182 		if(f->sect != nil)
183 			Bputc(&bout, '.');
184 		Bprint(&bout, "%s", fetchPartNames[f->part]);
185 		if(f->hdrs != nil){
186 			Bprint(&bout, " (");
187 			BSList(&bout, f->hdrs, " ");
188 			Bputc(&bout, ')');
189 		}
190 	}
191 	Bprint(&bout, "]");
192 	return findMsgSect(m, f->sect);
193 }
194 
195 /*
196  * actually return the body pieces
197  */
198 void
199 fetchBody(Msg *m, Fetch *f)
200 {
201 	Pair p;
202 	char *s, *t, *e, buf[BufSize + 2];
203 	ulong n, start, stop, pos;
204 	int fd, nn;
205 
206 	if(m == nil){
207 		fetchBodyStr(f, "", 0);
208 		return;
209 	}
210 	switch(f->part){
211 	case FPHeadFields:
212 	case FPHeadFieldsNot:
213 		n = m->head.size + 3;
214 		s = emalloc(n);
215 		n = selectFields(s, n, m->head.buf, f->hdrs, f->part == FPHeadFields);
216 		fetchBodyStr(f, s, n);
217 		free(s);
218 		return;
219 	case FPHead:
220 		fetchBodyStr(f, m->head.buf, m->head.size);
221 		return;
222 	case FPMime:
223 		fetchBodyStr(f, m->mime.buf, m->mime.size);
224 		return;
225 	case FPAll:
226 		fd = msgFile(m, "rawbody");
227 		if(fd < 0){
228 			msgDead(m);
229 			fetchBodyStr(f, "", 0);
230 			return;
231 		}
232 		p = fetchBodyPart(f, msgSize(m));
233 		start = p.start;
234 		if(start < m->head.size){
235 			stop = p.stop;
236 			if(stop > m->head.size)
237 				stop = m->head.size;
238 			Bwrite(&bout, &m->head.buf[start], stop - start);
239 			start = 0;
240 			stop = p.stop;
241 			if(stop <= m->head.size){
242 				close(fd);
243 				return;
244 			}
245 		}else
246 			start -= m->head.size;
247 		stop = p.stop - m->head.size;
248 		break;
249 	case FPText:
250 		fd = msgFile(m, "rawbody");
251 		if(fd < 0){
252 			msgDead(m);
253 			fetchBodyStr(f, "", 0);
254 			return;
255 		}
256 		p = fetchBodyPart(f, m->size);
257 		start = p.start;
258 		stop = p.stop;
259 		break;
260 	default:
261 		fetchBodyStr(f, "", 0);
262 		return;
263 	}
264 
265 	/*
266 	 * read in each block, convert \n without \r to \r\n.
267 	 * this means partial fetch requires fetching everything
268 	 * through stop, since we don't know how many \r's will be added
269 	 */
270 	buf[0] = ' ';
271 	for(pos = 0; pos < stop; ){
272 		n = BufSize;
273 		if(n > stop - pos)
274 			n = stop - pos;
275 		n = read(fd, &buf[1], n);
276 		if(n <= 0){
277 			fetchBodyFill(stop - pos);
278 			break;
279 		}
280 		e = &buf[n + 1];
281 		*e = '\0';
282 		for(s = &buf[1]; s < e && pos < stop; s = t + 1){
283 			t = memchr(s, '\n', e - s);
284 			if(t == nil)
285 				t = e;
286 			n = t - s;
287 			if(pos < start){
288 				if(pos + n <= start){
289 					s = t;
290 					pos += n;
291 				}else{
292 					s += start - pos;
293 					pos = start;
294 				}
295 				n = t - s;
296 			}
297 			nn = n;
298 			if(pos + nn > stop)
299 				nn = stop - pos;
300 			if(Bwrite(&bout, s, nn) != nn)
301 				writeErr();
302 			pos += n;
303 			if(*t == '\n'){
304 				if(t[-1] != '\r'){
305 					if(pos >= start && pos < stop)
306 						Bputc(&bout, '\r');
307 					pos++;
308 				}
309 				if(pos >= start && pos < stop)
310 					Bputc(&bout, '\n');
311 				pos++;
312 			}
313 		}
314 		buf[0] = e[-1];
315 	}
316 	close(fd);
317 }
318 
319 /*
320  * resolve the actual bounds of any partial fetch,
321  * and print out the bounds & size of string returned
322  */
323 Pair
324 fetchBodyPart(Fetch *f, ulong size)
325 {
326 	Pair p;
327 	ulong start, stop;
328 
329 	start = 0;
330 	stop = size;
331 	if(f->partial){
332 		start = f->start;
333 		if(start > size)
334 			start = size;
335 		stop = start + f->size;
336 		if(stop > size)
337 			stop = size;
338 		Bprint(&bout, "<%lud>", start);
339 	}
340 	Bprint(&bout, " {%lud}\r\n", stop - start);
341 	p.start = start;
342 	p.stop = stop;
343 	return p;
344 }
345 
346 /*
347  * something went wrong fetching data
348  * produce fill bytes for what we've committed to produce
349  */
350 void
351 fetchBodyFill(ulong n)
352 {
353 	while(n-- > 0)
354 		if(Bputc(&bout, ' ') < 0)
355 			writeErr();
356 }
357 
358 /*
359  * return a simple string
360  */
361 void
362 fetchBodyStr(Fetch *f, char *buf, ulong size)
363 {
364 	Pair p;
365 
366 	p = fetchBodyPart(f, size);
367 	Bwrite(&bout, &buf[p.start], p.stop-p.start);
368 }
369 
370 char*
371 printnlist(NList *sect)
372 {
373 	static char buf[100];
374 	char *p;
375 
376 	for(p= buf; sect; sect=sect->next){
377 		p += sprint(p, "%ld", sect->n);
378 		if(sect->next)
379 			*p++ = '.';
380 	}
381 	*p = '\0';
382 	return buf;
383 }
384 
385 /*
386  * find the numbered sub-part of the message
387  */
388 Msg*
389 findMsgSect(Msg *m, NList *sect)
390 {
391 	ulong id;
392 
393 	for(; sect != nil; sect = sect->next){
394 		id = sect->n;
395 #ifdef HACK
396 		/* HACK to solve extra level of structure not visible from upas/fs  */
397 		if(m->kids == 0 && id == 1 && sect->next == nil){
398 			if(m->mime.type->s && strcmp(m->mime.type->s, "message")==0)
399 			if(m->mime.type->t && strcmp(m->mime.type->t, "rfc822")==0)
400 			if(m->head.type->s && strcmp(m->head.type->s, "text")==0)
401 			if(m->head.type->t && strcmp(m->head.type->t, "plain")==0)
402 				break;
403 		}
404 		/* end of HACK */
405 #endif HACK
406 		for(m = m->kids; m != nil; m = m->next)
407 			if(m->id == id)
408 				break;
409 		if(m == nil)
410 			return nil;
411 	}
412 	return m;
413 }
414 
415 void
416 fetchEnvelope(Msg *m)
417 {
418 	Tm tm;
419 
420 	Bputc(&bout, '(');
421 	Brfc822date(&bout, date2tm(&tm, m->info[IDate]));
422 	Bputc(&bout, ' ');
423 	Bimapstr(&bout, m->info[ISubject]);
424 	Bputc(&bout, ' ');
425 	Bimapaddr(&bout, m->from);
426 	Bputc(&bout, ' ');
427 	Bimapaddr(&bout, m->sender);
428 	Bputc(&bout, ' ');
429 	Bimapaddr(&bout, m->replyTo);
430 	Bputc(&bout, ' ');
431 	Bimapaddr(&bout, m->to);
432 	Bputc(&bout, ' ');
433 	Bimapaddr(&bout, m->cc);
434 	Bputc(&bout, ' ');
435 	Bimapaddr(&bout, m->bcc);
436 	Bputc(&bout, ' ');
437 	Bimapstr(&bout, m->info[IInReplyTo]);
438 	Bputc(&bout, ' ');
439 	Bimapstr(&bout, m->info[IMessageId]);
440 	Bputc(&bout, ')');
441 }
442 
443 void
444 fetchBodyStruct(Msg *m, Header *h, int extensions)
445 {
446 	Msg *k;
447 	ulong len;
448 
449 	if(msgIsMulti(h)){
450 		Bputc(&bout, '(');
451 		for(k = m->kids; k != nil; k = k->next)
452 			fetchBodyStruct(k, &k->mime, extensions);
453 
454 		Bputc(&bout, ' ');
455 		Bimapstr(&bout, h->type->t);
456 
457 		if(extensions){
458 			Bputc(&bout, ' ');
459 			BimapMimeParams(&bout, h->type->next);
460 			fetchStructExt(h);
461 		}
462 
463 		Bputc(&bout, ')');
464 		return;
465 	}
466 
467 	Bputc(&bout, '(');
468 	if(h->type != nil){
469 		Bimapstr(&bout, h->type->s);
470 		Bputc(&bout, ' ');
471 		Bimapstr(&bout, h->type->t);
472 		Bputc(&bout, ' ');
473 		BimapMimeParams(&bout, h->type->next);
474 	}else
475 		Bprint(&bout, "\"text\" \"plain\" NIL");
476 
477 	Bputc(&bout, ' ');
478 	if(h->id != nil)
479 		Bimapstr(&bout, h->id->s);
480 	else
481 		Bprint(&bout, "NIL");
482 
483 	Bputc(&bout, ' ');
484 	if(h->description != nil)
485 		Bimapstr(&bout, h->description->s);
486 	else
487 		Bprint(&bout, "NIL");
488 
489 	Bputc(&bout, ' ');
490 	if(h->encoding != nil)
491 		Bimapstr(&bout, h->encoding->s);
492 	else
493 		Bprint(&bout, "NIL");
494 
495 	/*
496 	 * this is so strange: return lengths for a body[text] response,
497 	 * except in the case of a multipart message, when return lengths for a body[] response
498 	 */
499 	len = m->size;
500 	if(h == &m->mime)
501 		len += m->head.size;
502 	Bprint(&bout, " %lud", len);
503 
504 	len = m->lines;
505 	if(h == &m->mime)
506 		len += m->head.lines;
507 
508 	if(h->type == nil || cistrcmp(h->type->s, "text") == 0){
509 		Bprint(&bout, " %lud", len);
510 	}else if(msgIsRfc822(h)){
511 		Bputc(&bout, ' ');
512 		k = m;
513 		if(h != &m->mime)
514 			k = m->kids;
515 		if(k == nil)
516 			Bprint(&bout, "(NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) (\"text\" \"plain\" NIL NIL NIL NIL 0 0) 0");
517 		else{
518 			fetchEnvelope(k);
519 			Bputc(&bout, ' ');
520 			fetchBodyStruct(k, &k->head, extensions);
521 			Bprint(&bout, " %lud", len);
522 		}
523 	}
524 
525 	if(extensions){
526 		Bputc(&bout, ' ');
527 
528 		/*
529 		 * don't have the md5 laying around,
530 		 * since the header & body have added newlines.
531 		 */
532 		Bprint(&bout, "NIL");
533 
534 		fetchStructExt(h);
535 	}
536 	Bputc(&bout, ')');
537 }
538 
539 /*
540  * common part of bodystructure extensions
541  */
542 void
543 fetchStructExt(Header *h)
544 {
545 	Bputc(&bout, ' ');
546 	if(h->disposition != nil){
547 		Bputc(&bout, '(');
548 		Bimapstr(&bout, h->disposition->s);
549 		Bputc(&bout, ' ');
550 		BimapMimeParams(&bout, h->disposition->next);
551 		Bputc(&bout, ')');
552 	}else
553 		Bprint(&bout, "NIL");
554 	Bputc(&bout, ' ');
555 	if(h->language != nil){
556 		if(h->language->next != nil)
557 			BimapMimeParams(&bout, h->language->next);
558 		else
559 			Bimapstr(&bout, h->language->s);
560 	}else
561 		Bprint(&bout, "NIL");
562 }
563 
564 int
565 BimapMimeParams(Biobuf *b, MimeHdr *mh)
566 {
567 	char *sep;
568 	int n;
569 
570 	if(mh == nil)
571 		return Bprint(b, "NIL");
572 
573 	n = Bputc(b, '(');
574 
575 	sep = "";
576 	for(; mh != nil; mh = mh->next){
577 		n += Bprint(b, sep);
578 		n += Bimapstr(b, mh->s);
579 		n += Bputc(b, ' ');
580 		n += Bimapstr(b, mh->t);
581 		sep = " ";
582 	}
583 
584 	n += Bputc(b, ')');
585 	return n;
586 }
587 
588 /*
589  * print a list of addresses;
590  * each address is printed as '(' personalName AtDomainList mboxName hostName ')'
591  * the AtDomainList is always NIL
592  */
593 int
594 Bimapaddr(Biobuf *b, MAddr *a)
595 {
596 	char *host, *sep;
597 	int n;
598 
599 	if(a == nil)
600 		return Bprint(b, "NIL");
601 
602 	n = Bputc(b, '(');
603 	sep = "";
604 	for(; a != nil; a = a->next){
605 		n += Bprint(b, "%s(", sep);
606 		n += Bimapstr(b, a->personal);
607 		n += Bprint(b," NIL ");
608 		n += Bimapstr(b, a->box);
609 		n += Bputc(b, ' ');
610 
611 		/*
612 		 * can't send NIL as hostName, since that is code for a group
613 		 */
614 		host = a->host;
615 		if(host == nil)
616 			host = "";
617 		n += Bimapstr(b, host);
618 
619 		n += Bputc(b, ')');
620 		sep = " ";
621 	}
622 	n += Bputc(b, ')');
623 	return n;
624 }
625