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