Tweak VERBOSE handling (William Yodlowsky)..
[s-mailx.git] / head.c
blobe603f8235d702a2a098ab7d9532dc836af82c8ab
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Routines for processing and detecting headlines.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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 head
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 #ifdef HAVE_IDNA
43 # if HAVE_IDNA == HAVE_IDNA_LIBIDNA
44 # include <idna.h>
45 # include <idn-free.h>
46 # include <stringprep.h>
47 # elif HAVE_IDNA == HAVE_IDNA_IDNKIT
48 # include <idn/api.h>
49 # endif
50 #endif
52 struct cmatch_data {
53 size_t tlen; /* Length of .tdata */
54 char const *tdata; /* Template date - see _cmatch_data[] */
57 /* Template characters for cmatch_data.tdata:
58 * 'A' An upper case char
59 * 'a' A lower case char
60 * ' ' A space
61 * '0' A digit
62 * 'O' An optional digit or space
63 * ':' A colon
64 * '+' Either a plus or a minus sign */
65 static struct cmatch_data const _cmatch_data[] = {
66 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
67 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
68 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
69 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
70 /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
71 * in the wild (by UW-imap) */
72 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
73 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
74 * zone strings; note that 1. is strictly speaking not correct as some
75 * letters are not used, and 2. is not because only "UT" is defined */
76 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
77 { 28 - 2, __reuse }, { 28 - 1, __reuse }, { 28 - 0, __reuse },
78 { 0, NULL }
80 #define _DATE_MINLEN 21
82 /* Skip over "word" as found in From_ line */
83 static char const * _from__skipword(char const *wp);
85 /* Match the date string against the date template (tp), return if match.
86 * See _cmatch_data[] for template character description */
87 static int _cmatch(size_t len, char const *date,
88 char const *tp);
90 /* Check wether date is a valid 'From_' date.
91 * (Rather ctime(3) generated dates, according to RFC 4155) */
92 static int _is_date(char const *date);
94 /* Convert the domain part of a skinned address to IDNA.
95 * If an error occurs before Unicode information is available, revert the IDNA
96 * error to a normal CHAR one so that the error message doesn't talk Unicode */
97 #ifdef HAVE_IDNA
98 static struct addrguts * _idna_apply(struct addrguts *agp);
99 #endif
101 /* Classify and check a (possibly skinned) header body according to RFC
102 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
103 * also a file or a pipe command, so check that first, then.
104 * Otherwise perform content checking and isolate the domain part (for IDNA) */
105 static int _addrspec_check(int doskin, struct addrguts *agp);
107 /* Return the next header field found in the given message.
108 * Return >= 0 if something found, < 0 elsewise.
109 * "colon" is set to point to the colon in the header.
110 * Must deal with \ continuations & other such fraud */
111 static int gethfield(FILE *f, char **linebuf, size_t *linesize,
112 int rem, char **colon);
114 static int msgidnextc(char const **cp, int *status);
116 /* Count the occurances of c in str */
117 static int charcount(char *str, int c);
119 static char const * nexttoken(char const *cp);
121 static char const *
122 _from__skipword(char const *wp)
124 char c = 0;
125 NYD2_ENTER;
127 if (wp != NULL) {
128 while ((c = *wp++) != '\0' && !blankchar(c)) {
129 if (c == '"') {
130 while ((c = *wp++) != '\0' && c != '"')
132 if (c != '"')
133 --wp;
136 for (; blankchar(c); c = *wp++)
139 NYD2_LEAVE;
140 return (c == 0 ? NULL : wp - 1);
143 static int
144 _cmatch(size_t len, char const *date, char const *tp)
146 int ret = 0;
147 NYD2_ENTER;
149 while (len--) {
150 char c = date[len];
151 switch (tp[len]) {
152 case 'a':
153 if (!lowerchar(c))
154 goto jleave;
155 break;
156 case 'A':
157 if (!upperchar(c))
158 goto jleave;
159 break;
160 case ' ':
161 if (c != ' ')
162 goto jleave;
163 break;
164 case '0':
165 if (!digitchar(c))
166 goto jleave;
167 break;
168 case 'O':
169 if (c != ' ' && !digitchar(c))
170 goto jleave;
171 break;
172 case ':':
173 if (c != ':')
174 goto jleave;
175 break;
176 case '+':
177 if (c != '+' && c != '-')
178 goto jleave;
179 break;
182 ret = 1;
183 jleave:
184 NYD2_LEAVE;
185 return ret;
188 static int
189 _is_date(char const *date)
191 struct cmatch_data const *cmdp;
192 size_t dl;
193 int rv = 0;
194 NYD2_ENTER;
196 if ((dl = strlen(date)) >= _DATE_MINLEN)
197 for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
198 if (dl == cmdp->tlen && (rv = _cmatch(dl, date, cmdp->tdata)))
199 break;
200 NYD2_LEAVE;
201 return rv;
204 #ifdef HAVE_IDNA
205 # if HAVE_IDNA == HAVE_IDNA_LIBIDNA
206 static struct addrguts *
207 _idna_apply(struct addrguts *agp)
209 char *idna_utf8, *idna_ascii, *cs;
210 size_t sz, i;
211 NYD_ENTER;
213 sz = agp->ag_slen - agp->ag_sdom_start;
214 assert(sz > 0);
215 idna_utf8 = ac_alloc(sz +1);
216 memcpy(idna_utf8, agp->ag_skinned + agp->ag_sdom_start, sz);
217 idna_utf8[sz] = '\0';
219 /* GNU Libidn settles on top of iconv(3) without any fallback, so let's just
220 * let it perform the charset conversion, if any should be necessary */
221 if (!(options & OPT_UNICODE)) {
222 char const *tcs = charset_get_lc();
223 idna_ascii = idna_utf8;
224 idna_utf8 = stringprep_convert(idna_ascii, "UTF-8", tcs);
225 i = (idna_utf8 == NULL && errno == EINVAL);
226 ac_free(idna_ascii);
228 if (idna_utf8 == NULL) {
229 if (i)
230 n_err(_("Cannot convert from %s to %s\n"), tcs, "UTF-8");
231 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
232 goto jleave;
236 if (idna_to_ascii_8z(idna_utf8, &idna_ascii, 0) != IDNA_SUCCESS) {
237 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
238 goto jleave1;
241 /* Replace the domain part of .ag_skinned with IDNA version */
242 sz = strlen(idna_ascii);
243 i = agp->ag_sdom_start;
244 cs = salloc(agp->ag_slen - i + sz +1);
245 memcpy(cs, agp->ag_skinned, i);
246 memcpy(cs + i, idna_ascii, sz);
247 i += sz;
248 cs[i] = '\0';
250 agp->ag_skinned = cs;
251 agp->ag_slen = i;
252 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
253 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
255 idn_free(idna_ascii);
256 jleave1:
257 if (options & OPT_UNICODE)
258 ac_free(idna_utf8);
259 else
260 idn_free(idna_utf8);
261 jleave:
262 NYD_LEAVE;
263 return agp;
266 # elif HAVE_IDNA == HAVE_IDNA_IDNKIT /* IDNA==LIBIDNA */
267 static struct addrguts *
268 _idna_apply(struct addrguts *agp)
270 char *idna_in, *idna_out, *cs;
271 size_t sz, i;
272 idn_result_t r;
273 NYD_ENTER;
275 sz = agp->ag_slen - agp->ag_sdom_start;
276 assert(sz > 0);
277 idna_in = ac_alloc(sz +1);
278 memcpy(idna_in, agp->ag_skinned + agp->ag_sdom_start, sz);
279 idna_in[sz] = '\0';
281 for (idna_out = NULL, sz = HOST_NAME_MAX +1;; sz += HOST_NAME_MAX) {
282 idna_out = ac_alloc(sz);
284 r = idn_encodename(IDN_ENCODE_APP, idna_in, idna_out, sz);
285 switch (r) {
286 case idn_success:
287 case idn_buffer_overflow:
288 break;
289 case idn_invalid_encoding:
290 n_err(_("Cannot convert from %s to %s\n"), charset_get_lc(), "UTF-8");
291 /* FALLTHRU */
292 default:
293 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
294 goto jleave;
297 if (r == idn_success)
298 break;
299 ac_free(idna_out);
302 /* Replace the domain part of .ag_skinned with IDNA version */
303 sz = strlen(idna_out);
304 i = agp->ag_sdom_start;
305 cs = salloc(agp->ag_slen - i + sz +1);
306 memcpy(cs, agp->ag_skinned, i);
307 memcpy(cs + i, idna_out, sz);
308 i += sz;
309 cs[i] = '\0';
311 agp->ag_skinned = cs;
312 agp->ag_slen = i;
313 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
314 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
316 jleave:
317 ac_free(idna_out);
318 ac_free(idna_in);
319 NYD_LEAVE;
320 return agp;
322 # endif /* IDNA==IDNKIT */
323 #endif /* HAVE_IDNA */
325 static int
326 _addrspec_check(int skinned, struct addrguts *agp)
328 char *addr, *p;
329 bool_t in_quote;
330 ui8_t in_domain, hadat;
331 union {char c; unsigned char u;} c;
332 #ifdef HAVE_IDNA
333 ui8_t use_idna;
334 #endif
335 NYD_ENTER;
337 #ifdef HAVE_IDNA
338 use_idna = ok_blook(idna_disable) ? 0 : 1;
339 #endif
340 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
341 addr = agp->ag_skinned;
343 if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
344 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
345 goto jleave;
348 /* If the field is not a recipient, it cannot be a file or a pipe */
349 if (!skinned)
350 goto jaddr_check;
352 /* When changing any of the following adjust any RECIPIENTADDRSPEC;
353 * grep the latter for the complete picture */
354 if (*addr == '|') {
355 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
356 goto jleave;
358 if (addr[0] == '/' || (addr[0] == '.' && addr[1] == '/'))
359 goto jisfile;
360 if (memchr(addr, '@', agp->ag_slen) == NULL) {
361 if (*addr == '+')
362 goto jisfile;
363 for (p = addr; (c.c = *p); ++p) {
364 if (c.c == '!' || c.c == '%')
365 break;
366 if (c.c == '/') {
367 jisfile:
368 agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
369 goto jleave;
374 jaddr_check:
375 in_quote = FAL0;
376 in_domain = hadat = 0;
378 for (p = addr; (c.c = *p++) != '\0';) {
379 if (c.c == '"') {
380 in_quote = !in_quote;
381 } else if (c.u < 040 || c.u >= 0177) { /* TODO no magics: !bodychar()? */
382 #ifdef HAVE_IDNA
383 if (in_domain && use_idna > 0) {
384 if (use_idna == 1)
385 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_IDNA,
386 c.u);
387 use_idna = 2;
388 } else
389 #endif
390 break;
391 } else if (in_domain == 2) {
392 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
393 break;
394 } else if (in_quote && in_domain == 0) {
395 /*EMPTY*/;
396 } else if (c.c == '\\' && *p != '\0') {
397 ++p;
398 } else if (c.c == '@') {
399 if (hadat++ > 0) {
400 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
401 c.u);
402 goto jleave;
404 agp->ag_sdom_start = PTR2SIZE(p - addr);
405 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
406 in_domain = (*p == '[') ? 2 : 1;
407 continue;
408 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
409 c.c == ',' || c.c == ';' || c.c == ':' || c.c == '\\' ||
410 c.c == '[' || c.c == ']')
411 break;
412 hadat = 0;
414 if (c.c != '\0') {
415 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
416 goto jleave;
419 if (!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR))
420 agp->ag_n_flags |= NAME_ADDRSPEC_ISNAME;
422 #ifdef HAVE_IDNA
423 if (use_idna == 2)
424 agp = _idna_apply(agp);
425 #endif
426 jleave:
427 NYD_LEAVE;
428 return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) != 0);
431 static int
432 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
434 char *line2 = NULL, *cp, *cp2;
435 size_t line2size = 0;
436 int c, isenc;
437 NYD2_ENTER;
439 if (*linebuf == NULL)
440 *linebuf = srealloc(*linebuf, *linesize = 1);
441 **linebuf = '\0';
442 for (;;) {
443 if (--rem < 0) {
444 rem = -1;
445 break;
447 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
448 rem = -1;
449 break;
451 for (cp = *linebuf; fieldnamechar(*cp); ++cp)
453 if (cp > *linebuf)
454 while (blankchar(*cp))
455 ++cp;
456 if (*cp != ':' || cp == *linebuf)
457 continue;
459 /* I guess we got a headline. Handle wraparound */
460 *colon = cp;
461 cp = *linebuf + c;
462 for (;;) {
463 isenc = 0;
464 while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
466 cp++;
467 if (rem <= 0)
468 break;
469 if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
470 isenc |= 1;
471 ungetc(c = getc(f), f);
472 if (!blankchar(c))
473 break;
474 c = readline_restart(f, &line2, &line2size, 0);
475 if (c < 0)
476 break;
477 --rem;
478 for (cp2 = line2; blankchar(*cp2); ++cp2)
480 c -= (int)PTR2SIZE(cp2 - line2);
481 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
482 isenc |= 2;
483 if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
484 size_t diff = PTR2SIZE(cp - *linebuf),
485 colondiff = PTR2SIZE(*colon - *linebuf);
486 *linebuf = srealloc(*linebuf, *linesize += c + 2);
487 cp = &(*linebuf)[diff];
488 *colon = &(*linebuf)[colondiff];
490 if (isenc != 3)
491 *cp++ = ' ';
492 memcpy(cp, cp2, c);
493 cp += c;
495 *cp = '\0';
497 if (line2 != NULL)
498 free(line2);
499 break;
501 NYD2_LEAVE;
502 return rem;
505 static int
506 msgidnextc(char const **cp, int *status)
508 int c;
509 NYD2_ENTER;
511 assert(cp != NULL);
512 assert(*cp != NULL);
513 assert(status != NULL);
515 for (;;) {
516 if (*status & 01) {
517 if (**cp == '"') {
518 *status &= ~01;
519 (*cp)++;
520 continue;
522 if (**cp == '\\') {
523 (*cp)++;
524 if (**cp == '\0')
525 goto jeof;
527 goto jdfl;
529 switch (**cp) {
530 case '(':
531 *cp = skip_comment(&(*cp)[1]);
532 continue;
533 case '>':
534 case '\0':
535 jeof:
536 c = '\0';
537 goto jleave;
538 case '"':
539 (*cp)++;
540 *status |= 01;
541 continue;
542 case '@':
543 *status |= 02;
544 /*FALLTHRU*/
545 default:
546 jdfl:
547 c = *(*cp)++ & 0377;
548 c = (*status & 02) ? lowerconv(c) : c;
549 goto jleave;
552 jleave:
553 NYD2_LEAVE;
554 return c;
557 static int
558 charcount(char *str, int c)
560 char *cp;
561 int i;
562 NYD2_ENTER;
564 for (i = 0, cp = str; *cp; ++cp)
565 if (*cp == c)
566 ++i;
567 NYD2_LEAVE;
568 return i;
571 static char const *
572 nexttoken(char const *cp)
574 NYD2_ENTER;
575 for (;;) {
576 if (*cp == '\0') {
577 cp = NULL;
578 break;
581 if (*cp == '(') {
582 size_t nesting = 1;
584 do switch (*++cp) {
585 case '(':
586 ++nesting;
587 break;
588 case ')':
589 --nesting;
590 break;
591 } while (nesting > 0 && *cp != '\0'); /* XXX error? */
592 } else if (blankchar(*cp) || *cp == ',')
593 ++cp;
594 else
595 break;
597 NYD2_LEAVE;
598 return cp;
601 FL char const *
602 myaddrs(struct header *hp)
604 struct name *np;
605 char *rv;
606 NYD_ENTER;
608 if (hp != NULL && (np = hp->h_from) != NULL) {
609 if ((rv = np->n_fullname) != NULL)
610 goto jleave;
611 if ((rv = np->n_name) != NULL)
612 goto jleave;
615 if ((rv = ok_vlook(from)) != NULL)
616 goto jleave;
618 /* When invoking *sendmail* directly, it's its task to generate an otherwise
619 * undeterminable From: address. However, if the user sets *hostname*,
620 * accept his desire */
621 if (ok_vlook(smtp) != NULL || ok_vlook(hostname) != NULL) {
622 char *hn = nodename(1);
623 size_t sz = strlen(myname) + strlen(hn) + 1 +1;
624 rv = salloc(sz);
625 sstpcpy(sstpcpy(sstpcpy(rv, myname), "@"), hn);
627 jleave:
628 NYD_LEAVE;
629 return rv;
632 FL char const *
633 myorigin(struct header *hp)
635 char const *rv = NULL, *ccp;
636 struct name *np;
637 NYD_ENTER;
639 if ((ccp = myaddrs(hp)) != NULL &&
640 (np = lextract(ccp, GEXTRA | GFULL)) != NULL)
641 rv = (np->n_flink != NULL) ? ok_vlook(sender) : ccp;
642 NYD_LEAVE;
643 return rv;
646 FL int
647 is_head(char const *linebuf, size_t linelen, bool_t compat)
649 char date[FROM_DATEBUF];
650 int rv;
651 NYD2_ENTER;
653 if ((rv = (linelen >= 5 && !strncmp(linebuf, "From ", 5))) &&
654 (!compat || ok_blook(mbox_rfc4155)))
655 rv = (extract_date_from_from_(linebuf, linelen, date) && _is_date(date));
656 NYD2_LEAVE;
657 return rv;
660 FL int
661 extract_date_from_from_(char const *line, size_t linelen,
662 char datebuf[FROM_DATEBUF])
664 int rv = 0;
665 char const *cp = line;
666 NYD_ENTER;
668 /* "From " */
669 cp = _from__skipword(cp);
670 if (cp == NULL)
671 goto jerr;
672 /* "addr-spec " */
673 cp = _from__skipword(cp);
674 if (cp == NULL)
675 goto jerr;
676 if (cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
677 cp = _from__skipword(cp);
678 if (cp == NULL)
679 goto jerr;
681 /* It seems there are invalid MBOX archives in the wild, compare
682 * . http://bugs.debian.org/624111
683 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
684 * What they do is that they obfuscate the address to "name at host".
685 * I think we should handle that */
686 else if(cp[0] == 'a' && cp[1] == 't' && cp[2] == ' '){
687 cp = _from__skipword(cp += 3);
688 if (cp == NULL)
689 goto jerr;
692 linelen -= PTR2SIZE(cp - line);
693 if (linelen < _DATE_MINLEN)
694 goto jerr;
695 if (cp[linelen - 1] == '\n') {
696 --linelen;
697 /* (Rather IMAP/POP3 only) */
698 if (cp[linelen - 1] == '\r')
699 --linelen;
700 if (linelen < _DATE_MINLEN)
701 goto jerr;
703 if (linelen >= FROM_DATEBUF)
704 goto jerr;
706 rv = 1;
707 jleave:
708 memcpy(datebuf, cp, linelen);
709 datebuf[linelen] = '\0';
710 NYD_LEAVE;
711 return rv;
712 jerr:
713 cp = _("<Unknown date>");
714 linelen = strlen(cp);
715 if (linelen >= FROM_DATEBUF)
716 linelen = FROM_DATEBUF;
717 goto jleave;
720 FL void
721 extract_header(FILE *fp, struct header *hp, si8_t *checkaddr_err)
723 /* See the prototype declaration for the hairy relationship of
724 * options&OPT_t_FLAG and/or pstate&PS_t_FLAG in here */
725 struct n_header_field **hftail;
726 struct header nh, *hq = &nh;
727 char *linebuf = NULL /* TODO line pool */, *colon;
728 size_t linesize = 0, seenfields = 0;
729 int lc, c;
730 char const *val, *cp;
731 NYD_ENTER;
733 memset(hq, 0, sizeof *hq);
734 if ((pstate & PS_t_FLAG) && (options & OPT_t_FLAG)) {
735 hq->h_to = hp->h_to;
736 hq->h_cc = hp->h_cc;
737 hq->h_bcc = hp->h_bcc;
739 hftail = &hq->h_user_headers;
741 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
744 /* TODO yippieia, cat(check(lextract)) :-) */
745 rewind(fp);
746 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
747 struct name *np;
749 /* We explicitly allow EAF_NAME for some addressees since aliases are not
750 * yet expanded when we parse these! */
751 if ((val = thisfield(linebuf, "to")) != NULL) {
752 ++seenfields;
753 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL),
754 EACM_NORMAL | EAF_NAME, checkaddr_err));
755 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
756 ++seenfields;
757 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL),
758 EACM_NORMAL | EAF_NAME, checkaddr_err));
759 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
760 ++seenfields;
761 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL),
762 EACM_NORMAL | EAF_NAME, checkaddr_err));
763 } else if ((val = thisfield(linebuf, "from")) != NULL) {
764 if (!(pstate & PS_t_FLAG) || (options & OPT_t_FLAG)) {
765 ++seenfields;
766 hq->h_from = cat(hq->h_from,
767 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
768 EACM_STRICT, NULL));
770 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
771 ++seenfields;
772 hq->h_replyto = cat(hq->h_replyto,
773 checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
774 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
775 if (!(pstate & PS_t_FLAG) || (options & OPT_t_FLAG)) {
776 ++seenfields;
777 hq->h_sender = cat(hq->h_sender, /* TODO cat? check! */
778 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
779 EACM_STRICT, NULL));
780 } else
781 goto jebadhead;
782 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
783 (val = thisfield(linebuf, "subj")) != NULL) {
784 ++seenfields;
785 for (cp = val; blankchar(*cp); ++cp)
787 hq->h_subject = (hq->h_subject != NULL)
788 ? save2str(hq->h_subject, cp) : savestr(cp);
790 /* The remaining are mostly hacked in and thus TODO -- at least in
791 * TODO respect to their content checking */
792 else if((val = thisfield(linebuf, "message-id")) != NULL){
793 if(pstate & PS_t_FLAG){
794 np = checkaddrs(lextract(val, GREF),
795 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
796 NULL);
797 if (np == NULL || np->n_flink != NULL)
798 goto jebadhead;
799 ++seenfields;
800 hq->h_message_id = np;
801 }else
802 goto jebadhead;
803 }else if((val = thisfield(linebuf, "in-reply-to")) != NULL){
804 if(pstate & PS_t_FLAG){
805 np = checkaddrs(lextract(val, GREF),
806 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
807 NULL);
808 if (np == NULL || np->n_flink != NULL)
809 goto jebadhead;
810 ++seenfields;
811 hq->h_in_reply_to = np;
812 }else
813 goto jebadhead;
814 }else if((val = thisfield(linebuf, "references")) != NULL){
815 if(pstate & PS_t_FLAG){
816 ++seenfields;
817 /* TODO Limit number of references TODO better on parser side */
818 hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
819 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
820 NULL));
821 }else
822 goto jebadhead;
824 /* and that is very hairy */
825 else if((val = thisfield(linebuf, "mail-followup-to")) != NULL){
826 if(pstate & PS_t_FLAG){
827 ++seenfields;
828 hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val, GEXTRA | GFULL),
829 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME,
830 checkaddr_err));
831 }else
832 goto jebadhead;
834 /* A free-form user header; gethfield() did some verification already.. */
835 else{
836 struct n_header_field *hfp;
837 ui32_t nl, bl;
838 char const *nstart;
840 for(nstart = cp = linebuf;; ++cp)
841 if(!fieldnamechar(*cp))
842 break;
843 nl = (ui32_t)PTR2SIZE(cp - nstart);
845 while(blankchar(*cp))
846 ++cp;
847 if(*cp++ != ':'){
848 jebadhead:
849 n_err(_("Ignoring header field \"%s\"\n"), linebuf);
850 continue;
852 while(blankchar(*cp))
853 ++cp;
854 bl = (ui32_t)strlen(cp) +1;
856 ++seenfields;
857 *hftail = hfp = salloc(VSTRUCT_SIZEOF(struct n_header_field, hf_dat) +
858 nl +1 + bl);
859 hftail = &hfp->hf_next;
860 hfp->hf_next = NULL;
861 hfp->hf_nl = nl;
862 hfp->hf_bl = bl - 1;
863 memcpy(hfp->hf_dat, nstart, nl);
864 hfp->hf_dat[nl++] = '\0';
865 memcpy(hfp->hf_dat + nl, cp, bl);
869 /* In case the blank line after the header has been edited out. Otherwise,
870 * fetch the header separator */
871 if (linebuf != NULL) {
872 if (linebuf[0] != '\0') {
873 for (cp = linebuf; *(++cp) != '\0';)
875 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
876 } else {
877 if ((c = getc(fp)) != '\n' && c != EOF)
878 ungetc(c, fp);
882 if (seenfields > 0) {
883 hp->h_to = hq->h_to;
884 hp->h_cc = hq->h_cc;
885 hp->h_bcc = hq->h_bcc;
886 hp->h_from = hq->h_from;
887 hp->h_replyto = hq->h_replyto;
888 hp->h_sender = hq->h_sender;
889 if (hq->h_subject != NULL || !(pstate & PS_t_FLAG) ||
890 !(options & OPT_t_FLAG))
891 hp->h_subject = hq->h_subject;
892 hp->h_user_headers = hq->h_user_headers;
894 if (pstate & PS_t_FLAG) {
895 hp->h_ref = hq->h_ref;
896 hp->h_message_id = hq->h_message_id;
897 hp->h_in_reply_to = hq->h_in_reply_to;
898 hp->h_mft = hq->h_mft;
900 /* And perform additional validity checks so that we don't bail later
901 * on TODO this is good and the place where this should occur,
902 * TODO unfortunately a lot of other places do again and blabla */
903 if (pstate & PS_t_FLAG) {
904 if (hp->h_from == NULL)
905 hp->h_from = option_r_arg;
906 else if (hp->h_from->n_flink != NULL && hp->h_sender == NULL)
907 hp->h_sender = lextract(ok_vlook(sender),
908 GEXTRA | GFULL | GFULLEXTRA);
911 } else
912 n_err(_("Restoring deleted header lines\n"));
914 if (linebuf != NULL)
915 free(linebuf);
916 NYD_LEAVE;
919 FL char *
920 hfield_mult(char const *field, struct message *mp, int mult)
922 FILE *ibuf;
923 int lc;
924 struct str hfs;
925 size_t linesize = 0; /* TODO line pool */
926 char *linebuf = NULL, *colon;
927 char const *hfield;
928 NYD_ENTER;
930 /* There are (spam) messages which have header bytes which are many KB when
931 * joined, so resize a single heap storage until we are done if we shall
932 * collect a field that may have multiple bodies; only otherwise use the
933 * string dope directly */
934 memset(&hfs, 0, sizeof hfs);
936 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
937 goto jleave;
938 if ((lc = mp->m_lines - 1) < 0)
939 goto jleave;
941 if ((mp->m_flag & MNOFROM) == 0 &&
942 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
943 goto jleave;
944 while (lc > 0) {
945 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
946 break;
947 if ((hfield = thisfield(linebuf, field)) != NULL && *hfield != '\0') {
948 if (mult)
949 n_str_add_buf(&hfs, hfield, strlen(hfield));
950 else {
951 hfs.s = savestr(hfield);
952 break;
957 jleave:
958 if (linebuf != NULL)
959 free(linebuf);
960 if (mult && hfs.s != NULL) {
961 colon = savestrbuf(hfs.s, hfs.l);
962 free(hfs.s);
963 hfs.s = colon;
965 NYD_LEAVE;
966 return hfs.s;
969 FL char const *
970 thisfield(char const *linebuf, char const *field)
972 char const *rv = NULL;
973 NYD2_ENTER;
975 while (lowerconv(*linebuf) == lowerconv(*field)) {
976 ++linebuf;
977 ++field;
979 if (*field != '\0')
980 goto jleave;
982 while (blankchar(*linebuf))
983 ++linebuf;
984 if (*linebuf++ != ':')
985 goto jleave;
987 while (blankchar(*linebuf)) /* TODO header parser.. strip trailing WS?!? */
988 ++linebuf;
989 rv = linebuf;
990 jleave:
991 NYD2_LEAVE;
992 return rv;
995 FL char *
996 nameof(struct message *mp, int reptype)
998 char *cp, *cp2;
999 NYD_ENTER;
1001 cp = skin(name1(mp, reptype));
1002 if (reptype != 0 || charcount(cp, '!') < 2)
1003 goto jleave;
1004 cp2 = strrchr(cp, '!');
1005 --cp2;
1006 while (cp2 > cp && *cp2 != '!')
1007 --cp2;
1008 if (*cp2 == '!')
1009 cp = cp2 + 1;
1010 jleave:
1011 NYD_LEAVE;
1012 return cp;
1015 FL char const *
1016 skip_comment(char const *cp)
1018 size_t nesting;
1019 NYD_ENTER;
1021 for (nesting = 1; nesting > 0 && *cp; ++cp) {
1022 switch (*cp) {
1023 case '\\':
1024 if (cp[1])
1025 ++cp;
1026 break;
1027 case '(':
1028 ++nesting;
1029 break;
1030 case ')':
1031 --nesting;
1032 break;
1035 NYD_LEAVE;
1036 return cp;
1039 FL char const *
1040 routeaddr(char const *name)
1042 char const *np, *rp = NULL;
1043 NYD_ENTER;
1045 for (np = name; *np; np++) {
1046 switch (*np) {
1047 case '(':
1048 np = skip_comment(np + 1) - 1;
1049 break;
1050 case '"':
1051 while (*np) {
1052 if (*++np == '"')
1053 break;
1054 if (*np == '\\' && np[1])
1055 np++;
1057 break;
1058 case '<':
1059 rp = np;
1060 break;
1061 case '>':
1062 goto jleave;
1065 rp = NULL;
1066 jleave:
1067 NYD_LEAVE;
1068 return rp;
1071 FL enum expand_addr_flags
1072 expandaddr_to_eaf(void)
1074 struct eafdesc {
1075 char const *eafd_name;
1076 bool_t eafd_is_target;
1077 ui8_t eafd_andoff;
1078 ui8_t eafd_or;
1079 } const eafa[] = {
1080 {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
1081 {"fail", FAL0, EAF_NONE, EAF_FAIL},
1082 {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
1083 {"file", TRU1, EAF_NONE, EAF_FILE},
1084 {"pipe", TRU1, EAF_NONE, EAF_PIPE},
1085 {"name", TRU1, EAF_NONE, EAF_NAME},
1086 {"addr", TRU1, EAF_NONE, EAF_ADDR}
1087 }, *eafp;
1089 char *buf;
1090 enum expand_addr_flags rv;
1091 char const *cp;
1092 NYD2_ENTER;
1094 if ((cp = ok_vlook(expandaddr)) == NULL)
1095 rv = EAF_RESTRICT_TARGETS;
1096 else if (*cp == '\0')
1097 rv = EAF_TARGET_MASK;
1098 else {
1099 rv = EAF_TARGET_MASK;
1101 for (buf = savestr(cp); (cp = n_strsep(&buf, ',', TRU1)) != NULL;) {
1102 bool_t minus;
1104 if ((minus = (*cp == '-')) || *cp == '+')
1105 ++cp;
1106 for (eafp = eafa;; ++eafp) {
1107 if (eafp == eafa + NELEM(eafa)) {
1108 if (options & OPT_D_V)
1109 n_err(_("Unknown *expandaddr* value: \"%s\"\n"), cp);
1110 break;
1111 } else if (!asccasecmp(cp, eafp->eafd_name)) {
1112 if (!minus) {
1113 rv &= ~eafp->eafd_andoff;
1114 rv |= eafp->eafd_or;
1115 } else {
1116 if (eafp->eafd_is_target)
1117 rv &= ~eafp->eafd_or;
1118 else if (options & OPT_D_V)
1119 n_err(_("\"-\" prefix invalid for *expandaddr* value: "
1120 "\"%s\"\n"), --cp);
1122 break;
1123 } else if (!asccasecmp(cp, "noalias")) { /* TODO v15 OBSOLETE */
1124 OBSOLETE(_("*expandaddr*: \"noalias\" is henceforth \"-name\""));
1125 rv &= ~EAF_NAME;
1126 break;
1131 if ((rv & EAF_RESTRICT) && (options & (OPT_INTERACTIVE | OPT_TILDE_FLAG)))
1132 rv |= EAF_TARGET_MASK;
1133 else if (options & OPT_D_V) {
1134 if (!(rv & EAF_TARGET_MASK))
1135 n_err(_("*expandaddr* doesn't allow any addresses\n"));
1136 else if ((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
1137 n_err(_("*expandaddr* with \"fail\" but no restrictions\n"));
1140 NYD2_LEAVE;
1141 return rv;
1144 FL si8_t
1145 is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm)
1147 char cbuf[sizeof "'\\U12340'"];
1148 enum expand_addr_flags eaf;
1149 char const *cs;
1150 int f;
1151 si8_t rv;
1152 NYD_ENTER;
1154 f = np->n_flags;
1156 if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) {
1157 if ((eacm & EACM_NOLOG) || (f & NAME_ADDRSPEC_ERR_EMPTY)) {
1159 } else {
1160 ui32_t c;
1161 char const *fmt = "'\\x%02X'";
1162 bool_t ok8bit = TRU1;
1164 if (f & NAME_ADDRSPEC_ERR_IDNA) {
1165 cs = _("Invalid domain name: \"%s\", character %s\n");
1166 fmt = "'\\U%04X'";
1167 ok8bit = FAL0;
1168 } else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
1169 cs = _("\"%s\" contains invalid %s sequence\n");
1170 else
1171 cs = _("\"%s\" contains invalid character %s\n");
1173 c = NAME_ADDRSPEC_ERR_GETWC(f);
1174 snprintf(cbuf, sizeof cbuf,
1175 (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
1176 goto jprint;
1178 goto jleave;
1181 /* *expandaddr* stuff */
1182 if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
1183 goto jleave;
1185 eaf = expandaddr_to_eaf();
1187 if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) {
1188 if (eaf & EAF_FAIL)
1189 rv = -rv;
1190 cs = _("\"%s\"%s: file or pipe addressees not allowed here\n");
1191 if (eacm & EACM_NOLOG)
1192 goto jleave;
1193 else
1194 goto j0print;
1197 eaf |= (eacm & EAF_TARGET_MASK);
1198 if (eacm & EACM_NONAME)
1199 eaf &= ~EAF_NAME;
1201 if (eaf == EAF_NONE) {
1202 rv = FAL0;
1203 goto jleave;
1205 if (eaf & EAF_FAIL)
1206 rv = -rv;
1208 if (!(eaf & EAF_FILE) && (f & NAME_ADDRSPEC_ISFILE)) {
1209 cs = _("\"%s\"%s: *expandaddr* doesn't allow file target\n");
1210 if (eacm & EACM_NOLOG)
1211 goto jleave;
1212 } else if (!(eaf & EAF_PIPE) && (f & NAME_ADDRSPEC_ISPIPE)) {
1213 cs = _("\"%s\"%s: *expandaddr* doesn't allow command pipe target\n");
1214 if (eacm & EACM_NOLOG)
1215 goto jleave;
1216 } else if (!(eaf & EAF_NAME) && (f & NAME_ADDRSPEC_ISNAME)) {
1217 cs = _("\"%s\"%s: *expandaddr* doesn't allow user name target\n");
1218 if (eacm & EACM_NOLOG)
1219 goto jleave;
1220 } else if (!(eaf & EAF_ADDR) && (f & NAME_ADDRSPEC_ISADDR)) {
1221 cs = _("\"%s\"%s: *expandaddr* doesn't allow mail address target\n");
1222 if (eacm & EACM_NOLOG)
1223 goto jleave;
1224 } else {
1225 rv = FAL0;
1226 goto jleave;
1229 j0print:
1230 cbuf[0] = '\0';
1231 jprint:
1232 n_err(cs, np->n_name, cbuf);
1233 jleave:
1234 NYD_LEAVE;
1235 return rv;
1238 FL char *
1239 skin(char const *name)
1241 struct addrguts ag;
1242 char *ret = NULL;
1243 NYD_ENTER;
1245 if (name != NULL) {
1246 addrspec_with_guts(1, name, &ag);
1247 ret = ag.ag_skinned;
1248 if (!(ag.ag_n_flags & NAME_NAME_SALLOC))
1249 ret = savestrbuf(ret, ag.ag_slen);
1251 NYD_LEAVE;
1252 return ret;
1255 /* TODO addrspec_with_guts: RFC 5322
1256 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1257 FL int
1258 addrspec_with_guts(int doskin, char const *name, struct addrguts *agp)
1260 char const *cp;
1261 char *cp2, *bufend, *nbuf, c, gotlt, gotaddr, lastsp;
1262 int rv = 1;
1263 NYD_ENTER;
1265 memset(agp, 0, sizeof *agp);
1267 if ((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0) {
1268 agp->ag_skinned = UNCONST(""); /* ok: NAME_SALLOC is not set */
1269 agp->ag_slen = 0;
1270 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
1271 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
1272 goto jleave;
1275 if (!doskin || !anyof(name, "(< ")) {
1276 /*agp->ag_iaddr_start = 0;*/
1277 agp->ag_iaddr_aend = agp->ag_ilen;
1278 agp->ag_skinned = UNCONST(name); /* (NAME_SALLOC not set) */
1279 agp->ag_slen = agp->ag_ilen;
1280 agp->ag_n_flags = NAME_SKINNED;
1281 goto jcheck;
1284 /* Something makes us think we have to perform the skin operation */
1285 nbuf = ac_alloc(agp->ag_ilen + 1);
1286 /*agp->ag_iaddr_start = 0;*/
1287 cp2 = bufend = nbuf;
1288 gotlt = gotaddr = lastsp = 0;
1290 for (cp = name++; (c = *cp++) != '\0'; ) {
1291 switch (c) {
1292 case '(':
1293 cp = skip_comment(cp);
1294 lastsp = 0;
1295 break;
1296 case '"':
1297 /* Start of a "quoted-string".
1298 * Copy it in its entirety */
1299 /* XXX RFC: quotes are "semantically invisible"
1300 * XXX But it was explicitly added (Changelog.Heirloom,
1301 * XXX [9.23] released 11/15/00, "Do not remove quotes
1302 * XXX when skinning names"? No more info.. */
1303 *cp2++ = c;
1304 while ((c = *cp) != '\0') { /* TODO improve */
1305 cp++;
1306 if (c == '"') {
1307 *cp2++ = c;
1308 break;
1310 if (c != '\\')
1311 *cp2++ = c;
1312 else if ((c = *cp) != '\0') {
1313 *cp2++ = c;
1314 cp++;
1317 lastsp = 0;
1318 break;
1319 case ' ':
1320 case '\t':
1321 if (gotaddr == 1) {
1322 gotaddr = 2;
1323 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1325 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
1326 cp += 3, *cp2++ = '@';
1327 else if (cp[0] == '@' && blankchar(cp[1]))
1328 cp += 2, *cp2++ = '@';
1329 else
1330 lastsp = 1;
1331 break;
1332 case '<':
1333 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
1334 cp2 = bufend;
1335 gotlt = gotaddr = 1;
1336 lastsp = 0;
1337 break;
1338 case '>':
1339 if (gotlt) {
1340 /* (_addrspec_check() verifies these later!) */
1341 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1342 gotlt = 0;
1343 while ((c = *cp) != '\0' && c != ',') {
1344 cp++;
1345 if (c == '(')
1346 cp = skip_comment(cp);
1347 else if (c == '"')
1348 while ((c = *cp) != '\0') {
1349 cp++;
1350 if (c == '"')
1351 break;
1352 if (c == '\\' && *cp != '\0')
1353 ++cp;
1356 lastsp = 0;
1357 break;
1359 /* FALLTRHOUGH */
1360 default:
1361 if (lastsp) {
1362 lastsp = 0;
1363 if (gotaddr)
1364 *cp2++ = ' ';
1366 *cp2++ = c;
1367 if (c == ',') {
1368 if (!gotlt) {
1369 *cp2++ = ' ';
1370 for (; blankchar(*cp); ++cp)
1372 lastsp = 0;
1373 bufend = cp2;
1375 } else if (!gotaddr) {
1376 gotaddr = 1;
1377 agp->ag_iaddr_start = PTR2SIZE(cp - name);
1381 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
1382 if (agp->ag_iaddr_aend == 0)
1383 agp->ag_iaddr_aend = agp->ag_ilen;
1385 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
1386 ac_free(nbuf);
1387 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
1388 jcheck:
1389 rv = _addrspec_check(doskin, agp);
1390 jleave:
1391 NYD_LEAVE;
1392 return rv;
1395 FL char *
1396 realname(char const *name)
1398 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1399 char *rname, *rp;
1400 struct str in, out;
1401 int quoted, good, nogood;
1402 NYD_ENTER;
1404 if ((cp = UNCONST(name)) == NULL)
1405 goto jleave;
1406 for (; *cp != '\0'; ++cp) {
1407 switch (*cp) {
1408 case '(':
1409 if (cstart != NULL) {
1410 /* More than one comment in address, doesn't make sense to display
1411 * it without context. Return the entire field */
1412 cp = mime_fromaddr(name);
1413 goto jleave;
1415 cstart = cp++;
1416 cp = skip_comment(cp);
1417 cend = cp--;
1418 if (cend <= cstart)
1419 cend = cstart = NULL;
1420 break;
1421 case '"':
1422 while (*cp) {
1423 if (*++cp == '"')
1424 break;
1425 if (*cp == '\\' && cp[1])
1426 ++cp;
1428 break;
1429 case '<':
1430 if (cp > name) {
1431 cstart = name;
1432 cend = cp;
1434 break;
1435 case ',':
1436 /* More than one address. Just use the first one */
1437 goto jbrk;
1441 jbrk:
1442 if (cstart == NULL) {
1443 if (*name == '<') {
1444 /* If name contains only a route-addr, the surrounding angle brackets
1445 * don't serve any useful purpose when displaying, so remove */
1446 cp = prstr(skin(name));
1447 } else
1448 cp = mime_fromaddr(name);
1449 goto jleave;
1452 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1453 * not stripped. The idea is to strip only syntactical relevant things (but
1454 * this is not necessarily the most sensible way in practice) */
1455 rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
1456 quoted = 0;
1457 for (cp = cstart; cp < cend; ++cp) {
1458 if (*cp == '(' && !quoted) {
1459 cq = skip_comment(++cp);
1460 if (PTRCMP(--cq, >, cend))
1461 cq = cend;
1462 while (cp < cq) {
1463 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
1464 ++cp;
1465 *rp++ = *cp++;
1467 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
1468 *rp++ = *++cp;
1469 else if (*cp == '"') {
1470 quoted = !quoted;
1471 continue;
1472 } else
1473 *rp++ = *cp;
1475 *rp = '\0';
1476 in.s = rname;
1477 in.l = rp - rname;
1478 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1479 ac_free(rname);
1480 rname = savestr(out.s);
1481 free(out.s);
1483 while (blankchar(*rname))
1484 ++rname;
1485 for (rp = rname; *rp != '\0'; ++rp)
1487 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
1488 *rp = '\0';
1489 if (rp == rname) {
1490 cp = mime_fromaddr(name);
1491 goto jleave;
1494 /* mime_fromhdr() has converted all nonprintable characters to question
1495 * marks now. These and blanks are considered uninteresting; if the
1496 * displayed part of the real name contains more than 25% of them, it is
1497 * probably better to display the plain email address instead */
1498 good = 0;
1499 nogood = 0;
1500 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
1501 if (*rp == '?' || blankchar(*rp))
1502 ++nogood;
1503 else
1504 ++good;
1505 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
1506 jleave:
1507 NYD_LEAVE;
1508 return UNCONST(cp);
1511 FL char *
1512 name1(struct message *mp, int reptype)
1514 char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
1515 size_t namesize, linesize = 0;
1516 FILE *ibuf;
1517 int f1st = 1;
1518 NYD_ENTER;
1520 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
1521 goto jleave;
1522 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
1523 goto jleave;
1525 namebuf = smalloc(namesize = 1);
1526 namebuf[0] = 0;
1527 if (mp->m_flag & MNOFROM)
1528 goto jout;
1529 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1530 goto jout;
1531 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1532 goto jout;
1534 jnewname:
1535 if (namesize <= linesize)
1536 namebuf = srealloc(namebuf, namesize = linesize +1);
1537 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
1539 for (; blankchar(*cp); ++cp)
1541 for (cp2 = namebuf + strlen(namebuf);
1542 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
1543 *cp2++ = *cp++;
1544 *cp2 = '\0';
1546 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1547 goto jout;
1548 if ((cp = strchr(linebuf, 'F')) == NULL)
1549 goto jout;
1550 if (strncmp(cp, "From", 4)) /* XXX is_head? */
1551 goto jout;
1552 if (namesize <= linesize)
1553 namebuf = srealloc(namebuf, namesize = linesize + 1);
1555 while ((cp = strchr(cp, 'r')) != NULL) {
1556 if (!strncmp(cp, "remote", 6)) {
1557 if ((cp = strchr(cp, 'f')) == NULL)
1558 break;
1559 if (strncmp(cp, "from", 4) != 0)
1560 break;
1561 if ((cp = strchr(cp, ' ')) == NULL)
1562 break;
1563 cp++;
1564 if (f1st) {
1565 strncpy(namebuf, cp, namesize);
1566 f1st = 0;
1567 } else {
1568 cp2 = strrchr(namebuf, '!') + 1;
1569 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
1571 namebuf[namesize - 2] = '!';
1572 namebuf[namesize - 1] = '\0';
1573 goto jnewname;
1575 cp++;
1577 jout:
1578 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
1579 *cp == '\0')
1580 cp = savestr(namebuf);
1582 if (linebuf != NULL)
1583 free(linebuf);
1584 free(namebuf);
1585 jleave:
1586 NYD_LEAVE;
1587 return cp;
1590 FL char *
1591 subject_re_trim(char *s)
1593 struct {
1594 ui8_t len;
1595 char dat[7];
1596 } const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
1597 { 3, "re:" },
1598 { 3, "aw:" }, { 5, "antw:" }, /* de */
1599 { 0, "" }
1602 bool_t any = FAL0;
1603 char *orig_s = s, *re_st = NULL, *re_st_x;
1604 size_t re_l = 0 /* pacify CC */;
1605 NYD_ENTER;
1607 if ((re_st_x = ok_vlook(reply_strings)) != NULL &&
1608 (re_l = strlen(re_st_x)) > 0) {
1609 re_st = ac_alloc(++re_l * 2);
1610 memcpy(re_st, re_st_x, re_l);
1613 jouter:
1614 while (*s != '\0') {
1615 while (spacechar(*s))
1616 ++s;
1618 for (pp = ignored; pp->len > 0; ++pp)
1619 if (is_asccaseprefix(s, pp->dat)) {
1620 s += pp->len;
1621 any = TRU1;
1622 goto jouter;
1625 if (re_st != NULL) {
1626 char *cp;
1628 memcpy(re_st_x = re_st + re_l, re_st, re_l);
1629 while ((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
1630 if (is_asccaseprefix(s, cp)) {
1631 s += strlen(cp);
1632 any = TRU1;
1633 goto jouter;
1636 break;
1639 if (re_st != NULL)
1640 ac_free(re_st);
1641 NYD_LEAVE;
1642 return any ? s : orig_s;
1645 FL int
1646 msgidcmp(char const *s1, char const *s2)
1648 int q1 = 0, q2 = 0, c1, c2;
1649 NYD_ENTER;
1651 do {
1652 c1 = msgidnextc(&s1, &q1);
1653 c2 = msgidnextc(&s2, &q2);
1654 if (c1 != c2)
1655 break;
1656 } while (c1 && c2);
1657 NYD_LEAVE;
1658 return c1 - c2;
1661 FL int
1662 is_ign(char const *field, size_t fieldlen, struct ignoretab igta[2])
1664 char *realfld;
1665 int rv;
1666 NYD_ENTER;
1668 rv = 0;
1669 if (igta == NULL)
1670 goto jleave;
1671 rv = 1;
1672 if (igta == allignore)
1673 goto jleave;
1675 /* Lowercase it so that "Status" and "status" will hash to the same place */
1676 realfld = ac_alloc(fieldlen +1);
1677 i_strcpy(realfld, field, fieldlen +1);
1678 if (igta[1].i_count > 0)
1679 rv = !member(realfld, igta + 1);
1680 else
1681 rv = member(realfld, igta);
1682 ac_free(realfld);
1683 jleave:
1684 NYD_LEAVE;
1685 return rv;
1688 FL int
1689 member(char const *realfield, struct ignoretab *table)
1691 struct ignored *igp;
1692 int rv = 0;
1693 NYD_ENTER;
1695 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
1696 if (*igp->i_field == *realfield && !strcmp(igp->i_field, realfield)) {
1697 rv = 1;
1698 break;
1700 NYD_LEAVE;
1701 return rv;
1704 FL char const *
1705 fakefrom(struct message *mp)
1707 char const *name;
1708 NYD_ENTER;
1710 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
1711 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
1712 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
1713 * RFC 4155 however requires a RFC 5322 (2822) conforming
1714 * "addr-spec", but we simply can't provide that */
1715 name = "MAILER-DAEMON";
1716 NYD_LEAVE;
1717 return name;
1720 FL char const *
1721 fakedate(time_t t)
1723 char *cp, *cq;
1724 NYD_ENTER;
1726 cp = ctime(&t);
1727 for (cq = cp; *cq != '\0' && *cq != '\n'; ++cq)
1729 *cq = '\0';
1730 cp = savestr(cp);
1731 NYD_LEAVE;
1732 return cp;
1735 #ifdef HAVE_IMAP_SEARCH
1736 FL time_t
1737 unixtime(char const *fromline)
1739 char const *fp;
1740 char *xp;
1741 time_t t;
1742 int i, year, month, day, hour, minute, second, tzdiff;
1743 struct tm *tmptr;
1744 NYD2_ENTER;
1746 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
1748 fp -= 24;
1749 if (PTR2SIZE(fp - fromline) < 7)
1750 goto jinvalid;
1751 if (fp[3] != ' ')
1752 goto jinvalid;
1753 for (i = 0;;) {
1754 if (!strncmp(fp + 4, month_names[i], 3))
1755 break;
1756 if (month_names[++i][0] == '\0')
1757 goto jinvalid;
1759 month = i + 1;
1760 if (fp[7] != ' ')
1761 goto jinvalid;
1762 day = strtol(fp + 8, &xp, 10);
1763 if (*xp != ' ' || xp != fp + 10)
1764 goto jinvalid;
1765 hour = strtol(fp + 11, &xp, 10);
1766 if (*xp != ':' || xp != fp + 13)
1767 goto jinvalid;
1768 minute = strtol(fp + 14, &xp, 10);
1769 if (*xp != ':' || xp != fp + 16)
1770 goto jinvalid;
1771 second = strtol(fp + 17, &xp, 10);
1772 if (*xp != ' ' || xp != fp + 19)
1773 goto jinvalid;
1774 year = strtol(fp + 20, &xp, 10);
1775 if (xp != fp + 24)
1776 goto jinvalid;
1777 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
1778 goto jinvalid;
1779 tzdiff = t - mktime(gmtime(&t));
1780 tmptr = localtime(&t);
1781 if (tmptr->tm_isdst > 0)
1782 tzdiff += 3600;
1783 t -= tzdiff;
1784 jleave:
1785 NYD2_LEAVE;
1786 return t;
1787 jinvalid:
1788 t = n_time_epoch();
1789 goto jleave;
1791 #endif /* HAVE_IMAP_SEARCH */
1793 FL time_t
1794 rfctime(char const *date)
1796 char const *cp = date;
1797 char *x;
1798 time_t t;
1799 int i, year, month, day, hour, minute, second;
1800 NYD2_ENTER;
1802 if ((cp = nexttoken(cp)) == NULL)
1803 goto jinvalid;
1804 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
1805 cp[3] == ',') {
1806 if ((cp = nexttoken(&cp[4])) == NULL)
1807 goto jinvalid;
1809 day = strtol(cp, &x, 10); /* XXX strtol */
1810 if ((cp = nexttoken(x)) == NULL)
1811 goto jinvalid;
1812 for (i = 0;;) {
1813 if (!strncmp(cp, month_names[i], 3))
1814 break;
1815 if (month_names[++i][0] == '\0')
1816 goto jinvalid;
1818 month = i + 1;
1819 if ((cp = nexttoken(&cp[3])) == NULL)
1820 goto jinvalid;
1821 /* RFC 5322, 4.3:
1822 * Where a two or three digit year occurs in a date, the year is to be
1823 * interpreted as follows: If a two digit year is encountered whose
1824 * value is between 00 and 49, the year is interpreted by adding 2000,
1825 * ending up with a value between 2000 and 2049. If a two digit year
1826 * is encountered with a value between 50 and 99, or any three digit
1827 * year is encountered, the year is interpreted by adding 1900 */
1828 year = strtol(cp, &x, 10); /* XXX strtol */
1829 i = (int)PTR2SIZE(x - cp);
1830 if (i == 2 && year >= 0 && year <= 49)
1831 year += 2000;
1832 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
1833 year += 1900;
1834 if ((cp = nexttoken(x)) == NULL)
1835 goto jinvalid;
1836 hour = strtol(cp, &x, 10); /* XXX strtol */
1837 if (*x != ':')
1838 goto jinvalid;
1839 cp = &x[1];
1840 minute = strtol(cp, &x, 10);
1841 if (*x == ':') {
1842 cp = x + 1;
1843 second = strtol(cp, &x, 10);
1844 } else
1845 second = 0;
1847 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
1848 goto jinvalid;
1849 if ((cp = nexttoken(x)) != NULL) {
1850 char buf[3];
1851 int sign = 1;
1853 switch (*cp) {
1854 case '+':
1855 sign = -1;
1856 /* FALLTHRU */
1857 case '-':
1858 ++cp;
1859 break;
1861 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
1862 digitchar(cp[3])) {
1863 long tadj;
1864 buf[2] = '\0';
1865 buf[0] = cp[0];
1866 buf[1] = cp[1];
1867 tadj = strtol(buf, NULL, 10) * 3600;/*XXX strtrol*/
1868 buf[0] = cp[2];
1869 buf[1] = cp[3];
1870 tadj += strtol(buf, NULL, 10) * 60; /* XXX strtol*/
1871 if (sign < 0)
1872 tadj = -tadj;
1873 t += tadj;
1875 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
1876 * TODO once again, Christos Zoulas and NetBSD Mail have done
1877 * TODO a really good job already, but using strptime(3), which
1878 * TODO is not portable. Nonetheless, WE must improve, not
1879 * TODO at last because we simply ignore obsolete timezones!!
1880 * TODO See RFC 5322, 4.3! */
1882 jleave:
1883 NYD2_LEAVE;
1884 return t;
1885 jinvalid:
1886 t = 0;
1887 goto jleave;
1890 #define is_leapyear(Y) ((((Y) % 100 ? (Y) : (Y) / 100) & 3) == 0)
1892 FL time_t
1893 combinetime(int year, int month, int day, int hour, int minute, int second)
1895 time_t t;
1896 NYD2_ENTER;
1898 if (second < 0 || minute < 0 || hour < 0 || day < 1) {
1899 t = (time_t)-1;
1900 goto jleave;
1903 t = second + minute * 60 + hour * 3600 + (day - 1) * 86400;
1904 if (month > 1)
1905 t += 86400 * 31;
1906 if (month > 2)
1907 t += 86400 * (is_leapyear(year) ? 29 : 28);
1908 if (month > 3)
1909 t += 86400 * 31;
1910 if (month > 4)
1911 t += 86400 * 30;
1912 if (month > 5)
1913 t += 86400 * 31;
1914 if (month > 6)
1915 t += 86400 * 30;
1916 if (month > 7)
1917 t += 86400 * 31;
1918 if (month > 8)
1919 t += 86400 * 31;
1920 if (month > 9)
1921 t += 86400 * 30;
1922 if (month > 10)
1923 t += 86400 * 31;
1924 if (month > 11)
1925 t += 86400 * 30;
1926 year -= 1900;
1927 t += (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
1928 ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
1929 jleave:
1930 NYD2_LEAVE;
1931 return t;
1934 FL void
1935 substdate(struct message *m)
1937 char const *cp;
1938 NYD_ENTER;
1940 /* Determine the date to print in faked 'From ' lines. This is traditionally
1941 * the date the message was written to the mail file. Try to determine this
1942 * using RFC message header fields, or fall back to current time */
1943 if ((cp = hfield1("received", m)) != NULL) {
1944 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
1946 ++cp;
1947 while (alnumchar(*cp));
1949 if (cp && *++cp)
1950 m->m_time = rfctime(cp);
1952 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
1953 if ((cp = hfield1("date", m)) != NULL)
1954 m->m_time = rfctime(cp);
1956 if (m->m_time == 0 || m->m_time > time_current.tc_time)
1957 m->m_time = time_current.tc_time;
1958 NYD_LEAVE;
1961 FL void
1962 setup_from_and_sender(struct header *hp)
1964 char const *addr;
1965 struct name *np;
1966 NYD_ENTER;
1968 /* If -t parsed or composed From: then take it. With -t we otherwise
1969 * want -r to be honoured in favour of *from* in order to have
1970 * a behaviour that is compatible with what users would expect from e.g.
1971 * postfix(1) */
1972 if ((np = hp->h_from) != NULL ||
1973 ((pstate & PS_t_FLAG) && (np = option_r_arg) != NULL)) {
1975 } else if ((addr = myaddrs(hp)) != NULL)
1976 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
1977 hp->h_from = np;
1979 if ((np = hp->h_sender) != NULL) {
1981 } else if ((addr = ok_vlook(sender)) != NULL)
1982 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
1983 hp->h_sender = np;
1985 NYD_LEAVE;
1988 FL struct name const *
1989 check_from_and_sender(struct name const *fromfield,
1990 struct name const *senderfield)
1992 struct name const *rv = NULL;
1993 NYD_ENTER;
1995 if (senderfield != NULL) {
1996 if (senderfield->n_flink != NULL) {
1997 n_err(_("The \"Sender:\" field may contain only one address\n"));
1998 goto jleave;
2000 rv = senderfield;
2003 if (fromfield != NULL) {
2004 if (fromfield->n_flink != NULL && senderfield == NULL) {
2005 n_err(_("A \"Sender:\" field is required with multiple "
2006 "addresses in \"From:\" field\n"));
2007 goto jleave;
2009 if (rv == NULL)
2010 rv = fromfield;
2013 if (rv == NULL)
2014 rv = (struct name*)0x1;
2015 jleave:
2016 NYD_LEAVE;
2017 return rv;
2020 #ifdef HAVE_OPENSSL
2021 FL char *
2022 getsender(struct message *mp)
2024 char *cp;
2025 struct name *np;
2026 NYD_ENTER;
2028 if ((cp = hfield1("from", mp)) == NULL ||
2029 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
2030 cp = NULL;
2031 else
2032 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
2033 NYD_LEAVE;
2034 return cp;
2036 #endif
2038 FL int
2039 grab_headers(struct header *hp, enum gfield gflags, int subjfirst)
2041 /* TODO grab_headers: again, check counts etc. against RFC;
2042 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2043 int errs;
2044 int volatile comma;
2045 NYD_ENTER;
2047 errs = 0;
2048 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
2050 if (gflags & GTO)
2051 hp->h_to = grab_names("To: ", hp->h_to, comma, GTO | GFULL);
2052 if (subjfirst && (gflags & GSUBJECT))
2053 hp->h_subject = n_lex_input_cp_addhist("Subject: ", hp->h_subject, TRU1);
2054 if (gflags & GCC)
2055 hp->h_cc = grab_names("Cc: ", hp->h_cc, comma, GCC | GFULL);
2056 if (gflags & GBCC)
2057 hp->h_bcc = grab_names("Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
2059 if (gflags & GEXTRA) {
2060 if (hp->h_from == NULL)
2061 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
2062 hp->h_from = grab_names("From: ", hp->h_from, comma,
2063 GEXTRA | GFULL | GFULLEXTRA);
2064 if (hp->h_replyto == NULL)
2065 hp->h_replyto = lextract(ok_vlook(replyto), GEXTRA | GFULL);
2066 hp->h_replyto = grab_names("Reply-To: ", hp->h_replyto, comma,
2067 GEXTRA | GFULL);
2068 if (hp->h_sender == NULL)
2069 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
2070 hp->h_sender = grab_names("Sender: ", hp->h_sender, comma,
2071 GEXTRA | GFULL);
2074 if (!subjfirst && (gflags & GSUBJECT))
2075 hp->h_subject = n_lex_input_cp_addhist("Subject: ", hp->h_subject, TRU1);
2077 NYD_LEAVE;
2078 return errs;
2081 FL bool_t
2082 header_match(struct message *mp, struct search_expr const *sep)
2084 struct str in, out;
2085 FILE *ibuf;
2086 int lc;
2087 size_t linesize = 0; /* TODO line pool */
2088 char *linebuf = NULL, *colon;
2089 bool_t rv = FAL0;
2090 NYD_ENTER;
2092 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2093 goto jleave;
2094 if ((lc = mp->m_lines - 1) < 0)
2095 goto jleave;
2097 if ((mp->m_flag & MNOFROM) == 0 &&
2098 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2099 goto jleave;
2100 while (lc > 0) {
2101 if (gethfield(ibuf, &linebuf, &linesize, lc, &colon) <= 0)
2102 break;
2103 if (blankchar(*++colon))
2104 ++colon;
2105 in.l = strlen(in.s = colon);
2106 mime_fromhdr(&in, &out, TD_ICONV);
2107 #ifdef HAVE_REGEX
2108 if (sep->ss_sexpr == NULL)
2109 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
2110 else
2111 #endif
2112 rv = substr(out.s, sep->ss_sexpr);
2113 free(out.s);
2114 if (rv)
2115 break;
2118 jleave:
2119 if (linebuf != NULL)
2120 free(linebuf);
2121 NYD_LEAVE;
2122 return rv;
2125 /* s-it-mode */