xref: /plan9-contrib/sys/src/cmd/ip/imap4d/fetch.c (revision 7dd7cddf99dd7472612f1413b4da293630e6b1bc)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include "imap4d.h"
5 
6 char *fetchPartNames[FPMax] =
7 {
8 	"",
9 	"HEADER",
10 	"HEADER.FIELDS",
11 	"HEADER.FIELDS.NOT",
12 	"MIME",
13 	"TEXT",
14 };
15 
16 /*
17  * implicitly set the \seen flag.  done in a separate pass
18  * so the .imp file doesn't need to be open while the
19  * messages are sent to the client.
20  */
21 int
22 fetchSeen(Box *box, Msg *m, int uids, void *vf)
23 {
24 	Fetch *f;
25 
26 	USED(uids);
27 
28 	if(m->expunged)
29 		return uids;
30 	for(f = vf; f != nil; f = f->next){
31 		switch(f->op){
32 		case FRfc822:
33 		case FRfc822Text:
34 		case FBodySect:
35 			msgSeen(box, m);
36 			goto breakout;
37 		}
38 	}
39 breakout:
40 
41 	return 1;
42 }
43 
44 /*
45  * fetch messages
46  *
47  * imap4 body[] requestes get translated to upas/fs files as follows
48  *	body[id.header] == id/rawheader file + extra \r\n
49  *	body[id.text] == id/rawbody
50  *	body[id.mime] == id/mimeheader + extra \r\n
51  *	body[id] === body[id.header] + body[id.text]
52 */
53 int
54 fetchMsg(Box *box, Msg *m, int uids, void *vf)
55 {
56 	Tm tm;
57 	Fetch *f;
58 	char *sep;
59 	int todo;
60 
61 	if(m->expunged)
62 		return uids;
63 
64 	todo = 0;
65 	for(f = vf; f != nil; f = f->next){
66 		switch(f->op){
67 		case FFlags:
68 			/*
69 			 * flags get sent with status update
70 			 */
71 			box->sendFlags = 1;
72 			m->sendFlags = 1;
73 			break;
74 		case FUid:
75 			todo = 1;
76 			break;
77 		case FInternalDate:
78 		case FEnvelope:
79 		case FRfc822:
80 		case FRfc822Head:
81 		case FRfc822Size:
82 		case FRfc822Text:
83 		case FBodySect:
84 		case FBodyPeek:
85 		case FBody:
86 		case FBodyStruct:
87 			todo = 1;
88 			if(!msgStruct(m, 1)){
89 				msgDead(m);
90 				return uids;
91 			}
92 			break;
93 		default:
94 			bye("bad implementation of fetch");
95 			return 0;
96 		}
97 	}
98 
99 	if(m->expunged)
100 		return uids;
101 	if(!todo)
102 		return 1;
103 
104 	/*
105 	 * note: it is allowed to send back the responses one at a time
106 	 * rather than all together.  this is exploited to send flags elsewhere.
107 	 */
108 	Bprint(&bout, "* %lud fetch (", m->seq);
109 	sep = "";
110 	if(uids){
111 		Bprint(&bout, "uid %lud", m->uid);
112 		sep = " ";
113 	}
114 	for(f = vf; f != nil; f = f->next){
115 		switch(f->op){
116 		default:
117 			bye("bad implementation of fetch");
118 			break;
119 		case FFlags:
120 			continue;
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 *
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
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
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
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
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 /*
372  * find the numbered sub-part of the message
373  */
374 Msg*
375 findMsgSect(Msg *m, NList *sect)
376 {
377 	ulong id;
378 
379 	for(; sect != nil; sect = sect->next){
380 		id = sect->n;
381 		for(m = m->kids; m != nil; m = m->next)
382 			if(m->id == id)
383 				break;
384 		if(m == nil)
385 			return nil;
386 	}
387 	return m;
388 }
389 
390 void
391 fetchEnvelope(Msg *m)
392 {
393 	Tm tm;
394 
395 	Bputc(&bout, '(');
396 	Brfc822date(&bout, date2tm(&tm, m->info[IDate]));
397 	Bputc(&bout, ' ');
398 	Bimapstr(&bout, m->info[ISubject]);
399 	Bputc(&bout, ' ');
400 	Bimapaddr(&bout, m->from);
401 	Bputc(&bout, ' ');
402 	Bimapaddr(&bout, m->sender);
403 	Bputc(&bout, ' ');
404 	Bimapaddr(&bout, m->replyTo);
405 	Bputc(&bout, ' ');
406 	Bimapaddr(&bout, m->to);
407 	Bputc(&bout, ' ');
408 	Bimapaddr(&bout, m->cc);
409 	Bputc(&bout, ' ');
410 	Bimapaddr(&bout, m->bcc);
411 	Bputc(&bout, ' ');
412 	Bimapstr(&bout, m->info[IInReplyTo]);
413 	Bputc(&bout, ' ');
414 	Bimapstr(&bout, m->info[IMessageId]);
415 	Bputc(&bout, ')');
416 }
417 
418 void
419 fetchBodyStruct(Msg *m, Header *h, int extensions)
420 {
421 	Msg *k;
422 	ulong len;
423 
424 	if(msgIsMulti(h)){
425 		Bputc(&bout, '(');
426 		for(k = m->kids; k != nil; k = k->next)
427 			fetchBodyStruct(k, &k->mime, extensions);
428 
429 		Bputc(&bout, ' ');
430 		Bimapstr(&bout, h->type->t);
431 
432 		if(extensions){
433 			Bputc(&bout, ' ');
434 			BimapMimeParams(&bout, h->type->next);
435 			fetchStructExt(h);
436 		}
437 
438 		Bputc(&bout, ')');
439 		return;
440 	}
441 
442 	Bputc(&bout, '(');
443 	if(h->type != nil){
444 		Bimapstr(&bout, h->type->s);
445 		Bputc(&bout, ' ');
446 		Bimapstr(&bout, h->type->t);
447 		Bputc(&bout, ' ');
448 		BimapMimeParams(&bout, h->type->next);
449 	}else
450 		Bprint(&bout, "\"text\" \"plain\" NIL");
451 
452 	Bputc(&bout, ' ');
453 	if(h->id != nil)
454 		Bimapstr(&bout, h->id->s);
455 	else
456 		Bprint(&bout, "NIL");
457 
458 	Bputc(&bout, ' ');
459 	if(h->description != nil)
460 		Bimapstr(&bout, h->description->s);
461 	else
462 		Bprint(&bout, "NIL");
463 
464 	Bputc(&bout, ' ');
465 	if(h->encoding != nil)
466 		Bimapstr(&bout, h->encoding->s);
467 	else
468 		Bprint(&bout, "NIL");
469 
470 	/*
471 	 * this is so strange: return lengths for a body[text] response,
472 	 * except in the case of a multipart message, when return lengths for a body[] repsonse
473 	 */
474 	len = m->size;
475 	if(h == &m->mime)
476 		len += m->head.size;
477 	Bprint(&bout, " %lud", len);
478 
479 	len = m->lines;
480 	if(h == &m->mime)
481 		len += m->head.lines;
482 
483 	if(h->type == nil || cistrcmp(h->type->s, "text") == 0){
484 		Bprint(&bout, " %lud", len);
485 	}else if(msgIsRfc822(h)){
486 		Bputc(&bout, ' ');
487 		k = m;
488 		if(h != &m->mime)
489 			k = m->kids;
490 		if(k == nil)
491 			Bprint(&bout, "(NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) (\"text\" \"plain\" NIL NIL NIL NIL 0 0) 0");
492 		else{
493 			fetchEnvelope(k);
494 			Bputc(&bout, ' ');
495 			fetchBodyStruct(k, &k->head, extensions);
496 			Bprint(&bout, " %lud", len);
497 		}
498 	}
499 
500 	if(extensions){
501 		Bputc(&bout, ' ');
502 
503 		/*
504 		 * don't have the md5 laying around,
505 		 * since the header & body have added newlines.
506 		 */
507 		Bprint(&bout, "NIL");
508 
509 		fetchStructExt(h);
510 	}
511 	Bputc(&bout, ')');
512 }
513 
514 /*
515  * common part of bodystructure extensions
516  */
517 void
518 fetchStructExt(Header *h)
519 {
520 	Bputc(&bout, ' ');
521 	if(h->disposition != nil){
522 		Bputc(&bout, '(');
523 		Bimapstr(&bout, h->disposition->s);
524 		Bputc(&bout, ' ');
525 		BimapMimeParams(&bout, h->disposition->next);
526 		Bputc(&bout, ')');
527 	}else
528 		Bprint(&bout, "NIL");
529 	Bputc(&bout, ' ');
530 	if(h->language != nil){
531 		if(h->language->next != nil)
532 			BimapMimeParams(&bout, h->language->next);
533 		else
534 			Bimapstr(&bout, h->language->s);
535 	}else
536 		Bprint(&bout, "NIL");
537 }
538 
539 int
540 BimapMimeParams(Biobuf *b, MimeHdr *mh)
541 {
542 	char *sep;
543 	int n;
544 
545 	if(mh == nil)
546 		return Bprint(b, "NIL");
547 
548 	n = Bputc(b, '(');
549 
550 	sep = "";
551 	for(; mh != nil; mh = mh->next){
552 		n += Bprint(b, sep);
553 		n += Bimapstr(b, mh->s);
554 		n += Bputc(b, ' ');
555 		n += Bimapstr(b, mh->t);
556 		sep = " ";
557 	}
558 
559 	n += Bputc(b, ')');
560 	return n;
561 }
562 
563 /*
564  * print a list of addresses;
565  * each address is printed as '(' personalName AtDomainList mboxName hostName ')'
566  * the AtDomainList is always NIL
567  */
568 int
569 Bimapaddr(Biobuf *b, MAddr *a)
570 {
571 	char *host, *sep;
572 	int n;
573 
574 	if(a == nil)
575 		return Bprint(b, "NIL");
576 
577 	n = Bputc(b, '(');
578 	sep = "";
579 	for(; a != nil; a = a->next){
580 		n += Bprint(b, "%s(", sep);
581 		n += Bimapstr(b, a->personal);
582 		n += Bprint(b," NIL ");
583 		n += Bimapstr(b, a->box);
584 		n += Bputc(b, ' ');
585 
586 		/*
587 		 * can't send NIL as hostName, since that is code for a group
588 		 */
589 		host = a->host;
590 		if(host == nil)
591 			host = "";
592 		n += Bimapstr(b, host);
593 
594 		n += Bputc(b, ')');
595 		sep = " ";
596 	}
597 	n += Bputc(b, ')');
598 	return n;
599 }
600