xref: /plan9/acme/mail/src/reply.c (revision 80ee5cbfe36716af62da8896207e9763b8e3d760)
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 static int	replyid;
10 
11 int
12 EQUAL(char *s, char *t)
13 {
14 	while(tolower(*s) == tolower(*t++))
15 		if(*s++ == '\0')
16 			return 1;
17 	return 0;
18 }
19 
20 void
21 mkreply(Message *m, char *label, char *to)
22 {
23 	Message *r;
24 	char *dir, *t;
25 
26 	r = emalloc(sizeof(Message));
27 	r->isreply = 1;
28 	if(m != nil)
29 		r->replyname = estrdup(m->name);
30 	r->next = replies.head;
31 	r->prev = nil;
32 	if(replies.head != nil)
33 		replies.head->prev = r;
34 	replies.head = r;
35 	if(replies.tail == nil)
36 		replies.tail = r;
37 	r->name = emalloc(strlen(mbox.name)+strlen(label)+10);
38 	sprint(r->name, "%s%s%d", mbox.name, label, ++replyid);
39 	r->w = newwindow();
40 	winname(r->w, r->name);
41 	wintagwrite(r->w, "|fmt Post", 5+4);
42 	r->tagposted = 1;
43 	threadcreate(mesgctl, r, STACK);
44 	winopenbody(r->w, OWRITE);
45 	if(to!=nil && to[0]!='\0')
46 		Bprint(r->w->body, "%s\n", to);
47 	if(m != nil){
48 		dir = estrstrdup(mbox.name, m->name);
49 		if(to == nil){
50 			/* Reply goes to replyto; Reply all goes to From and To and CC */
51 			if(strcmp(label, "Reply") == 0)
52 				Bprint(r->w->body, "To: %s\n", m->replyto);
53 			else{	/* Replyall */
54 				if(strlen(m->from) > 0)
55 					Bprint(r->w->body, "To: %s\n", m->from);
56 				if(strlen(m->to) > 0)
57 					Bprint(r->w->body, "To: %s\n", m->to);
58 				if(strlen(m->cc) > 0)
59 					Bprint(r->w->body, "CC: %s\n", m->cc);
60 			}
61 		}
62 		if(strlen(m->subject) > 0){
63 			t = "Subject: Re: ";
64 			if(strlen(m->subject) >= 3)
65 				if(tolower(m->subject[0])=='r' && tolower(m->subject[1])=='e' && m->subject[2]==':')
66 					t = "Subject: ";
67 			Bprint(r->w->body, "%s%s\n", t, m->subject);
68 		}
69 		Bprint(r->w->body, "Include: %sraw\n", dir);
70 		free(dir);
71 	}
72 	Bprint(r->w->body, "\n");
73 	if(m == nil)
74 		Bprint(r->w->body, "\n");
75 	winclosebody(r->w);
76 	if(m == nil)
77 		winselect(r->w, "0", 0);
78 	else
79 		winselect(r->w, "$", 0);
80 	winclean(r->w);
81 	windormant(r->w);
82 }
83 
84 void
85 delreply(Message *m)
86 {
87 	if(m->next == nil)
88 		replies.tail = m->prev;
89 	else
90 		m->next->prev = m->prev;
91 	if(m->prev == nil)
92 		replies.head = m->next;
93 	else
94 		m->prev->next = m->next;
95 	mesgfreeparts(m);
96 	free(m);
97 }
98 
99 enum
100 {
101 	NARGS		= 100,
102 	NARGCHAR	= 8*1024,
103 	EXECSTACK 	= STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
104 };
105 
106 struct Exec
107 {
108 	char		**argv;
109 	int		p[2];
110 	Channel	*sync;
111 };
112 
113 /* copy argv to stack and free the incoming strings, so we don't leak argument vectors */
114 void
115 buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR])
116 {
117 	int i, n;
118 	char *s, *a;
119 
120 	s = args;
121 	for(i=0; i<NARGS; i++){
122 		a = inargv[i];
123 		if(a == nil)
124 			break;
125 		n = strlen(a)+1;
126 		if((s-args)+n >= NARGCHAR)	/* too many characters */
127 			break;
128 		argv[i] = s;
129 		memmove(s, a, n);
130 		s += n;
131 		free(a);
132 	}
133 	argv[i] = nil;
134 }
135 
136 void
137 execproc(void *v)
138 {
139 	struct Exec *e;
140 	int p[2];
141 	char *argv[NARGS+1], args[NARGCHAR];
142 
143 	e = v;
144 	p[0] = e->p[0];
145 	p[1] = e->p[1];
146 	rfork(RFFDG);
147 	sendul(e->sync, 1);
148 	buildargv(e->argv, argv, args);
149 	free(e->argv);
150 	chanfree(e->sync);
151 	free(e);
152 	dup(p[0], 0);
153 	close(p[1]);
154 	procexec(nil, "/bin/upas/marshal", argv);
155 //threadprint(2, "exec: /bin/upas/marshal");
156 //{int i;
157 //for(i=0; argv[i]; i++) print(" '%s'", argv[i]);
158 //print("\n");
159 //}
160 //argv[0] = "cat";
161 //argv[1] = nil;
162 //procexec(nil, "/bin/cat", argv);
163 	threadprint(2, "Mail: can't exec %s: %r\n", argv[0]);
164 	threadexits("can't exec");
165 }
166 
167 enum{
168 	ATTACH,
169 	BCC,
170 	CC,
171 	FROM,
172 	INCLUDE,
173 	TO,
174 };
175 
176 char *headers[] = {
177 	"attach:",
178 	"bcc:",
179 	"cc:",
180 	"from:",
181 	"include:",
182 	"to:",
183 	nil,
184 };
185 
186 int
187 whichheader(char *h)
188 {
189 	int i;
190 
191 	for(i=0; headers[i]!=nil; i++)
192 		if(EQUAL(h, headers[i]))
193 			return i;
194 	return -1;
195 }
196 
197 char *tolist[200];
198 char	*cclist[200];
199 char	*bcclist[200];
200 int ncc, nbcc, nto;
201 char	*attlist[200];
202 int	rfc822[200];
203 
204 int
205 addressed(char *name)
206 {
207 	int i;
208 
209 	for(i=0; i<nto; i++)
210 		if(strcmp(name, tolist[i]) == 0)
211 			return 1;
212 	for(i=0; i<ncc; i++)
213 		if(strcmp(name, cclist[i]) == 0)
214 			return 1;
215 	for(i=0; i<nbcc; i++)
216 		if(strcmp(name, bcclist[i]) == 0)
217 			return 1;
218 	return 0;
219 }
220 
221 char*
222 skipbl(char *s, char *e)
223 {
224 	while(s < e){
225 		if(*s!=' ' && *s!='\t' && *s!=',')
226 			break;
227 		s++;
228 	}
229 	return s;
230 }
231 
232 char*
233 findbl(char *s, char *e)
234 {
235 	while(s < e){
236 		if(*s==' ' || *s=='\t' || *s==',')
237 			break;
238 		s++;
239 	}
240 	return s;
241 }
242 
243 /*
244  * comma-separate possibly blank-separated strings in line; e points before newline
245  */
246 void
247 commas(char *s, char *e)
248 {
249 	char *t;
250 
251 	/* may have initial blanks */
252 	s = skipbl(s, e);
253 	while(s < e){
254 		s = findbl(s, e);
255 		if(s == e)
256 			break;
257 		t = skipbl(s, e);
258 		if(t == e)	/* no more words */
259 			break;
260 		/* patch comma */
261 		*s++ = ',';
262 		while(s < t)
263 			*s++ = ' ';
264 	}
265 }
266 
267 void
268 mesgsend(Message *m)
269 {
270 	char *s, *body, *to;
271 	int i, j, h, n, natt, p[2];
272 	struct Exec *e;
273 	Channel *sync;
274 	int first, nfld, delit;
275 	char *copy, *fld[100];
276 
277 	body = winreadbody(m->w, &n);
278 	/* assemble to: list from first line, to: line, and cc: line */
279 	nto = 0;
280 	natt = 0;
281 	ncc = 0;
282 	nbcc = 0;
283 	first = 1;
284 	to = body;
285 	for(;;){
286 		for(s=to; *s!='\n'; s++)
287 			if(*s == '\0'){
288 				free(body);
289 				return;
290 			}
291 		if(s++ == to)	/* blank line */
292 			break;
293 		/* make copy of line to tokenize */
294 		copy = emalloc(s-to);
295 		memmove(copy, to, s-to);
296 		copy[s-to-1] = '\0';
297 		nfld = tokenizec(copy, fld, nelem(fld), ", \t");
298 		if(nfld == 0){
299 			free(copy);
300 			break;
301 		}
302 		n -= s-to;
303 		switch(h = whichheader(fld[0])){
304 		case TO:
305 		case FROM:
306 			delit = 1;
307 			commas(to+strlen(fld[0]), s-1);
308 			for(i=1; i<nfld && nto<nelem(tolist); i++)
309 				if(!addressed(fld[i]))
310 					tolist[nto++] = estrdup(fld[i]);
311 			break;
312 		case BCC:
313 			delit = 1;
314 			commas(to+strlen(fld[0]), s-1);
315 			for(i=1; i<nfld && nbcc<nelem(bcclist); i++)
316 				if(!addressed(fld[i]))
317 					bcclist[nbcc++] = estrdup(fld[i]);
318 			break;
319 		case CC:
320 			delit = 1;
321 			commas(to+strlen(fld[0]), s-1);
322 			for(i=1; i<nfld && ncc<nelem(cclist); i++)
323 				if(!addressed(fld[i]))
324 					cclist[ncc++] = estrdup(fld[i]);
325 			break;
326 		case ATTACH:
327 		case INCLUDE:
328 			delit = 1;
329 			for(i=1; i<nfld && natt<nelem(attlist); i++){
330 				attlist[natt] = estrdup(fld[i]);
331 				rfc822[natt++] = (h == INCLUDE);
332 			}
333 			break;
334 		default:
335 			if(first){
336 				delit = 1;
337 				for(i=0; i<nfld && nto<nelem(tolist); i++)
338 					tolist[nto++] = estrdup(fld[i]);
339 			}else	/* ignore it */
340 				delit = 0;
341 			break;
342 		}
343 		if(delit){
344 			/* delete line from body */
345 			memmove(to, s, n+1);
346 		}else
347 			to = s;
348 		free(copy);
349 		first = 0;
350 	}
351 
352 	e = emalloc(sizeof(struct Exec));
353 	if(pipe(p) < 0)
354 		error("Mail: can't create pipe\n");
355 	e->p[0] = p[0];
356 	e->p[1] = p[1];
357 	e->argv = emalloc((1+1+4*natt+1)*sizeof(char*));
358 	e->argv[0] = estrdup("marshal");
359 	e->argv[1] = estrdup("-8");
360 	j = 2;
361 	for(i=0; i<natt; i++){
362 		if(rfc822[i]){
363 			e->argv[j++] = estrdup("-t");
364 			e->argv[j++] = estrdup("message/rfc822");
365 			e->argv[j++] = estrdup("-A");
366 		}else
367 			e->argv[j++] = estrdup("-a");
368 		e->argv[j++] = attlist[i];
369 	}
370 	sync = chancreate(sizeof(int), 0);
371 	e->sync = sync;
372 	proccreate(execproc, e, EXECSTACK);
373 	recvul(sync);
374 	close(p[0]);
375 
376 	/* using marshal -8, so generate rfc822 headers */
377 	if(nto > 0){
378 		threadprint(p[1], "To: ");
379 		for(i=0; i<nto-1; i++)
380 			threadprint(p[1], "%s, ", tolist[i]);
381 		threadprint(p[1], "%s\n", tolist[i]);
382 	}
383 	if(ncc > 0){
384 		threadprint(p[1], "CC: ");
385 		for(i=0; i<ncc-1; i++)
386 			threadprint(p[1], "%s, ", cclist[i]);
387 		threadprint(p[1], "%s\n", cclist[i]);
388 	}
389 	if(nbcc > 0){
390 		threadprint(p[1], "BCC: ");
391 		for(i=0; i<nbcc-1; i++)
392 			threadprint(p[1], "%s, ", bcclist[i]);
393 		threadprint(p[1], "%s\n", bcclist[i]);
394 	}
395 
396 	i = strlen(body);
397 	if(i > 0)
398 		write(p[1], body, i);
399 
400 	/* guarantee a blank line, to ensure attachments are separated from body */
401 	if(i==0 || body[i-1]!='\n')
402 		write(p[1], "\n\n", 2);
403 	else if(i>1 && body[i-2]!='\n')
404 		write(p[1], "\n", 1);
405 	close(p[1]);
406 	free(body);
407 
408 	if(m->replyname != nil)
409 		mesgmenumark(mbox.w, m->replyname, "\t[replied]");
410 	if(m->name[0] == '/')
411 		s = estrdup(m->name);
412 	else
413 		s = estrstrdup(mbox.name, m->name);
414 	s = egrow(s, "-R", nil);
415 	winname(m->w, s);
416 	free(s);
417 	winclean(m->w);
418 }
419