Warning: Attempt to read property "date" on null in /usr/local/www/websvn.planix.org/blame.php on line 247

Warning: Attempt to read property "msg" on null in /usr/local/www/websvn.planix.org/blame.php on line 247
WebSVN – planix.SVN – Blame – /os/branches/feature_unix/acme/mail/src/mesg.c – Rev 2

Subversion Repositories planix.SVN

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
#include <u.h>
2
#include <libc.h>
3
#include <bio.h>
4
#include <thread.h>
5
#include <ctype.h>
6
#include <plumb.h>
7
#include "dat.h"
8
 
9
enum
10
{
11
	DIRCHUNK = 32*sizeof(Dir)
12
};
13
 
14
char	regexchars[] = "\\/[].+?()*^$";
15
char	deleted[] = "(deleted)-";
16
char	deletedrx[] = "\\(deleted\\)-";
17
char	deletedrx01[] = "(\\(deleted\\)-)?";
18
char	deletedaddr[] = "-#0;/^\\(deleted\\)-/";
19
 
20
struct{
21
	char	*type;
22
	char	*port;
23
	char *suffix;
24
} ports[] = {
25
	"text/",			"edit",		".txt",
26
	/* text must be first for plumbport() */
27
	"image/gif",			"image",	".gif",
28
	"image/jpeg",			"image",	".jpg",
29
	"image/jpeg",			"image",	".jpeg",
30
	"image/png",			"image",	".png",
31
	"image/tiff",			"image",	".tif",
32
	"application/postscript",	"postscript",	".ps",
33
	"application/pdf",		"postscript",	".pdf",
34
	"application/msword",		"msword",	".doc",
35
	"application/rtf",		"msword",	".rtf",
36
	"audio/x-wav",			"wav",		".wav",
37
	nil,	nil
38
};
39
 
40
char *goodtypes[] = {
41
	"text",
42
	"text/plain",
43
	"message/rfc822",
44
	"text/richtext",
45
	"text/tab-separated-values",
46
	"text/calendar",
47
	"application/octet-stream",
48
	nil,
49
};
50
 
51
struct{
52
	char *type;
53
	char	*ext;
54
} exts[] = {
55
	"image/gif",	".gif",
56
	"image/jpeg",	".jpg",
57
	nil, nil
58
};
59
 
60
char *okheaders[] =
61
{
62
	"From:",
63
	"Date:",
64
	"To:",
65
	"CC:",
66
	"Subject:",
67
	nil
68
};
69
 
70
char *extraheaders[] =
71
{
72
	"Resent-From:",
73
	"Resent-To:",
74
	"Sort:",
75
	nil,
76
};
77
 
78
char*
79
line(char *data, char **pp)
80
{
81
	char *p, *q;
82
 
83
	for(p=data; *p!='\0' && *p!='\n'; p++)
84
		;
85
	if(*p == '\n')
86
		*pp = p+1;
87
	else
88
		*pp = p;
89
	q = emalloc(p-data + 1);
90
	memmove(q, data, p-data);
91
	return q;
92
}
93
 
94
void
95
scanheaders(Message *m, char *dir)
96
{
97
	char *s, *t, *u, *f;
98
 
99
	s = f = readfile(dir, "header", nil);
100
	if(s != nil)
101
		while(*s){
102
			t = line(s, &s);
103
			if(strncmp(t, "From: ", 6) == 0){
104
				m->fromcolon = estrdup(t+6);
105
				/* remove all quotes; they're ugly and irregular */
106
				for(u=m->fromcolon; *u; u++)
107
					if(*u == '"')
108
						memmove(u, u+1, strlen(u));
109
			}
110
			if(strncmp(t, "Subject: ", 9) == 0)
111
				m->subject = estrdup(t+9);
112
			free(t);
113
		}
114
	if(m->fromcolon == nil)
115
		m->fromcolon = estrdup(m->from);
116
	free(f);
117
}
118
 
119
int
120
loadinfo(Message *m, char *dir)
121
{
122
	int n;
123
	char *data, *p, *s;
124
 
125
	data = readfile(dir, "info", &n);
126
	if(data == nil)
127
		return 0;
128
	m->from = line(data, &p);
129
	scanheaders(m, dir);	/* depends on m->from being set */
130
	m->to = line(p, &p);
131
	m->cc = line(p, &p);
132
	m->replyto = line(p, &p);
133
	m->date = line(p, &p);
134
	s = line(p, &p);
135
	if(m->subject == nil)
136
		m->subject = s;
137
	else
138
		free(s);
139
	m->type = line(p, &p);
140
	m->disposition = line(p, &p);
141
	m->filename = line(p, &p);
142
	m->digest = line(p, &p);
143
	free(data);
144
	return 1;
145
}
146
 
147
int
148
isnumeric(char *s)
149
{
150
	while(*s){
151
		if(!isdigit(*s))
152
			return 0;
153
		s++;
154
	}
155
	return 1;
156
}
157
 
158
Dir*
159
loaddir(char *name, int *np)
160
{
161
	int fd;
162
	Dir *dp;
163
 
164
	fd = open(name, OREAD);
165
	if(fd < 0)
166
		return nil;
167
	*np = dirreadall(fd, &dp);
168
	close(fd);
169
	return dp;
170
}
171
 
172
void
173
readmbox(Message *mbox, char *dir, char *subdir)
174
{
175
	char *name;
176
	Dir *d, *dirp;
177
	int i, n;
178
 
179
	name = estrstrdup(dir, subdir);
180
	dirp = loaddir(name, &n);
181
	mbox->recursed = 1;
182
	if(dirp)
183
		for(i=0; i<n; i++){
184
			d = &dirp[i];
185
			if(isnumeric(d->name))
186
				mesgadd(mbox, name, d, nil);
187
		}
188
	free(dirp);
189
	free(name);
190
}
191
 
192
/* add message to box, in increasing numerical order */
193
int
194
mesgadd(Message *mbox, char *dir, Dir *d, char *digest)
195
{
196
	Message *m;
197
	char *name;
198
	int loaded;
199
 
200
	m = emalloc(sizeof(Message));
201
	m->name = estrstrdup(d->name, "/");
202
	m->next = nil;
203
	m->prev = mbox->tail;
204
	m->level= mbox->level+1;
205
	m->recursed = 0;
206
	name = estrstrdup(dir, m->name);
207
	loaded = loadinfo(m, name);
208
	free(name);
209
	/* if two upas/fs are running, we can get misled, so check digest before accepting message */
210
	if(loaded==0 || (digest!=nil && m->digest!=nil && strcmp(digest, m->digest)!=0)){
211
		mesgfreeparts(m);
212
		free(m);
213
		return 0;
214
	}
215
	if(mbox->tail != nil)
216
		mbox->tail->next = m;
217
	mbox->tail = m;
218
	if(mbox->head == nil)
219
		mbox->head = m;
220
 
221
	if (m->level != 1){
222
		m->recursed = 1;
223
		readmbox(m, dir, m->name); 
224
	}
225
	return 1;
226
}
227
 
228
int
229
thisyear(char *year)
230
{
231
	static char now[10];
232
	char *s;
233
 
234
	if(now[0] == '\0'){
235
		s = ctime(time(nil));
236
		strcpy(now, s+24);
237
	}
238
	return strncmp(year, now, 4) == 0;
239
}
240
 
241
char*
242
stripdate(char *as)
243
{
244
	int n;
245
	char *s, *fld[10];
246
 
247
	as = estrdup(as);
248
	s = estrdup(as);
249
	n = tokenize(s, fld, 10);
250
	if(n > 5){
251
		sprint(as, "%.3s ", fld[0]);	/* day */
252
		/* some dates have 19 Apr, some Apr 19 */
253
		if(strlen(fld[1])<4 && isnumeric(fld[1]))
254
			sprint(as+strlen(as), "%.3s %.3s ", fld[1], fld[2]);	/* date, month */
255
		else
256
			sprint(as+strlen(as), "%.3s %.3s ", fld[2], fld[1]);	/* date, month */
257
		/* do we use time or year?  depends on whether year matches this one */
258
		if(thisyear(fld[5])){
259
			if(strchr(fld[3], ':') != nil)
260
				sprint(as+strlen(as), "%.5s ", fld[3]);	/* time */
261
			else if(strchr(fld[4], ':') != nil)
262
				sprint(as+strlen(as), "%.5s ", fld[4]);	/* time */
263
		}else
264
			sprint(as+strlen(as), "%.4s ", fld[5]);	/* year */
265
	}
266
	free(s);
267
	return as;
268
}
269
 
270
char*
271
readfile(char *dir, char *name, int *np)
272
{
273
	char *file, *data;
274
	int fd, len;
275
	Dir *d;
276
 
277
	if(np != nil)
278
		*np = 0;
279
	file = estrstrdup(dir, name);
280
	fd = open(file, OREAD);
281
	if(fd < 0)
282
		return nil;
283
	d = dirfstat(fd);
284
	free(file);
285
	len = 0;
286
	if(d != nil)
287
		len = d->length;
288
	free(d);
289
	data = emalloc(len+1);
290
	read(fd, data, len);
291
	close(fd);
292
	if(np != nil)
293
		*np = len;
294
	return data;
295
}
296
 
297
char*
298
info(Message *m, int ind, int ogf)
299
{
300
	char *i;
301
	int j, len, lens;
302
	char *p;
303
	char fmt[80], s[80];
304
 
305
	if (ogf)
306
		p=m->to;
307
	else
308
		p=m->fromcolon;
309
 
310
	if(ind==0 && shortmenu){
311
		len = 30;
312
		lens = 30;
313
		if(shortmenu > 1){
314
			len = 10;
315
			lens = 25;
316
		}
317
		if(ind==0 && m->subject[0]=='\0'){
318
			snprint(fmt, sizeof fmt, " %%-%d.%ds", len, len);
319
			snprint(s, sizeof s, fmt, p);
320
		}else{
321
			snprint(fmt, sizeof fmt, " %%-%d.%ds  %%-%d.%ds", len, len, lens, lens);
322
			snprint(s, sizeof s, fmt, p, m->subject);
323
		}
324
		i = estrdup(s);
325
 
326
		return i;
327
	} 
328
 
329
	i = estrdup("");
330
	i = eappend(i, "\t", p);
331
	i = egrow(i, "\t", stripdate(m->date));
332
	if(ind == 0){
333
		if(strcmp(m->type, "text")!=0 && strncmp(m->type, "text/", 5)!=0 && 
334
		   strncmp(m->type, "multipart/", 10)!=0)
335
			i = egrow(i, "\t(", estrstrdup(m->type, ")"));
336
	}else if(strncmp(m->type, "multipart/", 10) != 0)
337
		i = egrow(i, "\t(", estrstrdup(m->type, ")"));
338
	if(m->subject[0] != '\0'){
339
		i = eappend(i, "\n", nil);
340
		for(j=0; j<ind; j++)
341
			i = eappend(i, "\t", nil);
342
		i = eappend(i, "\t", m->subject);
343
	}
344
	return i;
345
}
346
 
347
void
348
mesgmenu0(Window *w, Message *mbox, char *realdir, char *dir, int ind, Biobuf *fd, int onlyone, int dotail)
349
{
350
	int i;
351
	Message *m;
352
	char *name, *tmp;
353
	int ogf=0;
354
 
355
	if(strstr(realdir, "outgoing") != nil)
356
		ogf=1;
357
 
358
	/* show mail box in reverse order, pieces in forward order */
359
	if(ind > 0)
360
		m = mbox->head;
361
	else
362
		m = mbox->tail;
363
	while(m != nil){
364
		for(i=0; i<ind; i++)
365
			Bprint(fd, "\t");
366
		if(ind != 0)
367
			Bprint(fd, "  ");
368
		name = estrstrdup(dir, m->name);
369
		tmp = info(m, ind, ogf);
370
		Bprint(fd, "%s%s\n", name, tmp);
371
		free(tmp);
372
		if(dotail && m->tail)
373
			mesgmenu0(w, m, realdir, name, ind+1, fd, 0, dotail);
374
		free(name);
375
		if(ind)
376
			m = m->next;
377
		else
378
			m = m->prev;
379
		if(onlyone)
380
			m = nil;
381
	}
382
}
383
 
384
void
385
mesgmenu(Window *w, Message *mbox)
386
{
387
	winopenbody(w, OWRITE);
388
	mesgmenu0(w, mbox, mbox->name, "", 0, w->body, 0, !shortmenu);
389
	winclosebody(w);
390
}
391
 
392
/* one new message has arrived, as mbox->tail */
393
void
394
mesgmenunew(Window *w, Message *mbox)
395
{
396
	Biobuf *b;
397
 
398
	winselect(w, "0", 0);
399
	w->data = winopenfile(w, "data");
400
	b = emalloc(sizeof(Biobuf));
401
	Binit(b, w->data, OWRITE);
402
	mesgmenu0(w, mbox, mbox->name, "", 0, b, 1, !shortmenu);
403
	Bterm(b);
404
	free(b);
405
	if(!mbox->dirty)
406
		winclean(w);
407
	/* select tag line plus following indented lines, but not final newline (it's distinctive) */
408
	winselect(w, "0/.*\\n((\t.*\\n)*\t.*)?/", 1);
409
	close(w->addr);
410
	close(w->data);
411
	w->addr = -1;
412
	w->data = -1;
413
}
414
 
415
char*
416
name2regexp(char *prefix, char *s)
417
{
418
	char *buf, *p, *q;
419
 
420
	buf = emalloc(strlen(prefix)+2*strlen(s)+50);	/* leave room to append more */
421
	p = buf;
422
	*p++ = '0';
423
	*p++ = '/';
424
	*p++ = '^';
425
	strcpy(p, prefix);
426
	p += strlen(prefix);
427
	for(q=s; *q!='\0'; q++){
428
		if(strchr(regexchars, *q) != nil)
429
			*p++ = '\\';
430
		*p++ = *q;
431
	}
432
	*p++ = '/';
433
	*p = '\0';
434
	return buf;
435
}
436
 
437
void
438
mesgmenumarkdel(Window *w, Message *mbox, Message *m, int writeback)
439
{
440
	char *buf;
441
 
442
 
443
	if(m->deleted)
444
		return;
445
	m->writebackdel = writeback;
446
	if(w->data < 0)
447
		w->data = winopenfile(w, "data");
448
	buf = name2regexp("", m->name);
449
	strcat(buf, "-#0");
450
	if(winselect(w, buf, 1))
451
		write(w->data, deleted, 10);
452
	free(buf);
453
	close(w->data);
454
	close(w->addr);
455
	w->addr = w->data = -1;
456
	mbox->dirty = 1;
457
	m->deleted = 1;
458
}
459
 
460
void
461
mesgmenumarkundel(Window *w, Message*, Message *m)
462
{
463
	char *buf;
464
 
465
	if(m->deleted == 0)
466
		return;
467
	if(w->data < 0)
468
		w->data = winopenfile(w, "data");
469
	buf = name2regexp(deletedrx, m->name);
470
	if(winselect(w, buf, 1))
471
		if(winsetaddr(w, deletedaddr, 1))
472
			write(w->data, "", 0);
473
	free(buf);
474
	close(w->data);
475
	close(w->addr);
476
	w->addr = w->data = -1;
477
	m->deleted = 0;
478
}
479
 
480
void
481
mesgmenudel(Window *w, Message *mbox, Message *m)
482
{
483
	char *buf;
484
 
485
	if(w->data < 0)
486
		w->data = winopenfile(w, "data");
487
	buf = name2regexp(deletedrx, m->name);
488
	if(winsetaddr(w, buf, 1) && winsetaddr(w, ".,./.*\\n(\t.*\\n)*/", 1))
489
		write(w->data, "", 0);
490
	free(buf);
491
	close(w->data);
492
	close(w->addr);
493
	w->addr = w->data = -1;
494
	mbox->dirty = 1;
495
	m->deleted = 1;
496
}
497
 
498
void
499
mesgmenumark(Window *w, char *which, char *mark)
500
{
501
	char *buf;
502
 
503
	if(w->data < 0)
504
		w->data = winopenfile(w, "data");
505
	buf = name2regexp(deletedrx01, which);
506
	if(winsetaddr(w, buf, 1) && winsetaddr(w, "+0-#1", 1))	/* go to end of line */
507
		write(w->data, mark, strlen(mark));
508
	free(buf);
509
	close(w->data);
510
	close(w->addr);
511
	w->addr = w->data = -1;
512
	if(!mbox.dirty)
513
		winclean(w);
514
}
515
 
516
void
517
mesgfreeparts(Message *m)
518
{
519
	free(m->name);
520
	free(m->replyname);
521
	free(m->fromcolon);
522
	free(m->from);
523
	free(m->to);
524
	free(m->cc);
525
	free(m->replyto);
526
	free(m->date);
527
	free(m->subject);
528
	free(m->type);
529
	free(m->disposition);
530
	free(m->filename);
531
	free(m->digest);
532
}
533
 
534
void
535
mesgdel(Message *mbox, Message *m)
536
{
537
	Message *n, *next;
538
 
539
	if(m->opened)
540
		error("internal error: deleted message still open in mesgdel");
541
	/* delete subparts */
542
	for(n=m->head; n!=nil; n=next){
543
		next = n->next;
544
		mesgdel(m, n);
545
	}
546
	/* remove this message from list */
547
	if(m->next)
548
		m->next->prev = m->prev;
549
	else
550
		mbox->tail = m->prev;
551
	if(m->prev)
552
		m->prev->next = m->next;
553
	else
554
		mbox->head = m->next;
555
 
556
	mesgfreeparts(m);
557
}
558
 
559
int
560
mesgsave(Message *m, char *s)
561
{
562
	int ofd, n, k, ret;
563
	char *t, *raw, *unixheader, *all;
564
 
565
	t = estrstrdup(mbox.name, m->name);
566
	raw = readfile(t, "raw", &n);
567
	unixheader = readfile(t, "unixheader", &k);
568
	if(raw==nil || unixheader==nil){
569
		fprint(2, "Mail: can't read %s: %r\n", t);
570
		free(t);
571
		return 0;
572
	}
573
	free(t);
574
 
575
	all = emalloc(k+n+1);
576
	memmove(all, unixheader, k);
577
	memmove(all+k, raw, n);
578
	memmove(all+k+n, "\n", 1);
579
	n += k+1;
580
	free(unixheader);
581
	free(raw);
582
	ret = 1;
583
	s = estrdup(s);
584
	if(s[0] != '/')
585
		s = egrow(estrdup(mailboxdir), "/", s);
586
	ofd = open(s, OWRITE);
587
	if(ofd < 0){
588
		fprint(2, "Mail: can't open %s: %r\n", s);
589
		ret = 0;
590
	}else if(seek(ofd, 0LL, 2)<0 || write(ofd, all, n)!=n){
591
		fprint(2, "Mail: save failed: can't write %s: %r\n", s);
592
		ret = 0;
593
	}
594
	free(all);
595
	close(ofd);
596
	free(s);
597
	return ret;
598
}
599
 
600
int
601
mesgcommand(Message *m, char *cmd)
602
{
603
	char *s;
604
	char *args[10];
605
	int ok, ret, nargs;
606
 
607
	s = cmd;
608
	ret = 1;
609
	nargs = tokenize(s, args, nelem(args));
610
	if(nargs == 0)
611
		return 0;
612
	if(strcmp(args[0], "Post") == 0){
613
		mesgsend(m);
614
		goto Return;
615
	}
616
	if(strncmp(args[0], "Save", 4) == 0){
617
		if(m->isreply)
618
			goto Return;
619
		s = estrdup("\t[saved");
620
		if(nargs==1 || strcmp(args[1], "")==0){
621
			ok = mesgsave(m, "stored");
622
		}else{
623
			ok = mesgsave(m, args[1]);
624
			s = eappend(s, " ", args[1]);
625
		}
626
		if(ok){
627
			s = egrow(s, "]", nil);
628
			mesgmenumark(mbox.w, m->name, s);
629
		}
630
		free(s);
631
		goto Return;
632
	}
633
	if(strcmp(args[0], "Reply")==0){
634
		if(nargs>=2 && strcmp(args[1], "all")==0)
635
			mkreply(m, "Replyall", nil, nil, nil);
636
		else
637
			mkreply(m, "Reply", nil, nil, nil);
638
		goto Return;
639
	}
640
	if(strcmp(args[0], "Q") == 0){
641
		s = winselection(m->w);	/* will be freed by mkreply */
642
		if(nargs>=3 && strcmp(args[1], "Reply")==0 && strcmp(args[2], "all")==0)
643
			mkreply(m, "QReplyall", nil, nil, s);
644
		else
645
			mkreply(m, "QReply", nil, nil, s);
646
		goto Return;
647
	}
648
	if(strcmp(args[0], "Del") == 0){
649
		if(windel(m->w, 0)){
650
			chanfree(m->w->cevent);
651
			free(m->w);
652
			m->w = nil;
653
			if(m->isreply)
654
				delreply(m);
655
			else{
656
				m->opened = 0;
657
				m->tagposted = 0;
658
			}
659
			free(cmd);
660
			threadexits(nil);
661
		}
662
		goto Return;
663
	}
664
	if(strcmp(args[0], "Delmesg") == 0){
665
		if(!m->isreply){
666
			mesgmenumarkdel(wbox, &mbox, m, 1);
667
			free(cmd);	/* mesgcommand might not return */
668
			mesgcommand(m, estrdup("Del"));
669
			return 1;
670
		}
671
		goto Return;
672
	}
673
	if(strcmp(args[0], "UnDelmesg") == 0){
674
		if(!m->isreply && m->deleted)
675
			mesgmenumarkundel(wbox, &mbox, m);
676
		goto Return;
677
	}
678
//	if(strcmp(args[0], "Headers") == 0){
679
//		m->showheaders();
680
//		return True;
681
//	}
682
 
683
	ret = 0;
684
 
685
    Return:
686
	free(cmd);
687
	return ret;
688
}
689
 
690
void
691
mesgtagpost(Message *m)
692
{
693
	if(m->tagposted)
694
		return;
695
	wintagwrite(m->w, " Post", 5);
696
	m->tagposted = 1;
697
}
698
 
699
/* need to expand selection more than default word */
700
#pragma varargck argpos eval 2
701
 
702
long
703
eval(Window *w, char *s, ...)
704
{
705
	char buf[64];
706
	va_list arg;
707
 
708
	va_start(arg, s);
709
	vsnprint(buf, sizeof buf, s, arg);
710
	va_end(arg);
711
 
712
	if(winsetaddr(w, buf, 1)==0)
713
		return -1;
714
 
715
	if(pread(w->addr, buf, 24, 0) != 24)
716
		return -1;
717
	return strtol(buf, 0, 10);
718
}
719
 
720
int
721
isemail(char *s)
722
{
723
	int nat;
724
 
725
	nat = 0;
726
	for(; *s; s++)
727
		if(*s == '@')
728
			nat++;
729
		else if(!isalpha(*s) && !isdigit(*s) && !strchr("_.-+/", *s))
730
			return 0;
731
	return nat==1;
732
}
733
 
734
char addrdelim[] =  "/[ \t\\n<>()\\[\\]]/";
735
char*
736
expandaddr(Window *w, Event *e)
737
{
738
	char *s;
739
	long q0, q1;
740
 
741
	if(e->q0 != e->q1)	/* cannot happen */
742
		return nil;
743
 
744
	q0 = eval(w, "#%d-%s", e->q0, addrdelim);
745
	if(q0 == -1)	/* bad char not found */
746
		q0 = 0;
747
	else			/* increment past bad char */
748
		q0++;
749
 
750
	q1 = eval(w, "#%d+%s", e->q0, addrdelim);
751
	if(q1 < 0){
752
		q1 = eval(w, "$");
753
		if(q1 < 0)
754
			return nil;
755
	}
756
	if(q0 >= q1)
757
		return nil;
758
	s = emalloc((q1-q0)*UTFmax+1);
759
	winread(w, q0, q1, s);
760
	return s;
761
}
762
 
763
int
764
replytoaddr(Window *w, Message *m, Event *e, char *s)
765
{
766
	int did;
767
	char *buf;
768
	Plumbmsg *pm;
769
 
770
	buf = nil;
771
	did = 0;
772
	if(e->flag & 2){
773
		/* autoexpanded; use our own bigger expansion */
774
		buf = expandaddr(w, e);
775
		if(buf == nil)
776
			return 0;
777
		s = buf;
778
	}
779
	if(isemail(s)){
780
		did = 1;
781
		pm = emalloc(sizeof(Plumbmsg));
782
		pm->src = estrdup("Mail");
783
		pm->dst = estrdup("sendmail");
784
		pm->data = estrdup(s);
785
		pm->ndata = -1;
786
		if(m->subject && m->subject[0]){
787
			pm->attr = emalloc(sizeof(Plumbattr));
788
			pm->attr->name = estrdup("Subject");
789
			if(tolower(m->subject[0]) != 'r' || tolower(m->subject[1]) != 'e' || m->subject[2] != ':')
790
				pm->attr->value = estrstrdup("Re: ", m->subject);
791
			else
792
				pm->attr->value = estrdup(m->subject);
793
			pm->attr->next = nil;
794
		}
795
		if(plumbsend(plumbsendfd, pm) < 0)
796
			fprint(2, "error writing plumb message: %r\n");
797
		plumbfree(pm);
798
	}
799
	free(buf);
800
	return did;
801
}
802
 
803
 
804
void
805
mesgctl(void *v)
806
{
807
	Message *m;
808
	Window *w;
809
	Event *e, *eq, *e2, *ea;
810
	int na, nopen, i, j;
811
	char *os, *s, *t, *buf;
812
 
813
	m = v;
814
	w = m->w;
815
	threadsetname("mesgctl");
816
	proccreate(wineventproc, w, STACK);
817
	for(;;){
818
		e = recvp(w->cevent);
819
		switch(e->c1){
820
		default:
821
		Unk:
822
			print("unknown message %c%c\n", e->c1, e->c2);
823
			break;
824
 
825
		case 'E':	/* write to body; can't affect us */
826
			break;
827
 
828
		case 'F':	/* generated by our actions; ignore */
829
			break;
830
 
831
		case 'K':	/* type away; we don't care */
832
		case 'M':
833
			switch(e->c2){
834
			case 'x':	/* mouse only */
835
			case 'X':
836
				ea = nil;
837
				eq = e;
838
				if(e->flag & 2){
839
					e2 = recvp(w->cevent);
840
					eq = e2;
841
				}
842
				if(e->flag & 8){
843
					ea = recvp(w->cevent);
844
					recvp(w->cevent);
845
					na = ea->nb;
846
				}else
847
					na = 0;
848
				if(eq->q1>eq->q0 && eq->nb==0){
849
					s = emalloc((eq->q1-eq->q0)*UTFmax+1);
850
					winread(w, eq->q0, eq->q1, s);
851
				}else
852
					s = estrdup(eq->b);
853
				if(na){
854
					t = emalloc(strlen(s)+1+na+1);
855
					sprint(t, "%s %s", s, ea->b);
856
					free(s);
857
					s = t;
858
				}
859
				if(!mesgcommand(m, s))	/* send it back */
860
					winwriteevent(w, e);
861
				break;
862
 
863
			case 'l':	/* mouse only */
864
			case 'L':
865
				buf = nil;
866
				eq = e;
867
				if(e->flag & 2){
868
					e2 = recvp(w->cevent);
869
					eq = e2;
870
				}
871
				s = eq->b;
872
				if(eq->q1>eq->q0 && eq->nb==0){
873
					buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
874
					winread(w, eq->q0, eq->q1, buf);
875
					s = buf;
876
				}
877
				os = s;
878
				nopen = 0;
879
				do{
880
					/* skip mail box name if present */
881
					if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
882
						s += strlen(mbox.name);
883
					if(strstr(s, "body") != nil){
884
						/* strip any known extensions */
885
						for(i=0; exts[i].ext!=nil; i++){
886
							j = strlen(exts[i].ext);
887
							if(strlen(s)>j && strcmp(s+strlen(s)-j, exts[i].ext)==0){
888
								s[strlen(s)-j] = '\0';
889
								break;
890
							}
891
						}
892
						if(strlen(s)>5 && strcmp(s+strlen(s)-5, "/body")==0)
893
							s[strlen(s)-4] = '\0';	/* leave / in place */
894
					}
895
					nopen += mesgopen(&mbox, mbox.name, s, m, 0, nil);
896
					while(*s!=0 && *s++!='\n')
897
						;
898
				}while(*s);
899
				if(nopen == 0 && e->c1 == 'L')
900
					nopen += replytoaddr(w, m, e, os);
901
				if(nopen == 0)
902
					winwriteevent(w, e);
903
				free(buf);
904
				break;
905
 
906
			case 'I':	/* modify away; we don't care */
907
			case 'D':
908
				mesgtagpost(m);
909
				/* fall through */
910
			case 'd':
911
			case 'i':
912
				break;
913
 
914
			default:
915
				goto Unk;
916
			}
917
		}
918
	}
919
}
920
 
921
void
922
mesgline(Message *m, char *header, char *value)
923
{
924
	if(strlen(value) > 0)
925
		Bprint(m->w->body, "%s: %s\n", header, value);
926
}
927
 
928
int
929
isprintable(char *type)
930
{
931
	int i;
932
 
933
	for(i=0; goodtypes[i]!=nil; i++)
934
		if(strcmp(type, goodtypes[i])==0)
935
			return 1;
936
	return 0;
937
}
938
 
939
char*
940
ext(char *type)
941
{
942
	int i;
943
 
944
	for(i=0; exts[i].type!=nil; i++)
945
		if(strcmp(type, exts[i].type)==0)
946
			return exts[i].ext;
947
	return "";
948
}
949
 
950
void
951
mimedisplay(Message *m, char *name, char *rootdir, Window *w, int fileonly)
952
{
953
	char *dest, *maildest;
954
 
955
	if(strcmp(m->disposition, "file")==0 || strlen(m->filename)!=0){
956
		if(strlen(m->filename) == 0){
957
			dest = estrdup(m->name);
958
			dest[strlen(dest)-1] = '\0';
959
		}else
960
			dest = estrdup(m->filename);
961
		if(maildest = getenv("maildest")){
962
			maildest = eappend(maildest, "/", dest);
963
			Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), maildest);
964
			free(maildest);
965
		}
966
		if(m->filename[0] != '/'){
967
			dest = egrow(estrdup(home), "/", dest);
968
		}
969
		Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), dest);
970
		free(dest);
971
	}else if(!fileonly)
972
		Bprint(w->body, "\tfile is %s%sbody%s\n", rootdir, name, ext(m->type));
973
}
974
 
975
void
976
printheader(char *dir, Biobuf *b, char **okheaders)
977
{
978
	char *s;
979
	char *lines[100];
980
	int i, j, n;
981
 
982
	s = readfile(dir, "header", nil);
983
	if(s == nil)
984
		return;
985
	n = getfields(s, lines, nelem(lines), 0, "\n");
986
	for(i=0; i<n; i++)
987
		for(j=0; okheaders[j]; j++)
988
			if(cistrncmp(lines[i], okheaders[j], strlen(okheaders[j])) == 0)
989
				Bprint(b, "%s\n", lines[i]);
990
	free(s);
991
}
992
 
993
/*
994
 * find the best alternative part.
995
 *
996
 * turkeys have started emitting empty text/plain parts,
997
 * with the actual content in a text/html part, which complicates the choice.
998
 *
999
 * bigger turkeys emit a tiny base64-encoded text/plain part,
1000
 * a small base64-encoded text/html part, and the real content is in
1001
 * a text/calendar part.
1002
 *
1003
 * the magic lengths are empirically derived.
1004
 * as turkeys devolve and mutate, this will only get worse.
1005
 */
1006
static Message*
1007
bestalt(Message *m, char *dir)
1008
{
1009
	int len;
1010
	char *subdir;
1011
	Message *nm;
1012
	Message *realplain, *realhtml, *realcal;
1013
 
1014
	realplain = realhtml = realcal = nil;
1015
	for(nm = m->head; nm != nil; nm = nm->next){
1016
		subdir = estrstrdup(dir, nm->name);
1017
		len = 0;
1018
		free(readbody(nm->type, subdir, &len));
1019
		free(subdir);
1020
		if(strcmp(nm->type, "text/plain") == 0 && len >= 8)
1021
			realplain = nm;
1022
		else if(strcmp(nm->type, "text/html") == 0 && len >= 600)
1023
			realhtml = nm;
1024
		else if(strcmp(nm->type, "text/calendar") == 0)
1025
			realcal = nm;
1026
	}
1027
	if(realplain == nil && realhtml == nil && realcal)
1028
		return realcal;			/* super-turkey */
1029
	else if(realplain == nil && realhtml)
1030
		return realhtml;		/* regular turkey */
1031
	else
1032
		return realplain;
1033
}
1034
 
1035
void
1036
mesgload(Message *m, char *rootdir, char *file, Window *w)
1037
{
1038
	char *s, *subdir, *name, *dir;
1039
	Message *mp, *thisone;
1040
	int n;
1041
 
1042
	dir = estrstrdup(rootdir, file);
1043
 
1044
	if(strcmp(m->type, "message/rfc822") != 0){	/* suppress headers of envelopes */
1045
		if(strlen(m->from) > 0){
1046
			Bprint(w->body, "From: %s\n", m->from);
1047
			mesgline(m, "Date", m->date);
1048
			mesgline(m, "To", m->to);
1049
			mesgline(m, "CC", m->cc);
1050
			mesgline(m, "Subject", m->subject);
1051
			printheader(dir, w->body, extraheaders);
1052
		}else{
1053
			printheader(dir, w->body, okheaders);
1054
			printheader(dir, w->body, extraheaders);
1055
		}
1056
		Bprint(w->body, "\n");
1057
	}
1058
 
1059
	if(m->level == 1 && m->recursed == 0){
1060
		m->recursed = 1;
1061
		readmbox(m, rootdir, m->name);
1062
	}
1063
	if(m->head == nil){	/* single part message */
1064
		if(strcmp(m->type, "text")==0 || strncmp(m->type, "text/", 5)==0){
1065
			mimedisplay(m, m->name, rootdir, w, 1);
1066
			s = readbody(m->type, dir, &n);
1067
			winwritebody(w, s, n);
1068
			free(s);
1069
		}else
1070
			mimedisplay(m, m->name, rootdir, w, 0);
1071
	}else{
1072
		/* multi-part message, either multipart/* or message/rfc822 */
1073
		thisone = nil;
1074
		if(strcmp(m->type, "multipart/alternative") == 0){
1075
			thisone = bestalt(m, dir);
1076
			if(thisone == nil){
1077
				thisone = m->head; /* in case we can't find a good one */
1078
				for(mp=m->head; mp!=nil; mp=mp->next)
1079
					if(isprintable(mp->type)){
1080
						thisone = mp;
1081
						break;
1082
					}
1083
			}
1084
		}
1085
		for(mp=m->head; mp!=nil; mp=mp->next){
1086
			if(thisone!=nil && mp!=thisone)
1087
				continue;
1088
			subdir = estrstrdup(dir, mp->name);
1089
			name = estrstrdup(file, mp->name);
1090
			/* skip first element in name because it's already in window name */
1091
			if(mp != m->head)
1092
				Bprint(w->body, "\n===> %s (%s) [%s]\n",
1093
					strchr(name, '/')+1, mp->type,
1094
					mp->disposition);
1095
			if(strcmp(mp->type, "text")==0 ||
1096
			    strncmp(mp->type, "text/", 5)==0){
1097
				mimedisplay(mp, name, rootdir, w, 1);
1098
				printheader(subdir, w->body, okheaders);
1099
				printheader(subdir, w->body, extraheaders);
1100
				winwritebody(w, "\n", 1);
1101
				s = readbody(mp->type, subdir, &n);
1102
				winwritebody(w, s, n);
1103
				free(s);
1104
			}else{
1105
				if(strncmp(mp->type, "multipart/", 10)==0 ||
1106
				    strcmp(mp->type, "message/rfc822")==0){
1107
					mp->w = w;
1108
					mesgload(mp, rootdir, name, w);
1109
					mp->w = nil;
1110
				}else
1111
					mimedisplay(mp, name, rootdir, w, 0);
1112
			}
1113
			free(name);
1114
			free(subdir);
1115
		}
1116
	}
1117
	free(dir);
1118
}
1119
 
1120
int
1121
tokenizec(char *str, char **args, int max, char *splitc)
1122
{
1123
	int na;
1124
	int intok = 0;
1125
 
1126
	if(max <= 0)
1127
		return 0;	
1128
	for(na=0; *str != '\0';str++){
1129
		if(strchr(splitc, *str) == nil){
1130
			if(intok)
1131
				continue;
1132
			args[na++] = str;
1133
			intok = 1;
1134
		}else{
1135
			/* it's a separator/skip character */
1136
			*str = '\0';
1137
			if(intok){
1138
				intok = 0;
1139
				if(na >= max)
1140
					break;
1141
			}
1142
		}
1143
	}
1144
	return na;
1145
}
1146
 
1147
Message*
1148
mesglookup(Message *mbox, char *name, char *digest)
1149
{
1150
	int n;
1151
	Message *m;
1152
	char *t;
1153
 
1154
	if(digest){
1155
		/* can find exactly */
1156
		for(m=mbox->head; m!=nil; m=m->next)
1157
			if(strcmp(digest, m->digest) == 0)
1158
				break;
1159
		return m;
1160
	}
1161
 
1162
	n = strlen(name);
1163
	if(n == 0)
1164
		return nil;
1165
	if(name[n-1] == '/')
1166
		t = estrdup(name);
1167
	else
1168
		t = estrstrdup(name, "/");
1169
	for(m=mbox->head; m!=nil; m=m->next)
1170
		if(strcmp(t, m->name) == 0)
1171
			break;
1172
	free(t);
1173
	return m;
1174
}
1175
 
1176
/*
1177
 * Find plumb port, knowing type is text, given file name (by extension)
1178
 */
1179
int
1180
plumbportbysuffix(char *file)
1181
{
1182
	char *suf;
1183
	int i, nsuf, nfile;
1184
 
1185
	nfile = strlen(file);
1186
	for(i=0; ports[i].type!=nil; i++){
1187
		suf = ports[i].suffix;
1188
		nsuf = strlen(suf);
1189
		if(nfile > nsuf)
1190
			if(cistrncmp(file+nfile-nsuf, suf, nsuf) == 0)
1191
				return i;
1192
	}
1193
	return 0;
1194
}
1195
 
1196
/*
1197
 * Find plumb port using type and file name (by extension)
1198
 */
1199
int
1200
plumbport(char *type, char *file)
1201
{
1202
	int i;
1203
 
1204
	for(i=0; ports[i].type!=nil; i++)
1205
		if(strncmp(type, ports[i].type, strlen(ports[i].type)) == 0)
1206
			return i;
1207
	/* see if it's a text type */
1208
	for(i=0; goodtypes[i]!=nil; i++)
1209
		if(strncmp(type, goodtypes[i], strlen(goodtypes[i])) == 0)
1210
			return plumbportbysuffix(file);
1211
	return -1;
1212
}
1213
 
1214
void
1215
plumb(Message *m, char *dir)
1216
{
1217
	int i;
1218
	char *port;
1219
	Plumbmsg *pm;
1220
 
1221
	if(strlen(m->type) == 0)
1222
		return;
1223
	i = plumbport(m->type, m->filename);
1224
	if(i < 0)
1225
		fprint(2, "can't find destination for message subpart\n");
1226
	else{
1227
		port = ports[i].port;
1228
		pm = emalloc(sizeof(Plumbmsg));
1229
		pm->src = estrdup("Mail");
1230
		if(port)
1231
			pm->dst = estrdup(port);
1232
		else
1233
			pm->dst = nil;
1234
		pm->wdir = nil;
1235
		pm->type = estrdup("text");
1236
		pm->ndata = -1;
1237
		pm->data = estrstrdup(dir, "body");
1238
		pm->data = eappend(pm->data, "", ports[i].suffix);
1239
		if(plumbsend(plumbsendfd, pm) < 0)
1240
			fprint(2, "error writing plumb message: %r\n");
1241
		plumbfree(pm);
1242
	}
1243
}
1244
 
1245
int
1246
mesgopen(Message *mbox, char *dir, char *s, Message *mesg, int plumbed, char *digest)
1247
{
1248
	char *t, *u, *v;
1249
	Message *m;
1250
	char *direlem[10];
1251
	int i, ndirelem, reuse;
1252
 
1253
	/* find white-space-delimited first word */
1254
	for(t=s; *t!='\0' && !isspace(*t); t++)
1255
		;
1256
	u = emalloc(t-s+1);
1257
	memmove(u, s, t-s);
1258
	/* separate it on slashes */
1259
	ndirelem = tokenizec(u, direlem, nelem(direlem), "/");
1260
	if(ndirelem <= 0){
1261
    Error:
1262
		free(u);
1263
		return 0;
1264
	}
1265
	if(plumbed){
1266
		write(wctlfd, "top", 3);
1267
		write(wctlfd, "current", 7);
1268
	}
1269
	/* open window for message */
1270
	m = mesglookup(mbox, direlem[0], digest);
1271
	if(m == nil)
1272
		goto Error;
1273
	if(mesg!=nil && m!=mesg)	/* string looked like subpart but isn't part of this message */
1274
		goto Error;
1275
	if(m->opened == 0){
1276
		if(m->w == nil){
1277
			reuse = 0;
1278
			m->w = newwindow();
1279
		}else{
1280
			reuse = 1;
1281
			/* re-use existing window */
1282
			if(winsetaddr(m->w, "0,$", 1)){
1283
				if(m->w->data < 0)
1284
					m->w->data = winopenfile(m->w, "data");
1285
				write(m->w->data, "", 0);
1286
			}
1287
		}
1288
		v = estrstrdup(mbox->name, m->name);
1289
		winname(m->w, v);
1290
		free(v);
1291
		if(!reuse){
1292
			if(m->deleted)
1293
				wintagwrite(m->w, "Q Reply all UnDelmesg Save ", 2+6+4+10+5);
1294
			else
1295
				wintagwrite(m->w, "Q Reply all Delmesg Save ", 2+6+4+8+5);
1296
		}
1297
		threadcreate(mesgctl, m, STACK);
1298
		winopenbody(m->w, OWRITE);
1299
		mesgload(m, dir, m->name, m->w);
1300
		winclosebody(m->w);
1301
		winclean(m->w);
1302
		m->opened = 1;
1303
		if(ndirelem == 1){
1304
			free(u);
1305
			return 1;
1306
		}
1307
	}
1308
	if(ndirelem == 1 && plumbport(m->type, m->filename) <= 0){
1309
		/* make sure dot is visible */
1310
		ctlprint(m->w->ctl, "show\n");
1311
		return 0;
1312
	}
1313
	/* walk to subpart */
1314
	dir = estrstrdup(dir, m->name);
1315
	for(i=1; i<ndirelem; i++){
1316
		m = mesglookup(m, direlem[i], digest);
1317
		if(m == nil)
1318
			break;
1319
		dir = egrow(dir, m->name, nil);
1320
	}
1321
	if(m != nil && plumbport(m->type, m->filename) > 0)
1322
		plumb(m, dir);
1323
	free(dir);
1324
	free(u);
1325
	return 1;
1326
}
1327
 
1328
void
1329
rewritembox(Window *w, Message *mbox)
1330
{
1331
	Message *m, *next;
1332
	char *deletestr, *t;
1333
	int nopen;
1334
 
1335
	deletestr = estrstrdup("delete ", fsname);
1336
 
1337
	nopen = 0;
1338
	for(m=mbox->head; m!=nil; m=next){
1339
		next = m->next;
1340
		if(m->deleted == 0)
1341
			continue;
1342
		if(m->opened){
1343
			nopen++;
1344
			continue;
1345
		}
1346
		if(m->writebackdel){
1347
			/* messages deleted by plumb message are not removed again */
1348
			t = estrdup(m->name);
1349
			if(strlen(t) > 0)
1350
				t[strlen(t)-1] = '\0';
1351
			deletestr = egrow(deletestr, " ", t);
1352
		}
1353
		mesgmenudel(w, mbox, m);
1354
		mesgdel(mbox, m);
1355
	}
1356
	if(write(mbox->ctlfd, deletestr, strlen(deletestr)) < 0)
1357
		fprint(2, "Mail: warning: error removing mail message files: %r\n");
1358
	free(deletestr);
1359
	winselect(w, "0", 0);
1360
	if(nopen == 0)
1361
		winclean(w);
1362
	mbox->dirty = 0;
1363
}
1364
 
1365
/* name is a full file name, but it might not belong to us */
1366
Message*
1367
mesglookupfile(Message *mbox, char *name, char *digest)
1368
{
1369
	int k, n;
1370
 
1371
	k = strlen(name);
1372
	n = strlen(mbox->name);
1373
	if(k==0 || strncmp(name, mbox->name, n) != 0){
1374
//		fprint(2, "Mail: message %s not in this mailbox\n", name);
1375
		return nil;
1376
	}
1377
	return mesglookup(mbox, name+n, digest);
1378
}