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