From 0ede309c2a74e302ba5065318abacd0fa5cc5f4a Mon Sep 17 00:00:00 2001 From: "Steffen (Daode) Nurpmeso" Date: Mon, 26 Dec 2016 19:42:03 +0100 Subject: [PATCH] Rudely fix the RFC 5322 dot-atom, the "Dr. Problem" (Dr. Werner Fink) --- head.c | 352 ++++++++++++++++++++++++++++++++++++++++++++++++------------ mime.c | 245 ++++++++++++++++++++++++++++++------------ nail.h | 17 +-- nailfuns.h | 8 +- nam_a_grp.c | 38 +++++-- 5 files changed, 498 insertions(+), 162 deletions(-) diff --git a/head.c b/head.c index 6e1b6b12..5648f6ea 100644 --- a/head.c +++ b/head.c @@ -1,5 +1,6 @@ /*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Routines for processing and detecting headlines. + *@ TODO Mostly a hackery, we need RFC compliant parsers instead. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2016 Steffen (Daode) Nurpmeso . @@ -102,14 +103,14 @@ static void a_head_jdn_to_gregorian(size_t jdn, * If an error occurs before Unicode information is available, revert the IDNA * error to a normal CHAR one so that the error message doesn't talk Unicode */ #ifdef HAVE_IDNA -static struct addrguts * _idna_apply(struct addrguts *agp); +static struct n_addrguts *a_head_idna_apply(struct n_addrguts *agp); #endif /* Classify and check a (possibly skinned) header body according to RFC * *addr-spec* rules; if it (is assumed to has been) skinned it may however be * also a file or a pipe command, so check that first, then. * Otherwise perform content checking and isolate the domain part (for IDNA) */ -static int _addrspec_check(int doskin, struct addrguts *agp); +static bool_t a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned); /* Return the next header field found in the given message. * Return >= 0 if something found, < 0 elsewise. @@ -296,8 +297,8 @@ a_head_jdn_to_gregorian(size_t jdn, ui32_t *yp, ui32_t *mp, ui32_t *dp){ #ifdef HAVE_IDNA # if HAVE_IDNA == HAVE_IDNA_LIBIDNA -static struct addrguts * -_idna_apply(struct addrguts *agp) +static struct n_addrguts * +a_head_idna_apply(struct n_addrguts *agp) { char *idna_utf8, *idna_ascii, *cs; size_t sz, i; @@ -314,13 +315,13 @@ _idna_apply(struct addrguts *agp) if (!(options & OPT_UNICODE)) { char const *tcs = ok_vlook(ttycharset); idna_ascii = idna_utf8; - idna_utf8 = stringprep_convert(idna_ascii, "UTF-8", tcs); + idna_utf8 = stringprep_convert(idna_ascii, "utf-8", tcs); i = (idna_utf8 == NULL && errno == EINVAL); ac_free(idna_ascii); if (idna_utf8 == NULL) { if (i) - n_err(_("Cannot convert from %s to %s\n"), tcs, "UTF-8"); + n_err(_("Cannot convert from %s to %s\n"), tcs, "utf-8"); agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR; goto jleave; } @@ -357,8 +358,8 @@ jleave: } # elif HAVE_IDNA == HAVE_IDNA_IDNKIT /* IDNA==LIBIDNA */ -static struct addrguts * -_idna_apply(struct addrguts *agp) +static struct n_addrguts * +a_head_idna_apply(struct n_addrguts *agp) { char *idna_in, *idna_out, *cs; size_t sz, i; @@ -416,13 +417,13 @@ jleave: # endif /* IDNA==IDNKIT */ #endif /* HAVE_IDNA */ -static int -_addrspec_check(int skinned, struct addrguts *agp) +static bool_t +a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned) { char *addr, *p; bool_t in_quote; ui8_t in_domain, hadat; - union {char c; unsigned char u;} c; + union {char c; unsigned char u; ui32_t ui32;} c; #ifdef HAVE_IDNA ui8_t use_idna; #endif @@ -470,6 +471,7 @@ jaddr_check: in_quote = FAL0; in_domain = hadat = 0; + /* TODO addrspec_check: we need a real RFC 5322 parser! */ for (p = addr; (c.c = *p++) != '\0';) { if (c.c == '"') { in_quote = !in_quote; @@ -501,8 +503,8 @@ jaddr_check: in_domain = (*p == '[') ? 2 : 1; continue; } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' || - c.c == ',' || c.c == ';' || c.c == ':' || c.c == '\\' || - c.c == '[' || c.c == ']') + c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' || + c.c == '\\' || c.c == ',') break; hadat = 0; } @@ -513,11 +515,214 @@ jaddr_check: if (!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR)) agp->ag_n_flags |= NAME_ADDRSPEC_ISNAME; + else{ + /* If we seem to know that this is an address. Perform some simple checks + * for dot-atom as of RFC 5322 and ensure we fix that "Dr. problem". + * TODO We need a real RFC 5322 parser which produces tokens, to store them + * TODO in a list including typeattr, as in atom-text, dot-atom-text, + * TODO quote, comment address, so then we could simply join as many + * TODO consecutive -text tokens and convert them to quote as possible + * TODO For now bad and expensive; wrong for some comments in tokens. + * TODO In fact i haven't read RFC 5322 so much, esp. before doing this */ + struct n_string ost, *ostp; + char lastc; + size_t rangestart, lastpoi; + char const *cp, *cpmax, *xp; #ifdef HAVE_IDNA - if (use_idna == 2) - agp = _idna_apply(agp); + if(use_idna == 2) + agp = a_head_idna_apply(agp); #endif + + ostp = n_string_creat_auto(&ost); + if((c.ui32 = agp->ag_ilen) <= UI32_MAX >> 1) + ostp = n_string_reserve(ostp, c.ui32 <<= 1); + + hadat = FAL0; + cp = agp->ag_input; + if((c.ui32 = agp->ag_iaddr_start) > 0) + --c.ui32; + cpmax = &cp[c.ui32]; + rangestart = 0; + + while(cp < cpmax && blankchar(*cp)) + ++cp; + + lastc = '\0'; +jdotatom_redo: + for(lastpoi = UIZ_MAX; cp < cpmax;){ + switch((c.c = *cp)){ + case '(': +jdotatom_comment: + lastc = 0; + if((c.ui32 = ostp->s_len) > 0){ + while(c.ui32 > 0 && blankchar(ostp->s_dat[c.ui32 - 1])) + --c.ui32; + ostp = n_string_trunc(ostp, c.ui32); + + if(c.ui32 > 0){ + --c.ui32; + /* Can we join two comments? */ + if(ostp->s_dat[c.ui32] == ')'){ + ostp->s_dat[c.ui32] = ' '; + lastc = 1; + } + /* Otherwise ensure it is separated! */ + else if(!blankchar(ostp->s_dat[c.ui32])) + ostp = n_string_push_c(ostp, ' '); + } + } + if(!lastc) + ostp = n_string_push_c(ostp, '('); + + xp = skip_comment(++cp); + c.ui32 = (ui32_t)PTR2SIZE(xp - cp); + if(c.ui32 > 0){ + if(xp[-1] == ')') + --c.ui32; + /* Run out of buffer while searching for comment close, this is + * artificial and we strip all blanks at EOS */ + else while(blankchar(xp[-1])){ + --xp; + if(--c.ui32 == 0) + break; + } + } + ostp = n_string_push_buf(ostp, cp, c.ui32); + + ostp = n_string_push_c(ostp, ')'); + lastpoi = ostp->s_len - 1; + lastc = '\0'; + cp = xp; + break; + case '"': + lastc = 0; +jdotatom_quote: + xp = ++cp; + if((c.ui32 = ostp->s_len) > 0){ + while(c.ui32 > 0 && blankchar(ostp->s_dat[c.ui32 - 1])) + --c.ui32; + ostp = n_string_trunc(ostp, c.ui32); + + if(c.ui32 > 0){ + --c.ui32; + /* Can we join two quotes? */ + if(ostp->s_dat[c.ui32] == '"'){ + ostp->s_dat[c.ui32] = ' '; + lastc = 1; + } + /* Otherwise ensure it is separated! */ + else if(!blankchar(ostp->s_dat[c.ui32])) + ostp = n_string_push_c(ostp, ' '); + } + } + if(!lastc) + ostp = n_string_push_c(ostp, '"'); + + for(; xp < cpmax; ++xp){ + if((c.c = *xp) == '"') + break; + if(c.c == '\\' && xp[1] != '\0') + ++xp; + } + c.ui32 = (ui32_t)PTR2SIZE(xp - cp); + if(c.ui32 > 0){ + if(xp != cpmax && *xp == '"') + ++xp; + /* Run out of buffer while searching for quote close, this is + * artificial and we strip all blanks at EOS */ + else while(blankchar(xp[-1])){ + --xp; + if(--c.ui32 == 0) + break; + } + } + ostp = n_string_push_buf(ostp, cp, c.ui32); + + ostp = n_string_push_c(ostp, '"'); + lastpoi = ostp->s_len - 1; + lastc = '\0'; + cp = xp; + break; + case '.': + if(lastpoi != UIZ_MAX){ + if(ostp->s_dat[lastpoi] == '"') + /* Simply join backward to a yet existing quote */ + ostp = n_string_cut(ostp, lastpoi, 1); + }else + /* Otherwise convert anything before to a quote */ + ostp = n_string_insert_c(ostp, rangestart, '"'); + + for(xp = cp; cp < cpmax; ++cp){ + /* If we reach another quote, join forward */ + if((c.c = *cp) == '"'){ + lastc = 1; + goto jdotatom_quote; + } + else if(c.c == '('){ + /* End the quote before the whitespace before the comment */ + for(c.ui32 = 0; blankchar(cp[-1 - c.ui32]); ++c.ui32) + ; + if(c.ui32 > 0) + ostp = n_string_trunc(ostp, ostp->s_len - c.ui32); + ostp = n_string_push_c(ostp, '"'); + goto jdotatom_comment; + } + ostp = n_string_push_c(ostp, c.c); + } + + /* Since we have created this quote it would be false to let it end + * in a series of whitespace */ + for(c.ui32 = ostp->s_len; + c.ui32 > 0 && blankchar(ostp->s_dat[c.ui32 - 1]); --c.ui32) + ; + ostp = n_string_trunc(ostp, c.ui32); + + ostp = n_string_push_c(ostp, '"'); + lastpoi = ostp->s_len - 1; + lastc = '\0'; + break; + default: + if(lastc == '\0' || !blankchar(c.c) || !blankchar(lastc)) + ostp = n_string_push_c(ostp, lastc = c.c); + ++cp; + break; + } + } + + if(hadat == FAL0){ + hadat = TRU1; + if((c.ui32 = ostp->s_len) > 0 && !blankchar(ostp->s_dat[c.ui32 - 1])){ + ostp = n_string_push_c(ostp, ' '); + ++c.ui32; + } + if(c.ui32 != 0) + ++c.ui32; + agp->ag_iaddr_start = c.ui32; + cp = &agp->ag_input[agp->ag_iaddr_aend]; + if(cp != &agp->ag_input[agp->ag_ilen]) + ++cp; + c.ui32 = (ui32_t)PTR2SIZE(cp - cpmax); + ostp = n_string_push_buf(ostp, cpmax, c.ui32); + agp->ag_iaddr_aend = ostp->s_len - 1; + cpmax = &agp->ag_input[agp->ag_ilen]; + + c.ui32 = (ui32_t)PTR2SIZE(cpmax - cp); + if(c.ui32 > 0){ + ostp = n_string_push_c(ostp, lastc = ' '); + rangestart = ostp->s_len; + goto jdotatom_redo; + } + }else if((c.ui32 = ostp->s_len) > 0){ + while(blankchar(ostp->s_dat[c.ui32 - 1])) + --c.ui32; + ostp = n_string_trunc(ostp, c.ui32); + } + + agp->ag_input = n_string_cp(ostp); + agp->ag_ilen = ostp->s_len; + ostp = n_string_drop_ownership(ostp); + } jleave: NYD_LEAVE; return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) != 0); @@ -1309,12 +1514,13 @@ FL si8_t is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm) { char cbuf[sizeof "'\\U12340'"]; - enum expand_addr_flags eaf; char const *cs; int f; si8_t rv; + enum expand_addr_flags eaf; NYD_ENTER; + eaf = expandaddr_to_eaf(); f = np->n_flags; if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) { @@ -1349,8 +1555,6 @@ is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm) if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE))) goto jleave; - eaf = expandaddr_to_eaf(); - if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) { if (eaf & EAF_FAIL) rv = -rv; @@ -1405,41 +1609,45 @@ jleave: FL char * skin(char const *name) { - struct addrguts ag; - char *ret = NULL; + struct n_addrguts ag; + char *rv; NYD_ENTER; - if (name != NULL) { - addrspec_with_guts(1, name, &ag); - ret = ag.ag_skinned; - if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) - ret = savestrbuf(ret, ag.ag_slen); - } + if(name != NULL){ + name = n_addrspec_with_guts(&ag,name, TRU1); + rv = ag.ag_skinned; + if(!(ag.ag_n_flags & NAME_NAME_SALLOC)) + rv = savestrbuf(rv, ag.ag_slen); + }else + rv = NULL; NYD_LEAVE; - return ret; + return rv; } /* TODO addrspec_with_guts: RFC 5322 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */ -FL int -addrspec_with_guts(int doskin, char const *name, struct addrguts *agp) -{ +FL char const * +n_addrspec_with_guts(struct n_addrguts *agp, char const *name, bool_t doskin){ char const *cp; - char *cp2, *bufend, *nbuf, c, gotlt, gotaddr, lastsp; - int rv = 1; + char *cp2, *bufend, *nbuf, c; + enum{ + a_NONE, + a_GOTLT = 1<<0, + a_GOTADDR = 1<<1, + a_GOTSPACE = 1<<2, + a_LASTSP = 1<<3 + } flags; NYD_ENTER; memset(agp, 0, sizeof *agp); - if ((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0) { + if((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0){ agp->ag_skinned = n_UNCONST(n_empty); /* ok: NAME_SALLOC is not set */ agp->ag_slen = 0; agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED; NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0); goto jleave; - } - - if (!doskin || !anyof(name, "(< ")) { + }else if(!doskin){ /*agp->ag_iaddr_start = 0;*/ agp->ag_iaddr_aend = agp->ag_ilen; agp->ag_skinned = n_UNCONST(name); /* (NAME_SALLOC not set) */ @@ -1448,28 +1656,26 @@ addrspec_with_guts(int doskin, char const *name, struct addrguts *agp) goto jcheck; } - /* Something makes us think we have to perform the skin operation */ - nbuf = ac_alloc(agp->ag_ilen + 1); + flags = a_NONE; + nbuf = n_lofi_alloc(agp->ag_ilen +1); /*agp->ag_iaddr_start = 0;*/ cp2 = bufend = nbuf; - gotlt = gotaddr = lastsp = 0; - for (cp = name++; (c = *cp++) != '\0'; ) { + for(cp = name++; (c = *cp++) != '\0';){ switch (c) { case '(': cp = skip_comment(cp); - lastsp = 0; + flags &= ~a_LASTSP; break; case '"': - /* Start of a "quoted-string". - * Copy it in its entirety */ + /* Start of a "quoted-string". Copy it in its entirety */ /* XXX RFC: quotes are "semantically invisible" * XXX But it was explicitly added (Changelog.Heirloom, * XXX [9.23] released 11/15/00, "Do not remove quotes * XXX when skinning names"? No more info.. */ *cp2++ = c; while ((c = *cp) != '\0') { /* TODO improve */ - cp++; + ++cp; if (c == '"') { *cp2++ = c; break; @@ -1478,15 +1684,15 @@ addrspec_with_guts(int doskin, char const *name, struct addrguts *agp) *cp2++ = c; else if ((c = *cp) != '\0') { *cp2++ = c; - cp++; + ++cp; } } - lastsp = 0; + flags &= ~a_LASTSP; break; case ' ': case '\t': - if (gotaddr == 1) { - gotaddr = 2; + if((flags & (a_GOTADDR | a_GOTSPACE)) == a_GOTADDR){ + flags |= a_GOTADDR | a_GOTSPACE; agp->ag_iaddr_aend = PTR2SIZE(cp - name); } if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2])) @@ -1494,69 +1700,73 @@ addrspec_with_guts(int doskin, char const *name, struct addrguts *agp) else if (cp[0] == '@' && blankchar(cp[1])) cp += 2, *cp2++ = '@'; else - lastsp = 1; + flags |= a_LASTSP; break; case '<': agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1)); cp2 = bufend; - gotlt = gotaddr = 1; - lastsp = 0; + flags &= ~(a_GOTSPACE | a_LASTSP); + flags |= a_GOTLT | a_GOTADDR; break; case '>': - if (gotlt) { + if(flags & a_GOTLT){ /* (_addrspec_check() verifies these later!) */ + flags &= ~(a_GOTLT | a_LASTSP); agp->ag_iaddr_aend = PTR2SIZE(cp - name); - gotlt = 0; - while ((c = *cp) != '\0' && c != ',') { - cp++; + + /* Skip over the entire remaining field */ + while((c = *cp) != '\0' && c != ','){ + ++cp; if (c == '(') cp = skip_comment(cp); else if (c == '"') while ((c = *cp) != '\0') { - cp++; + ++cp; if (c == '"') break; if (c == '\\' && *cp != '\0') ++cp; } } - lastsp = 0; break; } - /* FALLTRHOUGH */ + /* FALLTHRU */ default: - if (lastsp) { - lastsp = 0; - if (gotaddr) + if(flags & a_LASTSP){ + flags &= ~a_LASTSP; + if(flags & a_GOTADDR) *cp2++ = ' '; } *cp2++ = c; - if (c == ',') { - if (!gotlt) { + if(c == ','){ + if(!(flags & a_GOTLT)){ *cp2++ = ' '; - for (; blankchar(*cp); ++cp) + for(; blankchar(*cp); ++cp) ; - lastsp = 0; + flags &= ~a_LASTSP; bufend = cp2; } - } else if (!gotaddr) { - gotaddr = 1; + }else if(!(flags & a_GOTADDR)){ + flags |= a_GOTADDR; agp->ag_iaddr_start = PTR2SIZE(cp - name); } } } + --name; agp->ag_slen = PTR2SIZE(cp2 - nbuf); if (agp->ag_iaddr_aend == 0) agp->ag_iaddr_aend = agp->ag_ilen; - agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen); - ac_free(nbuf); + n_lofi_free(nbuf); agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED; jcheck: - rv = _addrspec_check(doskin, agp); + if(a_head_addrspec_check(agp, doskin) <= 0) + name = NULL; + else + name = agp->ag_input; jleave: NYD_LEAVE; - return rv; + return name; } FL char * diff --git a/mime.c b/mime.c index 7e0a9727..13a8c3c9 100644 --- a/mime.c +++ b/mime.c @@ -1,5 +1,6 @@ /*@ S-nail - a mail user agent derived from Berkeley Mail. *@ MIME support functions. + *@ TODO Complete rewrite. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2016 Steffen (Daode) Nurpmeso . @@ -43,6 +44,13 @@ # include "nail.h" #endif +/* Don't ask, but it keeps body and soul together */ +enum a_mime_structure_hack{ + a_MIME_SH_NONE, + a_MIME_SH_COMMENT, + a_MIME_SH_QUOTE +}; + static char *_cs_iter_base, *_cs_iter; #ifdef HAVE_ICONV # define _CS_ITER_GET() \ @@ -62,15 +70,22 @@ static bool_t _name_highbit(struct name *np); static ssize_t _fwrite_td(struct str const *input, enum tdflags flags, struct str *outrest, struct quoteflt *qf); -/* Convert header fields to RFC 1522 format and write to the file fo */ -static ssize_t mime_write_tohdr(struct str *in, FILE *fo); +/* Convert header fields to RFC 2047 format and write to the file fo */ +static ssize_t mime_write_tohdr(struct str *in, FILE *fo, + size_t *colp, enum a_mime_structure_hack msh); /* Write len characters of the passed string to the passed file, doing charset * and header conversion */ -static ssize_t convhdra(char const *str, size_t len, FILE *fp); /* Write an address to a header field */ -static ssize_t mime_write_tohdr_a(struct str *in, FILE *f); +static ssize_t mime_write_tohdr_a(struct str *in, FILE *f, + size_t *colp); +#ifdef HAVE_ICONV +static ssize_t a_mime__convhdra(struct str *inp, FILE *fp, size_t *colp, + enum a_mime_structure_hack msh); +#else +# define a_mime__convhdra(S,F,C,MSH) mime_write_tohdr(S, F, C, MSH) +#endif /* Append to buf, handling resizing */ static void _append_str(char **buf, size_t *sz, size_t *pos, @@ -252,12 +267,23 @@ jleave: } static ssize_t -mime_write_tohdr(struct str *in, FILE *fo) +mime_write_tohdr(struct str *in, FILE *fo, size_t *colp, + enum a_mime_structure_hack msh) { /* TODO mime_write_tohdr(): we don't know the name of our header->maxcol.. * TODO MIME/send layer rewrite: more available state!! * TODO Because of this we cannot make a difference in between structured * TODO and unstructured headers (RFC 2047, 5. (2)) + * TODO This means, e.g., that this gets called multiple times for a + * TODO structured header and always starts thinking it is at column 0. + * TODO I.e., it may get called for only the content of a comment etc., + * TODO not knowing anything of its context. + * TODO Instead we should have a list of header body content tokens, + * TODO convert them, and then dump the converted tokens, breaking lines. + * TODO I.e., get rid of convhdra, mime_write_tohdr_a and such... + * TODO Somewhen, the following should produce smooth stuff: + * TODO ' "Hallo\"," Dr. Backe "Bl\"ö\"d" (Gell) + * TODO "Nochm\"a\"l"(Dümm)' * TODO NOT MULTIBYTE SAFE IF AN ENCODED WORD HAS TO BE SPLITTED! * TODO To be better we had to mbtowc_l() (non-std! and no locale!!) and * TODO work char-wise! -> S-CText.. @@ -271,26 +297,28 @@ mime_write_tohdr(struct str *in, FILE *fo) * XXX MIME_LINELEN unless an RFC 2047 encoding was actually used */ _MAXCOL = MIME_LINELEN_RFC2047 }; + + struct str cout, cin; enum { _FIRST = 1<<0, /* Nothing written yet, start of string */ - _NO_QP = 1<<1, /* No quoted-printable allowed */ - _NO_B64 = 1<<2, /* Ditto, base64 */ - _ENC_LAST = 1<<3, /* Last round generated encoded word */ - _SHOULD_BEE = 1<<4, /* Avoid lines longer than SHOULD via encoding */ - _RND_SHIFT = 5, + _MSH_NOTHING = 1<<1, /* Now, really: nothing at all has been written */ + _NO_QP = 1<<2, /* No quoted-printable allowed */ + _NO_B64 = 1<<3, /* Ditto, base64 */ + _ENC_LAST = 1<<4, /* Last round generated encoded word */ + _SHOULD_BEE = 1<<5, /* Avoid lines longer than SHOULD via encoding */ + _RND_SHIFT = 6, _RND_MASK = (1<<_RND_SHIFT) - 1, _SPACE = 1<<(_RND_SHIFT+1), /* Leading whitespace */ _8BIT = 1<<(_RND_SHIFT+2), /* High bit set */ _ENCODE = 1<<(_RND_SHIFT+3), /* Need encoding */ _ENC_B64 = 1<<(_RND_SHIFT+4), /* - let it be base64 */ _OVERLONG = 1<<(_RND_SHIFT+5) /* Temporarily rised limit */ - } flags = _FIRST; - - struct str cout, cin; + } flags; char const *cset7, *cset8, *wbot, *upper, *wend, *wcur; ui32_t cset7_len, cset8_len; size_t col, i, j; ssize_t sz; + NYD_ENTER; cout.s = NULL, cout.l = 0; @@ -299,6 +327,10 @@ mime_write_tohdr(struct str *in, FILE *fo) cset8 = _CS_ITER_GET(); /* TODO MIME/send layer: iter active? iter! else */ cset8_len = (ui32_t)strlen(cset8); + flags = _FIRST; + if(msh != a_MIME_SH_NONE) + flags |= _MSH_NOTHING; + /* RFC 1468, "MIME Considerations": * ISO-2022-JP may also be used in MIME Part 2 headers. The "B" * encoding should be used with ISO-2022-JP text. */ @@ -311,7 +343,9 @@ mime_write_tohdr(struct str *in, FILE *fo) wbot = in->s; upper = wbot + in->l; - col = sizeof("Mail-Followup-To: ") -1; /* dreadful thing */ + + if(colp == NULL || (col = *colp) == 0) + col = sizeof("Mail-Followup-To: ") -1; /* dreadful thing */ for (sz = 0; wbot < upper; flags &= ~_FIRST, wbot = wend) { flags &= _RND_MASK; @@ -376,6 +410,12 @@ j_beejump: ++sz; col = 0; } + if(flags & _MSH_NOTHING){ + flags &= ~_MSH_NOTHING; + putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo); + ++sz; + ++col; + } putc(' ', fo); ++sz; ++col; @@ -390,6 +430,12 @@ jnoenc_putws: jnoenc_retry: i = PTR2SIZE(wend - wbot); if (i + col <= (flags & _OVERLONG ? MIME_LINELEN_MAX : _MAXCOL)) { + if(flags & _MSH_NOTHING){ + flags &= ~_MSH_NOTHING; + putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo); + ++sz; + ++col; + } i = fwrite(wbot, sizeof *wbot, i, fo); sz += i; col += i; @@ -406,6 +452,12 @@ jnoenc_retry: putc(' ', fo); /* Bad standard: artificial data! */ sz += 2; col = 1; + if(flags & _MSH_NOTHING){ + flags &= ~_MSH_NOTHING; + putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo); + ++sz; + ++col; + } flags |= _OVERLONG; goto jnoenc_retry; } @@ -476,6 +528,12 @@ jenc_retry_same: * 998 characters long"), so we cannot use the _OVERLONG mechanism, * even though all tested mailers seem to support it */ if (i + col <= (/*flags & _OVERLONG ? MIME_LINELEN_MAX :*/ _MAXCOL)) { + if(flags & _MSH_NOTHING){ + flags &= ~_MSH_NOTHING; + putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo); + ++sz; + ++col; + } fprintf(fo, "%.1s=?%s?%c?%.*s?=", wcur, (flags & _8BIT ? cset8 : cset7), (flags & _ENC_B64 ? 'B' : 'Q'), @@ -535,94 +593,134 @@ jenc_retry_same: } } + if(!(flags & _MSH_NOTHING) && msh != a_MIME_SH_NONE){ + putc((msh == a_MIME_SH_COMMENT ? ')' : '"'), fo); + ++sz; + ++col; + } + if (cout.s != NULL) free(cout.s); - NYD_LEAVE; - return sz; -} -static ssize_t -convhdra(char const *str, size_t len, FILE *fp) -{ -#ifdef HAVE_ICONV - struct str ciconv; -#endif - struct str cin; - ssize_t ret = 0; - NYD_ENTER; - - cin.s = n_UNCONST(str); - cin.l = len; -#ifdef HAVE_ICONV - ciconv.s = NULL; - if (iconvd != (iconv_t)-1) { - ciconv.l = 0; - if(n_iconv_str(iconvd, n_ICONV_IGN_NOREVERSE, &ciconv, &cin, NULL) != 0){ - n_iconv_reset(iconvd); - goto jleave; - } - cin = ciconv; - } -#endif - ret = mime_write_tohdr(&cin, fp); -#ifdef HAVE_ICONV -jleave: - if (ciconv.s != NULL) - free(ciconv.s); -#endif + if(colp != NULL) + *colp = col; NYD_LEAVE; - return ret; + return sz; } static ssize_t -mime_write_tohdr_a(struct str *in, FILE *f) /* TODO error handling */ +mime_write_tohdr_a(struct str *in, FILE *f, size_t *colp) { + struct str xin; + size_t i; char const *cp, *lastcp; ssize_t sz, x; NYD_ENTER; in->s[in->l] = '\0'; lastcp = in->s; - if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) { - if ((sz = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0) + if((cp = routeaddr(in->s)) != NULL && cp > lastcp) { + xin.s = in->s; + xin.l = PTR2SIZE(cp - in->s); + if ((sz = mime_write_tohdr_a(&xin, f, colp)) < 0) goto jleave; + xin.s[xin.l] = '<'; lastcp = cp; } else { cp = in->s; sz = 0; } - for ( ; *cp != '\0'; ++cp) { - switch (*cp) { + for( ; *cp != '\0'; ++cp){ + switch(*cp){ case '(': - sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp + 1), f); + i = PTR2SIZE(cp - lastcp); + if(i > 0){ + if(fwrite(lastcp, 1, i, f) != i) + goto jerr; + sz += i; + } lastcp = ++cp; cp = skip_comment(cp); - if (--cp > lastcp) { - if ((x = convhdra(lastcp, PTR2SIZE(cp - lastcp), f)) < 0) { - sz = x; - goto jleave; - } + if(--cp > lastcp){ + i = PTR2SIZE(cp - lastcp); + xin.s = n_UNCONST(lastcp); + xin.l = i; + if ((x = a_mime__convhdra(&xin, f, colp, a_MIME_SH_COMMENT)) < 0) + goto jerr; sz += x; } - lastcp = cp; + lastcp = &cp[1]; break; case '"': - while (*cp) { - if (*++cp == '"') + i = PTR2SIZE(cp - lastcp); + if(i > 0){ + if(fwrite(lastcp, 1, i, f) != i) + goto jerr; + sz += i; + } + for(lastcp = ++cp; *cp != '\0'; ++cp){ + if(*cp == '"') break; - if (*cp == '\\' && cp[1] != '\0') + if(*cp == '\\' && cp[1] != '\0') ++cp; } + i = PTR2SIZE(cp - lastcp); + if(i > 0){ + xin.s = n_UNCONST(lastcp); + xin.l = i; + if((x = a_mime__convhdra(&xin, f, colp, a_MIME_SH_QUOTE)) < 0) + goto jerr; + sz += x; + } + ++sz; + lastcp = &cp[1]; break; } } - if (cp > lastcp) - sz += fwrite(lastcp, 1, PTR2SIZE(cp - lastcp), f); + + i = PTR2SIZE(cp - lastcp); + if(i > 0){ + if(fwrite(lastcp, 1, i, f) != i) + goto jerr; + sz += i; + } jleave: NYD_LEAVE; return sz; +jerr: + sz = -1; + goto jleave; +} + +#ifdef HAVE_ICONV +static ssize_t +a_mime__convhdra(struct str *inp, FILE *fp, size_t *colp, + enum a_mime_structure_hack msh){ + struct str ciconv; + ssize_t rv; + NYD_ENTER; + + rv = 0; + ciconv.s = NULL; + + if(iconvd != (iconv_t)-1){ + ciconv.l = 0; + if(n_iconv_str(iconvd, n_ICONV_IGN_NOREVERSE, &ciconv, inp, NULL) != 0){ + n_iconv_reset(iconvd); + goto jleave; + } + *inp = ciconv; + } + + rv = mime_write_tohdr(inp, fp, colp, msh); +jleave: + if(ciconv.s != NULL) + free(ciconv.s); + NYD_LEAVE; + return rv; } +#endif /* HAVE_ICONV */ static void _append_str(char **buf, size_t *sz, size_t *pos, char const *str, size_t len) @@ -1084,9 +1182,9 @@ mime_write(char const *ptr, size_t size, FILE *f, NYD_ENTER; dflags |= _TD_BUFCOPY; - in.s = n_UNCONST(ptr); in.l = size; + if(inrest != NULL && inrest->l > 0){ out.s = smalloc(inrest->l + size + 1); memcpy(out.s, inrest->s, inrest->l); @@ -1183,11 +1281,20 @@ jqpb64_enc: sz = quoteflt_push(qf, out.s, out.l); break; case CONV_TOHDR: - sz = mime_write_tohdr(&in, f); - break; - case CONV_TOHDR_A: - sz = mime_write_tohdr_a(&in, f); + sz = mime_write_tohdr(&in, f, NULL, a_MIME_SH_NONE); break; + case CONV_TOHDR_A:{ + size_t col; + + if(dflags & _TD_BUFCOPY){ + n_str_dup(&out, &in); + in = out; + out.s = NULL; + dflags &= ~_TD_BUFCOPY; + } + col = 0; + sz = mime_write_tohdr_a(&in, f, &col); + } break; default: sz = _fwrite_td(&in, dflags, NULL, qf); break; diff --git a/nail.h b/nail.h index d48d94c5..1011e620 100644 --- a/nail.h +++ b/nail.h @@ -2321,14 +2321,15 @@ struct name { char *n_fullextra; /* GFULL, without address */ }; -struct addrguts { - char const *ag_input; /* Input string as given */ - size_t ag_ilen; /* strlen() of input */ - size_t ag_iaddr_start; /* Start of *addr-spec* in .ag_input */ - size_t ag_iaddr_aend; /* ..and one past its end */ - char *ag_skinned; /* Output (alloced if !=.ag_input) */ - size_t ag_slen; /* strlen() of .ag_skinned */ - size_t ag_sdom_start; /* Start of domain in .ag_skinned, */ +struct n_addrguts{ + /* Input string as given (maybe replaced with a fixed one!) */ + char const *ag_input; + size_t ag_ilen; /* strlen() of input */ + size_t ag_iaddr_start; /* Start of *addr-spec* in .ag_input */ + size_t ag_iaddr_aend; /* ..and one past its end */ + char *ag_skinned; /* Output (alloced if !=.ag_input) */ + size_t ag_slen; /* strlen() of .ag_skinned */ + size_t ag_sdom_start; /* Start of domain in .ag_skinned, */ enum nameflags ag_n_flags; /* enum nameflags of .ag_skinned */ }; diff --git a/nailfuns.h b/nailfuns.h index 7295d491..daa0887f 100644 --- a/nailfuns.h +++ b/nailfuns.h @@ -862,10 +862,10 @@ FL char * skin(char const *name); /* Skin *name* and extract the *addr-spec* according to RFC 5322. * Store the result in .ag_skinned and also fill in those .ag_ fields that have * actually been seen. - * Return 0 if something good has been parsed, 1 if fun didn't exactly know how - * to deal with the input, or if that was plain invalid */ -FL int addrspec_with_guts(int doskin, char const *name, - struct addrguts *agp); + * Return NULL on error, or name again, but which may have been replaced by + * a version with fixed quotation etc.! */ +FL char const *n_addrspec_with_guts(struct n_addrguts *agp, char const *name, + bool_t doskin); /* Fetch the real name from an internet mail address field */ FL char * realname(char const *name); diff --git a/nam_a_grp.c b/nam_a_grp.c index 1f6dfc03..53843bd9 100644 --- a/nam_a_grp.c +++ b/nam_a_grp.c @@ -285,10 +285,8 @@ yankname(char const *ap, char *wbuf, char const *separators, int keepcomms) c = *cp; if (c == '\0') break; - if (c == '\\') { - lastsp = 0; - continue; - } + if (c == '\\') + goto jwpwc; if (c == '"') { if (lc != '\\') inquote = !inquote; @@ -893,13 +891,22 @@ _mlmux_linkout(struct group *gp) FL struct name * nalloc(char const *str, enum gfield ntype) { - struct addrguts ag; + struct n_addrguts ag; struct str in, out; struct name *np; NYD_ENTER; assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0); - addrspec_with_guts(((ntype & (GFULL | GSKIN | GREF)) != 0), str, &ag); + str = n_addrspec_with_guts(&ag, str, + ((ntype & (GFULL | GSKIN | GREF)) != 0)); + if(str == NULL){ + /* + np = NULL; TODO We cannot return NULL, + goto jleave; TODO thus handle failures in here! + */ + str = ag.ag_input; + } + if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) { ag.ag_n_flags |= NAME_NAME_SALLOC; np = salloc(sizeof(*np) + ag.ag_slen +1); @@ -936,10 +943,20 @@ nalloc(char const *str, enum gfield ntype) if (s == 0 || str[--s] != '<' || str[e++] != '>') goto jskipfullextra; i = ag.ag_ilen - e; - in.s = ac_alloc(s + i +1); + in.s = n_lofi_alloc(s + 1 + i +1); + while(s > 0 && blankchar(str[s - 1])) + --s; memcpy(in.s, str, s); - if (i > 0) - memcpy(in.s + s, str + e, i); + if (i > 0) { + in.s[s++] = ' '; + while (blankchar(str[e])) { + ++e; + if (--i == 0) + break; + } + if (i > 0) + memcpy(&in.s[s], &str[e], i); + } s += i; in.s[in.l = s] = '\0'; mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV); @@ -950,8 +967,8 @@ nalloc(char const *str, enum gfield ntype) --i; np->n_fullextra = savestrbuf(cp, i); + n_lofi_free(in.s); free(out.s); - ac_free(in.s); } jskipfullextra: @@ -966,6 +983,7 @@ jskipfullextra: /* The domain name was IDNA and has been converted. We also have to * ensure that the domain name in .n_fullname is replaced with the * converted version, since MIME doesn't perform encoding of addrs */ + /* TODO This definetely doesn't belong here! */ size_t l = ag.ag_iaddr_start, lsuff = ag.ag_ilen - ag.ag_iaddr_aend; in.s = ac_alloc(l + ag.ag_slen + lsuff +1); -- 2.11.4.GIT