Show the Content-Description:, as applicable
[s-mailx.git] / cmd_resend.c
blobdbe804f4ed725f243ad70931754feca937ea6d65
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ All sorts of `reply', `resend', `forward', and similar user commands.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE cmd_resend
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 /* Modify subject we reply to to begin with Re: if it does not already */
43 static char * _reedit(char *subj);
45 static void make_ref_and_cs(struct message *mp, struct header *head);
47 /* `reply' and `Lreply' workhorse */
48 static int _list_reply(int *msgvec, enum header_flags hf);
50 /* Get PTF to implementation of command c (i.e., take care for *flipr*) */
51 static int (* _reply_or_Reply(char c))(int *, bool_t);
53 /* Reply to a single message. Extract each name from the message header and
54 * send them off to mail1() */
55 static int _reply(int *msgvec, bool_t recipient_record);
57 /* Reply to a series of messages by simply mailing to the senders and not
58 * messing around with the To: and Cc: lists as in normal reply */
59 static int _Reply(int *msgvec, bool_t recipient_record);
61 /* Forward a message to a new recipient, in the sense of RFC 2822 */
62 static int _fwd(char *str, int recipient_record);
64 /* Modify the subject we are replying to to begin with Fwd: */
65 static char * __fwdedit(char *subj);
67 /* Do the real work of resending */
68 static int _resend1(void *v, bool_t add_resent);
70 static char *
71 _reedit(char *subj)
73 struct str in, out;
74 char *newsubj = NULL;
75 NYD_ENTER;
77 if (subj == NULL || *subj == '\0')
78 goto jleave;
80 in.s = subj;
81 in.l = strlen(subj);
82 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
84 if ((newsubj = subject_re_trim(out.s)) != out.s)
85 newsubj = savestr(out.s);
86 else {
87 /* RFC mandates english "Re: " */
88 newsubj = salloc(out.l + 4 +1);
89 sstpcpy(sstpcpy(newsubj, "Re: "), out.s);
92 free(out.s);
93 jleave:
94 NYD_LEAVE;
95 return newsubj;
98 static void
99 make_ref_and_cs(struct message *mp, struct header *head) /* TODO rewrite FAST */
101 char *oldref, *oldmsgid, *newref, *cp;
102 size_t oldreflen = 0, oldmsgidlen = 0, reflen;
103 unsigned i;
104 struct name *n;
105 NYD_ENTER;
107 oldref = hfield1("references", mp);
108 oldmsgid = hfield1("message-id", mp);
109 if (oldmsgid == NULL || *oldmsgid == '\0') {
110 head->h_ref = NULL;
111 goto jleave;
114 reflen = 1;
115 if (oldref) {
116 oldreflen = strlen(oldref);
117 reflen += oldreflen + 2;
119 if (oldmsgid) {
120 oldmsgidlen = strlen(oldmsgid);
121 reflen += oldmsgidlen;
124 newref = smalloc(reflen);
125 if (oldref != NULL) {
126 memcpy(newref, oldref, oldreflen +1);
127 if (oldmsgid != NULL) {
128 newref[oldreflen++] = ',';
129 newref[oldreflen++] = ' ';
130 memcpy(newref + oldreflen, oldmsgid, oldmsgidlen +1);
132 } else if (oldmsgid)
133 memcpy(newref, oldmsgid, oldmsgidlen +1);
134 n = extract(newref, GREF);
135 free(newref);
137 /* Limit number of references TODO better on parser side */
138 while (n->n_flink != NULL)
139 n = n->n_flink;
140 for (i = 1; i <= REFERENCES_MAX; ++i) {
141 if (n->n_blink != NULL)
142 n = n->n_blink;
143 else
144 break;
146 n->n_blink = NULL;
147 head->h_ref = n;
148 if (ok_blook(reply_in_same_charset) &&
149 (cp = hfield1("content-type", mp)) != NULL)
150 head->h_charset = mime_param_get("charset", cp);
151 jleave:
152 NYD_LEAVE;
155 static int
156 _list_reply(int *msgvec, enum header_flags hf)
158 struct header head;
159 struct message *mp;
160 char const *reply_to, *rcv, *cp;
161 enum gfield gf;
162 struct name *rt, *mft, *np;
163 int *save_msgvec;
164 NYD_ENTER;
166 /* TODO Since we may recur and do stuff with message lists we need to save
167 * TODO away the argument vector as long as that isn't done by machinery */
169 size_t i;
170 for (i = 0; msgvec[i] != 0; ++i)
172 ++i;
173 save_msgvec = ac_alloc(sizeof(*save_msgvec) * i);
174 while (i-- > 0)
175 save_msgvec[i] = msgvec[i];
176 msgvec = save_msgvec;
179 jnext_msg:
180 mp = message + *msgvec - 1;
181 touch(mp);
182 setdot(mp);
184 memset(&head, 0, sizeof head);
185 head.h_flags = hf;
187 head.h_subject = _reedit(hfield1("subject", mp));
188 gf = ok_blook(fullnames) ? GFULL : GSKIN;
189 rt = mft = NULL;
191 rcv = NULL;
192 if ((reply_to = hfield1("reply-to", mp)) != NULL &&
193 (cp = ok_vlook(reply_to_honour)) != NULL &&
194 (rt = checkaddrs(lextract(reply_to, GTO | gf), EACM_STRICT, NULL)
195 ) != NULL) {
196 char const *tr = _("Reply-To %s%s");
197 size_t l = strlen(tr) + strlen(rt->n_name) + 3 +1;
198 char *sp = salloc(l);
200 snprintf(sp, l, tr, rt->n_name, (rt->n_flink != NULL ? "..." : n_empty));
201 if (quadify(cp, UIZ_MAX, sp, TRU1) > FAL0)
202 rcv = reply_to;
205 if (rcv == NULL && (rcv = hfield1("from", mp)) == NULL)
206 rcv = nameof(mp, 1);
208 /* Cc: */
209 np = NULL;
210 if (ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL)
211 np = lextract(cp, GCC | gf);
212 if ((cp = hfield1("cc", mp)) != NULL)
213 np = cat(np, lextract(cp, GCC | gf));
214 if (np != NULL)
215 head.h_cc = delete_alternates(np);
217 /* To: */
218 np = NULL;
219 if (rcv != NULL)
220 np = (rcv == reply_to) ? namelist_dup(rt, GTO | gf)
221 : lextract(rcv, GTO | gf);
222 if (!ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL)
223 np = cat(np, lextract(cp, GTO | gf));
224 /* Delete my name from reply list, and with it, all my alternate names */
225 np = delete_alternates(np);
226 if (count(np) == 0)
227 np = lextract(rcv, GTO | gf);
228 head.h_to = np;
230 /* The user may have send this to himself, don't ignore that */
231 namelist_vaporise_head(&head, EACM_NORMAL, FAL0, NULL);
232 if (head.h_to == NULL)
233 head.h_to = np;
235 /* Mail-Followup-To: */
236 mft = NULL;
237 if (ok_vlook(followup_to_honour) != NULL &&
238 (cp = hfield1("mail-followup-to", mp)) != NULL &&
239 (mft = np = checkaddrs(lextract(cp, GTO | gf), EACM_STRICT, NULL)
240 ) != NULL) {
241 char const *tr = _("Followup-To %s%s");
242 size_t l = strlen(tr) + strlen(np->n_name) + 3 +1;
243 char *sp = salloc(l);
245 snprintf(sp, l, tr, np->n_name, (np->n_flink != NULL ? "..." : n_empty));
246 if (quadify(ok_vlook(followup_to_honour), UIZ_MAX, sp, TRU1) > FAL0) {
247 head.h_cc = NULL;
248 head.h_to = np;
249 head.h_mft =
250 mft = namelist_vaporise_head(&head, EACM_STRICT, FAL0, NULL);
251 } else
252 mft = NULL;
255 /* Special massage for list (follow-up) messages */
256 if (mft != NULL || (hf & HF_LIST_REPLY) || ok_blook(followup_to)) {
257 /* Learn about a possibly sending mailing list; use do for break; */
258 if ((cp = hfield1("list-post", mp)) != NULL) do {
259 struct name *x;
261 if ((x = lextract(cp, GEXTRA | GSKIN)) == NULL || x->n_flink != NULL ||
262 (cp = url_mailto_to_address(x->n_name)) == NULL ||
263 /* XXX terribly wasteful to create a new name, and can't we find
264 * XXX a way to mitigate that?? */
265 is_addr_invalid(x = nalloc(cp, GEXTRA | GSKIN), EACM_STRICT)) {
266 if (options & OPT_D_V)
267 n_err(_("Message contains invalid List-Post: header\n"));
268 cp = NULL;
269 break;
271 cp = x->n_name;
273 /* A special case has been seen on e.g. ietf-announce@ietf.org:
274 * these usually post to multiple groups, with ietf-announce@
275 * in List-Post:, but with Reply-To: set to ietf@ietf.org (since
276 * -announce@ is only used for announcements, say).
277 * So our desire is to honour this request and actively overwrite
278 * List-Post: for our purpose; but only if its a single address.
279 * However, to avoid ambiguities with users that place themselve in
280 * Reply-To: and mailing lists which don't overwrite this (or only
281 * extend this, shall such exist), only do so if reply_to exists of
282 * a single address which points to the same domain as List-Post: */
283 if (reply_to != NULL && rt->n_flink == NULL &&
284 name_is_same_domain(x, rt))
285 cp = rt->n_name; /* rt is EACM_STRICT tested */
287 /* "Automatically `mlist'" the List-Post: address temporarily */
288 if (is_mlist(cp, FAL0) == MLIST_OTHER)
289 head.h_list_post = cp;
290 else
291 cp = NULL;
292 } while (0);
294 /* In case of list replies we actively sort out any non-list recipient,
295 * but _only_ if we did not honour a MFT:, assuming that members of MFT
296 * were there for a reason; cp is still List-Post:/eqivalent */
297 if ((hf & HF_LIST_REPLY) && mft == NULL) {
298 struct name *nhp = head.h_to;
299 head.h_to = NULL;
300 j_lt_redo:
301 while (nhp != NULL) {
302 np = nhp;
303 nhp = nhp->n_flink;
305 if ((cp != NULL && !asccasecmp(cp, np->n_name)) ||
306 is_mlist(np->n_name, FAL0) != MLIST_OTHER) {
307 np->n_type = (np->n_type & ~GMASK) | GTO;
308 np->n_flink = head.h_to;
309 head.h_to = np;
312 if ((nhp = head.h_cc) != NULL) {
313 head.h_cc = NULL;
314 goto j_lt_redo;
319 make_ref_and_cs(mp, &head);
321 if (ok_blook(quote_as_attachment)) {
322 head.h_attach = csalloc(1, sizeof *head.h_attach);
323 head.h_attach->a_msgno = *msgvec;
324 head.h_attach->a_content_description = _("Original message content");
327 if (mail1(&head, 1, mp, NULL, !!(hf & HF_RECIPIENT_RECORD), 0) == OKAY &&
328 ok_blook(markanswered) && !(mp->m_flag & MANSWERED))
329 mp->m_flag |= MANSWER | MANSWERED;
331 if (*++msgvec != 0) {
332 /* TODO message (error) ring.., less sleep */
333 printf(_("Waiting a second before proceeding to the next message..\n"));
334 fflush(stdout);
335 n_msleep(1000, FAL0);
336 goto jnext_msg;
339 ac_free(save_msgvec);
340 NYD_LEAVE;
341 return 0;
344 static int
345 (*_reply_or_Reply(char c))(int *, bool_t)
347 int (*rv)(int*, bool_t);
348 NYD_ENTER;
350 rv = (ok_blook(flipr) ^ (c == 'R')) ? &_Reply : &_reply;
351 NYD_LEAVE;
352 return rv;
355 static int
356 _reply(int *msgvec, bool_t recipient_record)
358 int rv;
359 NYD_ENTER;
361 rv = _list_reply(msgvec, recipient_record ? HF_RECIPIENT_RECORD : HF_NONE);
362 NYD_LEAVE;
363 return rv;
366 static int
367 _Reply(int *msgvec, bool_t recipient_record)
369 struct header head;
370 struct message *mp;
371 int *ap;
372 char *cp;
373 enum gfield gf;
374 NYD_ENTER;
376 memset(&head, 0, sizeof head);
377 gf = ok_blook(fullnames) ? GFULL : GSKIN;
379 for (ap = msgvec; *ap != 0; ++ap) {
380 char const *rp;
381 struct name *rt;
383 mp = message + *ap - 1;
384 touch(mp);
385 setdot(mp);
387 if ((rp = hfield1("reply-to", mp)) != NULL &&
388 (cp = ok_vlook(reply_to_honour)) != NULL &&
389 (rt = checkaddrs(lextract(rp, GTO | gf), EACM_STRICT, NULL)
390 ) != NULL) {
391 char const *tr = _("Reply-To %s%s");
392 size_t l = strlen(tr) + strlen(rt->n_name) + 3 +1;
393 char *sp = salloc(l);
395 snprintf(sp, l, tr, rt->n_name, (rt->n_flink != NULL ? "..."
396 : n_empty));
397 if (quadify(cp, UIZ_MAX, sp, TRU1) > FAL0) {
398 head.h_to = cat(head.h_to, rt);
399 continue;
403 if ((cp = hfield1("from", mp)) == NULL)
404 cp = nameof(mp, 2);
405 head.h_to = cat(head.h_to, lextract(cp, GTO | gf));
407 if (head.h_to == NULL)
408 goto jleave;
410 mp = message + msgvec[0] - 1;
411 head.h_subject = hfield1("subject", mp);
412 head.h_subject = _reedit(head.h_subject);
413 make_ref_and_cs(mp, &head);
415 if (ok_blook(quote_as_attachment)) {
416 head.h_attach = csalloc(1, sizeof *head.h_attach);
417 head.h_attach->a_msgno = *msgvec;
418 head.h_attach->a_content_description = _("Original message content");
421 if (mail1(&head, 1, mp, NULL, recipient_record, 0) == OKAY &&
422 ok_blook(markanswered) && !(mp->m_flag & MANSWERED))
423 mp->m_flag |= MANSWER | MANSWERED;
424 jleave:
425 NYD_LEAVE;
426 return 0;
429 static int
430 _fwd(char *str, int recipient_record)
432 struct header head;
433 int *msgvec, rv = 1;
434 char *recipient;
435 struct message *mp;
436 bool_t f, forward_as_attachment;
437 NYD_ENTER;
439 if ((recipient = laststring(str, &f, TRU1)) == NULL) {
440 puts(_("No recipient specified."));
441 goto jleave;
444 forward_as_attachment = ok_blook(forward_as_attachment);
445 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
447 if (!f) {
448 *msgvec = first(0, MMNORM);
449 if (*msgvec == 0) {
450 if (pstate & (PS_HOOK_MASK | PS_ROBOT)) {
451 rv = 0;
452 goto jleave;
454 printf(_("No messages to forward.\n"));
455 goto jleave;
457 msgvec[1] = 0;
458 } else if (getmsglist(str, msgvec, 0) < 0)
459 goto jleave;
461 if (*msgvec == 0) {
462 if (pstate & (PS_HOOK_MASK | PS_ROBOT)) {
463 rv = 0;
464 goto jleave;
466 printf(_("No applicable messages.\n"));
467 goto jleave;
469 if (msgvec[1] != 0) {
470 printf(_("Cannot forward multiple messages at once\n"));
471 goto jleave;
474 memset(&head, 0, sizeof head);
475 if ((head.h_to = lextract(recipient,
476 (GTO | (ok_blook(fullnames) ? GFULL : GSKIN)))) == NULL)
477 goto jleave;
479 mp = message + *msgvec - 1;
481 if (forward_as_attachment) {
482 head.h_attach = csalloc(1, sizeof *head.h_attach);
483 head.h_attach->a_msgno = *msgvec;
484 head.h_attach->a_content_description = _("Forwarded message");
485 } else {
486 touch(mp);
487 setdot(mp);
489 head.h_subject = hfield1("subject", mp);
490 head.h_subject = __fwdedit(head.h_subject);
491 mail1(&head, 1, (forward_as_attachment ? NULL : mp), NULL, recipient_record,
493 rv = 0;
494 jleave:
495 NYD_LEAVE;
496 return rv;
499 static char *
500 __fwdedit(char *subj)
502 struct str in, out;
503 char *newsubj = NULL;
504 NYD_ENTER;
506 if (subj == NULL || *subj == '\0')
507 goto jleave;
509 in.s = subj;
510 in.l = strlen(subj);
511 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
513 newsubj = salloc(out.l + 6);
514 memcpy(newsubj, "Fwd: ", 5); /* XXX localizable */
515 memcpy(newsubj + 5, out.s, out.l +1);
516 free(out.s);
517 jleave:
518 NYD_LEAVE;
519 return newsubj;
522 static int
523 _resend1(void *v, bool_t add_resent)
525 char *name, *str;
526 struct name *to, *sn;
527 int *ip, *msgvec;
528 bool_t f = TRU1;
529 NYD_ENTER;
531 str = v;
532 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
533 name = laststring(str, &f, TRU1);
534 if (name == NULL) {
535 puts(_("No recipient specified."));
536 goto jleave;
539 if (!f) {
540 *msgvec = first(0, MMNORM);
541 if (*msgvec == 0) {
542 if (pstate & (PS_HOOK_MASK | PS_ROBOT)) {
543 f = FAL0;
544 goto jleave;
546 puts(_("No applicable messages."));
547 goto jleave;
549 msgvec[1] = 0;
550 } else if (getmsglist(str, msgvec, 0) < 0)
551 goto jleave;
553 if (*msgvec == 0) {
554 if (pstate & (PS_HOOK_MASK | PS_ROBOT)) {
555 f = FAL0;
556 goto jleave;
558 printf("No applicable messages.\n");
559 goto jleave;
562 sn = nalloc(name, GTO | GSKIN);
563 to = usermap(sn, FAL0);
564 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
565 ++ip)
566 if (resend_msg(message + *ip - 1, to, add_resent) != OKAY)
567 goto jleave;
568 f = FAL0;
569 jleave:
570 NYD_LEAVE;
571 return (f != FAL0);
574 FL int
575 c_reply(void *v)
577 int rv;
578 NYD_ENTER;
580 rv = (*_reply_or_Reply('r'))(v, FAL0);
581 NYD_LEAVE;
582 return rv;
585 FL int
586 c_replyall(void *v)
588 int rv;
589 NYD_ENTER;
591 rv = _reply(v, FAL0);
592 NYD_LEAVE;
593 return rv;
596 FL int
597 c_replysender(void *v)
599 int rv;
600 NYD_ENTER;
602 rv = _Reply(v, FAL0);
603 NYD_LEAVE;
604 return rv;
607 FL int
608 c_Reply(void *v)
610 int rv;
611 NYD_ENTER;
613 rv = (*_reply_or_Reply('R'))(v, FAL0);
614 NYD_LEAVE;
615 return rv;
618 FL int
619 c_Lreply(void *v)
621 int rv;
622 NYD_ENTER;
624 rv = _list_reply(v, HF_LIST_REPLY);
625 NYD_LEAVE;
626 return rv;
629 FL int
630 c_followup(void *v)
632 int rv;
633 NYD_ENTER;
635 rv = (*_reply_or_Reply('r'))(v, TRU1);
636 NYD_LEAVE;
637 return rv;
640 FL int
641 c_followupall(void *v)
643 int rv;
644 NYD_ENTER;
646 rv = _reply(v, TRU1);
647 NYD_LEAVE;
648 return rv;
651 FL int
652 c_followupsender(void *v)
654 int rv;
655 NYD_ENTER;
657 rv = _Reply(v, TRU1);
658 NYD_LEAVE;
659 return rv;
662 FL int
663 c_Followup(void *v)
665 int rv;
666 NYD_ENTER;
668 rv = (*_reply_or_Reply('R'))(v, TRU1);
669 NYD_LEAVE;
670 return rv;
673 FL int
674 c_forward(void *v)
676 int rv;
677 NYD_ENTER;
679 rv = _fwd(v, 0);
680 NYD_LEAVE;
681 return rv;
684 FL int
685 c_Forward(void *v)
687 int rv;
688 NYD_ENTER;
690 rv = _fwd(v, 1);
691 NYD_LEAVE;
692 return rv;
695 FL int
696 c_resend(void *v)
698 int rv;
699 NYD_ENTER;
701 rv = _resend1(v, TRU1);
702 NYD_LEAVE;
703 return rv;
706 FL int
707 c_Resend(void *v)
709 int rv;
710 NYD_ENTER;
712 rv = _resend1(v, FAL0);
713 NYD_LEAVE;
714 return rv;
717 /* s-it-mode */