"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.11/message.c" (8 Aug 2018, 47451 Bytes) of package /linux/misc/s-nail-14.9.11.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "message.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.9.10_vs_14.9.11.

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ Message, message array, n_getmsglist(), and related operations.
    3  *
    4  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
    5  * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
    6  * SPDX-License-Identifier: BSD-3-Clause
    7  */
    8 /*
    9  * Copyright (c) 1980, 1993
   10  *      The Regents of the University of California.  All rights reserved.
   11  *
   12  * Redistribution and use in source and binary forms, with or without
   13  * modification, are permitted provided that the following conditions
   14  * are met:
   15  * 1. Redistributions of source code must retain the above copyright
   16  *    notice, this list of conditions and the following disclaimer.
   17  * 2. Redistributions in binary form must reproduce the above copyright
   18  *    notice, this list of conditions and the following disclaimer in the
   19  *    documentation and/or other materials provided with the distribution.
   20  * 3. Neither the name of the University nor the names of its contributors
   21  *    may be used to endorse or promote products derived from this software
   22  *    without specific prior written permission.
   23  *
   24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
   25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
   28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   34  * SUCH DAMAGE.
   35  */
   36 #undef n_FILE
   37 #define n_FILE message
   38 
   39 #ifndef HAVE_AMALGAMATION
   40 # include "nail.h"
   41 #endif
   42 
   43 /* Token values returned by the scanner used for argument lists.
   44  * Also, sizes of scanner-related things */
   45 enum a_msg_token{
   46    a_MSG_T_EOL,      /* End of the command line */
   47    a_MSG_T_NUMBER,   /* Message number */
   48    a_MSG_T_MINUS,    /* - */
   49    a_MSG_T_STRING,   /* A string (possibly containing -) */
   50    a_MSG_T_DOT,      /* . */
   51    a_MSG_T_UP,       /* ^ */
   52    a_MSG_T_DOLLAR,   /* $ */
   53    a_MSG_T_ASTER,    /* * */
   54    a_MSG_T_OPEN,     /* ( */
   55    a_MSG_T_CLOSE,    /* ) */
   56    a_MSG_T_PLUS,     /* + */
   57    a_MSG_T_COMMA,    /* , */
   58    a_MSG_T_SEMI,     /* ; */
   59    a_MSG_T_BACK,     /* ` */
   60    a_MSG_T_ERROR     /* Lexical error */
   61 };
   62 
   63 enum a_msg_idfield{
   64    a_MSG_ID_REFERENCES,
   65    a_MSG_ID_IN_REPLY_TO
   66 };
   67 
   68 enum a_msg_state{
   69    a_MSG_S_NEW = 1u<<0,
   70    a_MSG_S_OLD = 1u<<1,
   71    a_MSG_S_UNREAD = 1u<<2,
   72    a_MSG_S_DELETED = 1u<<3,
   73    a_MSG_S_READ = 1u<<4,
   74    a_MSG_S_FLAG = 1u<<5,
   75    a_MSG_S_ANSWERED = 1u<<6,
   76    a_MSG_S_DRAFT = 1u<<7,
   77    a_MSG_S_SPAM = 1u<<8,
   78    a_MSG_S_SPAMUNSURE = 1u<<9,
   79    a_MSG_S_MLIST = 1u<<10,
   80    a_MSG_S_MLSUBSCRIBE = 1u<<11
   81 };
   82 
   83 struct a_msg_coltab{
   84    char mco_char; /* What to find past : */
   85    ui8_t mco__dummy[3];
   86    int mco_bit;   /* Associated modifier bit */
   87    int mco_mask;  /* m_status bits to mask */
   88    int mco_equal; /* ... must equal this */
   89 };
   90 
   91 struct a_msg_lex{
   92    char ml_char;
   93    ui8_t ml_token;
   94 };
   95 
   96 struct a_msg_speclex{
   97    char *msl_str;             /* If parsed a string */
   98    int msl_no;                /* If parsed a number TODO siz_t! */
   99    char msl__smallstrbuf[4];
  100    /* We directly adjust pointer in .ca_arg.ca_str.s, do not adjust .l */
  101    struct n_cmd_arg *msl_cap;
  102    char const *msl_input_orig;
  103 };
  104 
  105 static struct a_msg_coltab const a_msg_coltabs[] = {
  106    {'n', {0,}, a_MSG_S_NEW, MNEW, MNEW},
  107    {'o', {0,}, a_MSG_S_OLD, MNEW, 0},
  108    {'u', {0,}, a_MSG_S_UNREAD, MREAD, 0},
  109    {'d', {0,}, a_MSG_S_DELETED, MDELETED, MDELETED},
  110    {'r', {0,}, a_MSG_S_READ, MREAD, MREAD},
  111    {'f', {0,}, a_MSG_S_FLAG, MFLAGGED, MFLAGGED},
  112    {'a', {0,}, a_MSG_S_ANSWERED, MANSWERED, MANSWERED},
  113    {'t', {0,}, a_MSG_S_DRAFT, MDRAFTED, MDRAFTED},
  114    {'s', {0,}, a_MSG_S_SPAM, MSPAM, MSPAM},
  115    {'S', {0,}, a_MSG_S_SPAMUNSURE, MSPAMUNSURE, MSPAMUNSURE},
  116    /* These have no per-message flags, but must be evaluated */
  117    {'l', {0,}, a_MSG_S_MLIST, 0, 0},
  118    {'L', {0,}, a_MSG_S_MLSUBSCRIBE, 0, 0},
  119 };
  120 
  121 static struct a_msg_lex const a_msg_singles[] = {
  122    {'$', a_MSG_T_DOLLAR},
  123    {'.', a_MSG_T_DOT},
  124    {'^', a_MSG_T_UP},
  125    {'*', a_MSG_T_ASTER},
  126    {'-', a_MSG_T_MINUS},
  127    {'+', a_MSG_T_PLUS},
  128    {'(', a_MSG_T_OPEN},
  129    {')', a_MSG_T_CLOSE},
  130    {',', a_MSG_T_COMMA},
  131    {';', a_MSG_T_SEMI},
  132    {'`', a_MSG_T_BACK}
  133 };
  134 
  135 /* Slots in ::message */
  136 static size_t a_msg_mem_space;
  137 
  138 /* Mark entire threads */
  139 static bool_t a_msg_threadflag;
  140 
  141 /* :d on its way HACK TODO */
  142 static bool_t a_msg_list_saw_d, a_msg_list_last_saw_d;
  143 
  144 /* Lazy load message header fields */
  145 static enum okay a_msg_get_header(struct message *mp);
  146 
  147 /* Append, taking care of resizes TODO vector */
  148 static char **a_msg_add_to_nmadat(char ***nmadat, size_t *nmasize,
  149                char **np, char *string);
  150 
  151 /* Mark all messages that the user wanted from the command line in the message
  152  * structure.  Return 0 on success, -1 on error */
  153 static int a_msg_markall(char const *orig, struct n_cmd_arg *cap, int f);
  154 
  155 /* Turn the character after a colon modifier into a bit value */
  156 static int a_msg_evalcol(int col);
  157 
  158 /* Check the passed message number for legality and proper flags.  Unless f is
  159  * MDELETED the message has to be undeleted */
  160 static bool_t a_msg_check(int mno, int f);
  161 
  162 /* Scan out a single lexical item and return its token number, updating *mslp */
  163 static int a_msg_scan(struct a_msg_speclex *mslp);
  164 
  165 /* See if the passed name sent the passed message */
  166 static bool_t a_msg_match_sender(struct message *mp, char const *str,
  167                bool_t allnet);
  168 
  169 /* Check whether the given message-id or references match */
  170 static bool_t a_msg_match_mid(struct message *mp, char const *id,
  171                enum a_msg_idfield idfield);
  172 
  173 /* See if the given string matches.
  174  * For the purpose of the scan, we ignore case differences.
  175  * This is the engine behind the "/" search */
  176 static bool_t a_msg_match_dash(struct message *mp, char const *str);
  177 
  178 /* See if the given search expression matches.
  179  * For the purpose of the scan, we ignore case differences.
  180  * This is the engine behind the "@[..@].." search */
  181 static bool_t a_msg_match_at(struct message *mp, struct search_expr *sep);
  182 
  183 /* Unmark the named message */
  184 static void a_msg_unmark(int mesg);
  185 
  186 /* Return the message number corresponding to the passed meta character */
  187 static int a_msg_metamess(int meta, int f);
  188 
  189 /* Helper for mark(): self valid, threading enabled */
  190 static void a_msg__threadmark(struct message *self, int f);
  191 
  192 static enum okay
  193 a_msg_get_header(struct message *mp){
  194    enum okay rv;
  195    NYD2_ENTER;
  196    n_UNUSED(mp);
  197 
  198    switch(mb.mb_type){
  199    case MB_FILE:
  200    case MB_MAILDIR:
  201       rv = OKAY;
  202       break;
  203 #ifdef HAVE_POP3
  204    case MB_POP3:
  205       rv = pop3_header(mp);
  206       break;
  207 #endif
  208 #ifdef HAVE_IMAP
  209    case MB_IMAP:
  210    case MB_CACHE:
  211       rv = imap_header(mp);
  212       break;
  213 #endif
  214    case MB_VOID:
  215    default:
  216       rv = STOP;
  217       break;
  218    }
  219    NYD2_LEAVE;
  220    return rv;
  221 }
  222 
  223 static char **
  224 a_msg_add_to_nmadat(char ***nmadat, size_t *nmasize, /* TODO Vector */
  225       char **np, char *string){
  226    size_t idx, i;
  227    NYD2_ENTER;
  228 
  229    if((idx = PTR2SIZE(np - *nmadat)) >= *nmasize){
  230       char **narr;
  231 
  232       i = *nmasize << 1;
  233       *nmasize = i;
  234       narr = n_autorec_alloc(i * sizeof *np);
  235       memcpy(narr, *nmadat, i >>= 1);
  236       *nmadat = narr;
  237       np = &narr[idx];
  238    }
  239    *np++ = string;
  240    NYD2_LEAVE;
  241    return np;
  242 }
  243 
  244 static int
  245 a_msg_markall(char const *orig, struct n_cmd_arg *cap, int f){
  246    struct a_msg_speclex msl;
  247    enum a_msg_idfield idfield;
  248    size_t j, nmasize;
  249    char const *id;
  250    char **nmadat_lofi, **nmadat, **np, **nq, *cp;
  251    struct message *mp, *mx;
  252    int i, valdot, beg, colmod, tok, colresult;
  253    enum{
  254       a_NONE = 0,
  255       a_ALLNET = 1u<<0,    /* (CTA()d to be == TRU1 */
  256       a_ALLOC = 1u<<1,     /* Have allocated something */
  257       a_THREADED = 1u<<2,
  258       a_ERROR = 1u<<3,
  259       a_ANY = 1u<<4,       /* Have marked just ANY */
  260       a_RANGE = 1u<<5,     /* Seen dash, await close */
  261       a_ASTER = 1u<<8,
  262       a_TOPEN = 1u<<9,     /* ( used (and didn't match) */
  263       a_TBACK = 1u<<10,    /* ` used (and didn't match) */
  264 #ifdef HAVE_IMAP
  265       a_HAVE_IMAP_HEADERS = 1u<<14,
  266 #endif
  267       a_LOG = 1u<<29,      /* Log errors */
  268       a_TMP = 1u<<30
  269    } flags;
  270    NYD_ENTER;
  271    n_LCTA((ui32_t)a_ALLNET == (ui32_t)TRU1,
  272       "Constant is converted to bool_t via AND, thus");
  273 
  274    /* Update message array: clear MMARK but remember its former state for ` */
  275    for(i = msgCount; i-- > 0;){
  276       enum mflag mf;
  277 
  278       mf = (mp = &message[i])->m_flag;
  279       if(mf & MMARK)
  280          mf |= MOLDMARK;
  281       else
  282          mf &= ~MOLDMARK;
  283       mf &= ~MMARK;
  284       mp->m_flag = mf;
  285    }
  286 
  287    memset(&msl, 0, sizeof msl);
  288    msl.msl_cap = cap;
  289    msl.msl_input_orig = orig;
  290 
  291    np = nmadat =
  292    nmadat_lofi = n_lofi_alloc((nmasize = 64) * sizeof *np); /* TODO vector */
  293    n_UNINIT(beg, 0);
  294    n_UNINIT(idfield, a_MSG_ID_REFERENCES);
  295    a_msg_threadflag = FAL0;
  296    valdot = (int)PTR2SIZE(dot - message + 1);
  297    colmod = 0;
  298    id = NULL;
  299    flags = a_ALLOC | (mb.mb_threaded ? a_THREADED : 0) |
  300          ((!(n_pstate & n_PS_HOOK_MASK) || (n_poption & n_PO_D_V))
  301             ? a_LOG : 0);
  302 
  303    while((tok = a_msg_scan(&msl)) != a_MSG_T_EOL){
  304       if((a_msg_threadflag = (tok < 0)))
  305          tok &= INT_MAX;
  306 
  307       switch(tok){
  308       case a_MSG_T_NUMBER:
  309          n_pstate |= n_PS_MSGLIST_GABBY;
  310 jnumber:
  311          if(!a_msg_check(msl.msl_no, f))
  312             goto jerr;
  313 
  314          if(flags & a_RANGE){
  315             flags ^= a_RANGE;
  316 
  317             if(!(flags & a_THREADED)){
  318                if(beg < msl.msl_no)
  319                   i = beg;
  320                else{
  321                   i = msl.msl_no;
  322                   msl.msl_no = beg;
  323                }
  324 
  325                for(; i <= msl.msl_no; ++i){
  326                   mp = &message[i - 1];
  327                   if(!(mp->m_flag & MHIDDEN) &&
  328                          (f == MDELETED || !(mp->m_flag & MDELETED))){
  329                      mark(i, f);
  330                      flags |= a_ANY;
  331                   }
  332                }
  333             }else{
  334                /* TODO threaded ranges are a mess */
  335                enum{
  336                   a_T_NONE,
  337                   a_T_HOT = 1u<<0,
  338                   a_T_DIR_PREV = 1u<<1
  339                } tf;
  340                int i_base;
  341 
  342                if(beg < msl.msl_no)
  343                   i = beg;
  344                else{
  345                   i = msl.msl_no;
  346                   msl.msl_no = beg;
  347                }
  348 
  349                i_base = i;
  350                tf = a_T_NONE;
  351 jnumber__thr:
  352                for(;;){
  353                   mp = &message[i - 1];
  354                   if(!(mp->m_flag & MHIDDEN) &&
  355                          (f == MDELETED || !(mp->m_flag & MDELETED))){
  356                      if(tf & a_T_HOT){
  357                         mark(i, f);
  358                         flags |= a_ANY;
  359                      }
  360                   }
  361 
  362                   /* We may have reached the endpoint.  If we were still
  363                    * detecting the direction to search for it, restart.
  364                    * Otherwise finished */
  365                   if(i == msl.msl_no){ /* XXX */
  366                      if(!(tf & a_T_HOT)){
  367                         tf |= a_T_HOT;
  368                         i = i_base;
  369                         goto jnumber__thr;
  370                      }
  371                      break;
  372                   }
  373 
  374                   mx = (tf & a_T_DIR_PREV) ? prev_in_thread(mp)
  375                         : next_in_thread(mp);
  376                   if(mx == NULL){
  377                      /* We anyway have failed to reach the endpoint in this
  378                       * direction; if we already switched that, report error */
  379                      if(!(tf & a_T_DIR_PREV)){
  380                         tf |= a_T_DIR_PREV;
  381                         i = i_base;
  382                         goto jnumber__thr;
  383                      }
  384                      id = N_("Range crosses multiple threads\n");
  385                      goto jerrmsg;
  386                   }
  387                   i = (int)PTR2SIZE(mx - message + 1);
  388                }
  389             }
  390 
  391             beg = 0;
  392          }else{
  393             /* Could be an inclusive range? */
  394             if(msl.msl_cap != NULL &&
  395                   msl.msl_cap->ca_arg.ca_str.s[0] == '-'){
  396                if(*++msl.msl_cap->ca_arg.ca_str.s == '\0')
  397                   msl.msl_cap = msl.msl_cap->ca_next;
  398                beg = msl.msl_no;
  399                flags |= a_RANGE;
  400             }else{
  401                mark(msl.msl_no, f);
  402                flags |= a_ANY;
  403             }
  404          }
  405          break;
  406       case a_MSG_T_PLUS:
  407          n_pstate &= ~n_PS_MSGLIST_DIRECT;
  408          n_pstate |= n_PS_MSGLIST_GABBY;
  409          i = valdot;
  410          do{
  411             if(flags & a_THREADED){
  412                mx = next_in_thread(&message[i - 1]);
  413                i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
  414             }else
  415                ++i;
  416             if(i > msgCount){
  417                id = N_("Referencing beyond last message\n");
  418                goto jerrmsg;
  419             }
  420          }while(message[i - 1].m_flag == MHIDDEN ||
  421             (message[i - 1].m_flag & MDELETED) != (unsigned)f);
  422          msl.msl_no = i;
  423          goto jnumber;
  424       case a_MSG_T_MINUS:
  425          n_pstate &= ~n_PS_MSGLIST_DIRECT;
  426          n_pstate |= n_PS_MSGLIST_GABBY;
  427          i = valdot;
  428          do{
  429             if(flags & a_THREADED){
  430                mx = prev_in_thread(&message[i - 1]);
  431                i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
  432             }else
  433                --i;
  434             if(i <= 0){
  435                id = N_("Referencing before first message\n");
  436                goto jerrmsg;
  437             }
  438          }while(message[i - 1].m_flag == MHIDDEN ||
  439             (message[i - 1].m_flag & MDELETED) != (unsigned)f);
  440          msl.msl_no = i;
  441          goto jnumber;
  442       case a_MSG_T_STRING:
  443          n_pstate &= ~n_PS_MSGLIST_DIRECT;
  444          if(flags & a_RANGE)
  445             goto jebadrange;
  446 
  447          /* This may be a colon modifier */
  448          if((cp = msl.msl_str)[0] != ':')
  449             np = a_msg_add_to_nmadat(&nmadat, &nmasize, np,
  450                   savestr(msl.msl_str));
  451          else{
  452             while(*++cp != '\0'){
  453                colresult = a_msg_evalcol(*cp);
  454                if(colresult == 0){
  455                   if(flags & a_LOG)
  456                      n_err(_("Unknown colon modifier: %s\n"), msl.msl_str);
  457                   goto jerr;
  458                }
  459                if(colresult == a_MSG_S_DELETED){
  460                   a_msg_list_saw_d = TRU1;
  461                   f |= MDELETED;
  462                }
  463                colmod |= colresult;
  464             }
  465          }
  466          break;
  467       case a_MSG_T_OPEN:
  468          n_pstate &= ~n_PS_MSGLIST_DIRECT;
  469          if(flags & a_RANGE)
  470             goto jebadrange;
  471          flags |= a_TOPEN;
  472 
  473 #ifdef HAVE_IMAP_SEARCH
  474          /* C99 */{
  475             ssize_t ires;
  476 
  477             if((ires = imap_search(msl.msl_str, f)) >= 0){
  478                if(ires > 0)
  479                   flags |= a_ANY;
  480                break;
  481             }
  482          }
  483 #else
  484          if(flags & a_LOG)
  485             n_err(_("Optional selector not available: %s\n"), msl.msl_str);
  486 #endif
  487          goto jerr;
  488       case a_MSG_T_DOLLAR:
  489       case a_MSG_T_UP:
  490       case a_MSG_T_SEMI:
  491          n_pstate |= n_PS_MSGLIST_GABBY;
  492          /* FALLTHRU */
  493       case a_MSG_T_DOT: /* Don't set _GABBY for dot to allow history.. */
  494          n_pstate &= ~n_PS_MSGLIST_DIRECT;
  495          if((msl.msl_no = a_msg_metamess(msl.msl_str[0], f)) == -1)
  496             goto jerr;
  497          goto jnumber;
  498       case a_MSG_T_BACK:
  499          n_pstate &= ~n_PS_MSGLIST_DIRECT;
  500          if(flags & a_RANGE)
  501             goto jebadrange;
  502 
  503          flags |= a_TBACK;
  504          for(i = 0; i < msgCount; ++i){
  505             if((mp = &message[i])->m_flag & MHIDDEN)
  506                continue;
  507             if((mp->m_flag & MDELETED) != (unsigned)f){
  508                if(!a_msg_list_last_saw_d)
  509                   continue;
  510                a_msg_list_saw_d = TRU1;
  511             }
  512             if(mp->m_flag & MOLDMARK){
  513                mark(i + 1, f);
  514                flags &= ~a_TBACK;
  515                flags |= a_ANY;
  516             }
  517          }
  518          break;
  519       case a_MSG_T_ASTER:
  520          n_pstate &= ~n_PS_MSGLIST_DIRECT;
  521          if(flags & a_RANGE)
  522             goto jebadrange;
  523          flags |= a_ASTER;
  524          break;
  525       case a_MSG_T_COMMA:
  526          n_pstate &= ~n_PS_MSGLIST_DIRECT;
  527          n_pstate |= n_PS_MSGLIST_GABBY;
  528          if(flags & a_RANGE)
  529             goto jebadrange;
  530 
  531 #ifdef HAVE_IMAP
  532          if(!(flags & a_HAVE_IMAP_HEADERS) && mb.mb_type == MB_IMAP){
  533             flags |= a_HAVE_IMAP_HEADERS;
  534             imap_getheaders(1, msgCount);
  535          }
  536 #endif
  537 
  538          if(id == NULL){
  539             if((cp = hfield1("in-reply-to", dot)) != NULL)
  540                idfield = a_MSG_ID_IN_REPLY_TO;
  541             else if((cp = hfield1("references", dot)) != NULL){
  542                struct name *enp;
  543 
  544                if((enp = extract(cp, GREF)) != NULL){
  545                   while(enp->n_flink != NULL)
  546                      enp = enp->n_flink;
  547                   cp = enp->n_name;
  548                   idfield = a_MSG_ID_REFERENCES;
  549                }else
  550                   cp = NULL;
  551             }
  552 
  553             if(cp != NULL)
  554                id = savestr(cp);
  555             else{
  556                id = N_("Message-ID of parent of \"dot\" is indeterminable\n");
  557                goto jerrmsg;
  558             }
  559          }else if(flags & a_LOG)
  560             n_err(_("Ignoring redundant specification of , selector\n"));
  561          break;
  562       case a_MSG_T_ERROR:
  563          n_pstate &= ~n_PS_MSGLIST_DIRECT;
  564          n_pstate |= n_PS_MSGLIST_GABBY;
  565          goto jerr;
  566       }
  567 
  568       /* Explicitly disallow invalid ranges for future safety */
  569       if(msl.msl_cap != NULL && msl.msl_cap->ca_arg.ca_str.s[0] == '-' &&
  570             !(flags & a_RANGE)){
  571          if(flags & a_LOG)
  572             n_err(_("Ignoring invalid range in: %s\n"), msl.msl_input_orig);
  573          if(*++msl.msl_cap->ca_arg.ca_str.s == '\0')
  574             msl.msl_cap = msl.msl_cap->ca_next;
  575       }
  576    }
  577    if(flags & a_RANGE){
  578       id = N_("Missing second range argument\n");
  579       goto jerrmsg;
  580    }
  581 
  582    np = a_msg_add_to_nmadat(&nmadat, &nmasize, np, NULL);
  583    --np;
  584 
  585    /* * is special at this point, after we have parsed the entire line */
  586    if(flags & a_ASTER){
  587       for(i = 0; i < msgCount; ++i){
  588          if((mp = &message[i])->m_flag & MHIDDEN)
  589             continue;
  590          if(!a_msg_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
  591             continue;
  592          mark(i + 1, f);
  593          flags |= a_ANY;
  594       }
  595       if(!(flags & a_ANY))
  596          goto jenoapp;
  597       goto jleave;
  598    }
  599 
  600    /* If any names were given, add any messages which match */
  601    if(np > nmadat || id != NULL){
  602       struct search_expr *sep;
  603 
  604       sep = NULL;
  605 
  606       /* The @ search works with struct search_expr, so build an array.
  607        * To simplify array, i.e., regex_t destruction, and optimize for the
  608        * common case we walk the entire array even in case of errors */
  609       /* XXX Like many other things around here: this should be outsourced */
  610       if(np > nmadat){
  611          j = PTR2SIZE(np - nmadat) * sizeof(*sep);
  612          sep = n_lofi_alloc(j);
  613          memset(sep, 0, j);
  614 
  615          for(j = 0, nq = nmadat; *nq != NULL; ++j, ++nq){
  616             char *xsave, *x, *y;
  617 
  618             sep[j].ss_body = x = xsave = *nq;
  619             if(*x != '@' || (flags & a_ERROR))
  620                continue;
  621 
  622             /* Cramp the namelist */
  623             for(y = &x[1];; ++y){
  624                if(*y == '\0'){
  625                   x = NULL;
  626                   break;
  627                }
  628                if(*y == '@'){
  629                   x = y;
  630                   break;
  631                }
  632             }
  633             if(x == NULL || &x[-1] == xsave)
  634 jat_where_default:
  635                sep[j].ss_field = "subject";
  636             else{
  637                ++xsave;
  638                if(*xsave == '~'){
  639                   sep[j].ss_skin = TRU1;
  640                   if(++xsave >= x){
  641                      if(flags & a_LOG)
  642                         n_err(_("[@..]@ search expression: no namelist, "
  643                            "only \"~\" skin indicator\n"));
  644                      flags |= a_ERROR;
  645                      continue;
  646                   }
  647                }
  648                cp = savestrbuf(xsave, PTR2SIZE(x - xsave));
  649 
  650                /* Namelist could be a regular expression, too */
  651 #ifdef HAVE_REGEX
  652                if(n_is_maybe_regex(cp)){
  653                   int s;
  654 
  655                   assert(sep[j].ss_field == NULL);
  656                   if((s = regcomp(&sep[j].ss__fieldre_buf, cp,
  657                         REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
  658                      if(flags & a_LOG)
  659                         n_err(_("Invalid regular expression: %s: %s\n"),
  660                            n_shexp_quote_cp(cp, FAL0),
  661                            n_regex_err_to_doc(NULL, s));
  662                      flags |= a_ERROR;
  663                      continue;
  664                   }
  665                   sep[j].ss_fieldre = &sep[j].ss__fieldre_buf;
  666                }else
  667 #endif
  668                     {
  669                   struct str sio;
  670 
  671                   /* Because of the special cases we need to trim explicitly
  672                    * here, they are not covered by n_strsep() */
  673                   sio.s = cp;
  674                   sio.l = PTR2SIZE(x - xsave);
  675                   if(*(cp = n_str_trim(&sio, n_STR_TRIM_BOTH)->s) == '\0')
  676                      goto jat_where_default;
  677                   sep[j].ss_field = cp;
  678                }
  679             }
  680 
  681             /* The actual search expression.  If it is empty we only test the
  682              * field(s) for existence  */
  683             x = &(x == NULL ? *nq : x)[1];
  684             if(*x == '\0'){
  685                sep[j].ss_field_exists = TRU1;
  686 #ifdef HAVE_REGEX
  687             }else if(n_is_maybe_regex(x)){
  688                int s;
  689 
  690                sep[j].ss_body = NULL;
  691                if((s = regcomp(&sep[j].ss__bodyre_buf, x,
  692                      REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
  693                   if(flags & a_LOG)
  694                      n_err(_("Invalid regular expression: %s: %s\n"),
  695                         n_shexp_quote_cp(x, FAL0),
  696                         n_regex_err_to_doc(NULL, s));
  697                   flags |= a_ERROR;
  698                   continue;
  699                }
  700                sep[j].ss_bodyre = &sep[j].ss__bodyre_buf;
  701 #endif
  702             }else
  703                sep[j].ss_body = x;
  704          }
  705          if(flags & a_ERROR)
  706             goto jnamesearch_sepfree;
  707       }
  708 
  709       /* Iterate the entire message array */
  710 #ifdef HAVE_IMAP
  711          if(!(flags & a_HAVE_IMAP_HEADERS) && mb.mb_type == MB_IMAP){
  712             flags |= a_HAVE_IMAP_HEADERS;
  713             imap_getheaders(1, msgCount);
  714          }
  715 #endif
  716       if(ok_blook(allnet))
  717          flags |= a_ALLNET;
  718       n_autorec_relax_create();
  719       for(i = 0; i < msgCount; ++i){
  720          if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
  721             continue;
  722          if(!a_msg_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
  723             continue;
  724 
  725          flags &= ~a_TMP;
  726          if(np > nmadat){
  727             for(nq = nmadat; *nq != NULL; ++nq){
  728                if(**nq == '@'){
  729                   if(a_msg_match_at(mp, &sep[PTR2SIZE(nq - nmadat)])){
  730                      flags |= a_TMP;
  731                      break;
  732                   }
  733                }else if(**nq == '/'){
  734                   if(a_msg_match_dash(mp, *nq)){
  735                      flags |= a_TMP;
  736                      break;
  737                   }
  738                }else if(a_msg_match_sender(mp, *nq, (flags & a_ALLNET))){
  739                   flags |= a_TMP;
  740                   break;
  741                }
  742             }
  743          }
  744          if(!(flags & a_TMP) &&
  745                id != NULL && a_msg_match_mid(mp, id, idfield))
  746             flags |= a_TMP;
  747 
  748          if(flags & a_TMP){
  749             mark(i + 1, f);
  750             flags |= a_ANY;
  751          }
  752          n_autorec_relax_unroll();
  753       }
  754       n_autorec_relax_gut();
  755 
  756 jnamesearch_sepfree:
  757       if(sep != NULL){
  758 #ifdef HAVE_REGEX
  759          for(j = PTR2SIZE(np - nmadat); j-- != 0;){
  760             if(sep[j].ss_fieldre != NULL)
  761                regfree(sep[j].ss_fieldre);
  762             if(sep[j].ss_bodyre != NULL)
  763                regfree(sep[j].ss_bodyre);
  764          }
  765 #endif
  766          n_lofi_free(sep);
  767       }
  768       if(flags & a_ERROR)
  769          goto jerr;
  770    }
  771 
  772    /* If any colon modifiers were given, go through and mark any messages which
  773     * do satisfy the modifiers */
  774    if(colmod != 0){
  775       for(i = 0; i < msgCount; ++i){
  776          struct a_msg_coltab const *colp;
  777 
  778          if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
  779             continue;
  780          if(!a_msg_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
  781             continue;
  782 
  783          for(colp = a_msg_coltabs;
  784                PTRCMP(colp, <, &a_msg_coltabs[n_NELEM(a_msg_coltabs)]); ++colp)
  785             if(colp->mco_bit & colmod){
  786                /* Is this a colon modifier that requires evaluation? */
  787                if(colp->mco_mask == 0){
  788                   if(colp->mco_bit & (a_MSG_S_MLIST |
  789                            a_MSG_S_MLSUBSCRIBE)){
  790                      enum mlist_state what;
  791 
  792                      what = (colp->mco_bit & a_MSG_S_MLIST) ? MLIST_KNOWN
  793                            : MLIST_SUBSCRIBED;
  794                      if(what == is_mlist_mp(mp, what))
  795                         goto jcolonmod_mark;
  796                   }
  797                }else if((mp->m_flag & colp->mco_mask
  798                      ) == (enum mflag)colp->mco_equal){
  799 jcolonmod_mark:
  800                   mark(i + 1, f);
  801                   flags |= a_ANY;
  802                   break;
  803                }
  804             }
  805       }
  806    }
  807 
  808    /* It shall be an error if ` didn't match anything, and nothing else did */
  809    if((flags & (a_TBACK | a_ANY)) == a_TBACK){
  810       id = N_("No previously marked messages\n");
  811       goto jerrmsg;
  812    }else if(!(flags & a_ANY))
  813       goto jenoapp;
  814 
  815    assert(!(flags & a_ERROR));
  816 jleave:
  817    if(flags & a_ALLOC)
  818       n_lofi_free(nmadat_lofi);
  819    NYD_LEAVE;
  820    return (flags & a_ERROR) ? -1 : 0;
  821 
  822 jebadrange:
  823    id = N_("Invalid range endpoint\n");
  824    goto jerrmsg;
  825 jenoapp:
  826    id = N_("No applicable messages\n");
  827 jerrmsg:
  828    if(flags & a_LOG)
  829       n_err(V_(id));
  830 jerr:
  831    flags |= a_ERROR;
  832    goto jleave;
  833 }
  834 
  835 static int
  836 a_msg_evalcol(int col){
  837    struct a_msg_coltab const *colp;
  838    int rv;
  839    NYD2_ENTER;
  840 
  841    rv = 0;
  842    for(colp = a_msg_coltabs;
  843          PTRCMP(colp, <, &a_msg_coltabs[n_NELEM(a_msg_coltabs)]); ++colp)
  844       if(colp->mco_char == col){
  845          rv = colp->mco_bit;
  846          break;
  847       }
  848    NYD2_LEAVE;
  849    return rv;
  850 }
  851 
  852 static bool_t
  853 a_msg_check(int mno, int f){
  854    struct message *mp;
  855    NYD2_ENTER;
  856 
  857    if(mno < 1 || mno > msgCount){
  858       n_err(_("%d: Invalid message number\n"), mno);
  859       mno = 1;
  860    }else if(((mp = &message[mno - 1])->m_flag & MHIDDEN) ||
  861          (f != MDELETED && (mp->m_flag & MDELETED) != 0))
  862       n_err(_("%d: inappropriate message\n"), mno);
  863    else
  864       mno = 0;
  865    NYD2_LEAVE;
  866    return (mno == 0);
  867 }
  868 
  869 static int
  870 a_msg_scan(struct a_msg_speclex *mslp){
  871    struct a_msg_lex const *lp;
  872    char *cp, c;
  873    int rv;
  874    NYD_ENTER;
  875 
  876    rv = a_MSG_T_EOL;
  877 
  878    /* Empty cap's even for IGNORE_EMPTY (quoted empty tokens produce output) */
  879    for(;; mslp->msl_cap = mslp->msl_cap->ca_next){
  880       if(mslp->msl_cap == NULL)
  881          goto jleave;
  882 
  883       cp = mslp->msl_cap->ca_arg.ca_str.s;
  884       if((c = *cp++) != '\0')
  885          break;
  886    }
  887 
  888    /* Select members of a message thread */
  889    if(c == '&'){
  890       c = *cp;
  891       if(c == '\0' || spacechar(c)){
  892          mslp->msl_str = mslp->msl__smallstrbuf;
  893          mslp->msl_str[0] = '.';
  894          mslp->msl_str[1] = '\0';
  895          if(c == '\0')
  896             mslp->msl_cap = mslp->msl_cap->ca_next;
  897          else{
  898 jshexp_err:
  899             n_err(_("Message list: invalid syntax: %s (in %s)\n"),
  900                n_shexp_quote_cp(cp, FAL0),
  901                n_shexp_quote_cp(mslp->msl_input_orig, FAL0));
  902             rv = a_MSG_T_ERROR;
  903             goto jleave;
  904          }
  905          rv = a_MSG_T_DOT | INT_MIN;
  906          goto jleave;
  907       }
  908       rv = INT_MIN;
  909       ++cp;
  910    }
  911 
  912    /* If the leading character is a digit, scan the number and convert it
  913     * on the fly.  Return a_MSG_T_NUMBER when done */
  914    if(digitchar(c)){
  915       mslp->msl_no = 0;
  916       do
  917          mslp->msl_no = (mslp->msl_no * 10) + c - '0'; /* XXX inline atoi */
  918       while((c = *cp++, digitchar(c)));
  919 
  920       if(c == '\0')
  921          mslp->msl_cap = mslp->msl_cap->ca_next;
  922       else{
  923          --cp;
  924          /* This could be a range */
  925          if(c == '-')
  926             mslp->msl_cap->ca_arg.ca_str.s = cp;
  927          else
  928             goto jshexp_err;
  929       }
  930       rv |= a_MSG_T_NUMBER;
  931       goto jleave;
  932    }
  933 
  934    /* An IMAP SEARCH list. Note that a_MSG_T_OPEN has always been included
  935     * in singles[] in Mail and mailx. Thus although there is no formal
  936     * definition for (LIST) lists, they do not collide with historical
  937     * practice because a subject string (LIST) could never been matched
  938     * this way */
  939    if (c == '(') {
  940       bool_t inquote;
  941       ui32_t level;
  942       char *tocp;
  943 
  944       (tocp = mslp->msl_str = mslp->msl_cap->ca_arg.ca_str.s)[0] = '(';
  945       ++tocp;
  946       level = 1;
  947       inquote = FAL0;
  948       do {
  949          if ((c = *cp++) == '\0') {
  950 jmtop:
  951             n_err(_("Missing )\n"));
  952             n_err(_("P.S.: message specifications are now shell tokens, "
  953                "making it necessary to enclose IMAP search expressions "
  954                "in (single) quotes, e.g., '(from \"me\")'\n"));
  955             rv = a_MSG_T_ERROR;
  956             goto jleave;
  957          }
  958          if (inquote && c == '\\') {
  959             *tocp++ = c;
  960             c = *cp++;
  961             if (c == '\0')
  962                goto jmtop;
  963          } else if (c == '"')
  964             inquote = !inquote;
  965          else if (inquote)
  966             /*EMPTY*/;
  967          else if (c == '(')
  968             ++level;
  969          else if (c == ')')
  970             --level;
  971          else if (spacechar(c)) {
  972             /* Replace unquoted whitespace by single space characters, to make
  973              * the string IMAP SEARCH conformant */
  974             c = ' ';
  975             if (tocp[-1] == ' ')
  976                --tocp;
  977          }
  978          *tocp++ = c;
  979       } while (c != ')' || level > 0);
  980       *tocp = '\0';
  981       if(*cp != '\0')
  982          goto jshexp_err;
  983       mslp->msl_cap = mslp->msl_cap->ca_next;
  984       rv |= a_MSG_T_OPEN;
  985       goto jleave;
  986    }
  987 
  988    /* Check for single character tokens; return such if found */
  989    for(lp = a_msg_singles;
  990          PTRCMP(lp, <, &a_msg_singles[n_NELEM(a_msg_singles)]); ++lp){
  991       if(c == lp->ml_char){
  992          mslp->msl_str = mslp->msl__smallstrbuf;
  993          mslp->msl_str[0] = c;
  994          mslp->msl_str[1] = '\0';
  995          if(*cp != '\0')
  996             goto jshexp_err;
  997          mslp->msl_cap = mslp->msl_cap->ca_next;
  998          rv = lp->ml_token;
  999          goto jleave;
 1000       }
 1001    }
 1002 
 1003    mslp->msl_cap = mslp->msl_cap->ca_next;
 1004    mslp->msl_str = --cp;
 1005    rv = a_MSG_T_STRING;
 1006 jleave:
 1007    NYD_LEAVE;
 1008    return rv;
 1009 }
 1010 
 1011 static bool_t
 1012 a_msg_match_sender(struct message *mp, char const *str, bool_t allnet){
 1013    char const *str_base, *np_base, *np;
 1014    char sc, nc;
 1015    struct name *namep;
 1016    bool_t rv;
 1017    NYD2_ENTER;
 1018 
 1019    rv = FAL0;
 1020 
 1021    /* Empty string doesn't match */
 1022    namep = lextract(n_header_senderfield_of(mp), GFULL | GSKIN);
 1023 
 1024    if(namep == NULL || *(str_base = str) == '\0')
 1025       goto jleave;
 1026 
 1027    /* *allnet* is POSIX and, since it explicitly mentions login and user names,
 1028     * most likely case-sensitive.  XXX Still allow substr matching, though
 1029     * XXX possibly the first letter should be case-insensitive, then? */
 1030    if(allnet){
 1031       for(; namep != NULL; str = str_base, namep = namep->n_flink){
 1032          for(np_base = np = namep->n_name;;){
 1033             if((sc = *str++) == '@')
 1034                sc = '\0';
 1035             if((nc = *np++) == '@' || nc == '\0' || sc == '\0'){
 1036                if((rv = (sc == '\0')))
 1037                   goto jleave;
 1038                break;
 1039             }
 1040             if(sc != nc){
 1041                np = ++np_base;
 1042                str = str_base;
 1043             }
 1044          }
 1045       }
 1046    }else{
 1047       /* TODO POSIX says ~"match any address as shown in header overview",
 1048        * TODO but a normalized match would be more sane i guess.
 1049        * TODO struct name should gain a comparison method, normalize realname
 1050        * TODO content (in TODO) and thus match as likewise
 1051        * TODO "Buddy (Today) <here>" and "(Now) Buddy <here>" */
 1052       bool_t again_base, again;
 1053 
 1054       again_base = ok_blook(showname);
 1055 
 1056       for(; namep != NULL; str = str_base, namep = namep->n_flink){
 1057          again = again_base;
 1058 jagain:
 1059          np_base = np = again ? namep->n_fullname : namep->n_name;
 1060          str = str_base;
 1061          for(;;){
 1062             sc = *str++;
 1063             if((nc = *np++) == '\0' || sc == '\0'){
 1064                if((rv = (sc == '\0')))
 1065                   goto jleave;
 1066                break;
 1067             }
 1068             sc = upperconv(sc);
 1069             nc = upperconv(nc);
 1070             if(sc != nc){
 1071                np = ++np_base;
 1072                str = str_base;
 1073             }
 1074          }
 1075 
 1076          /* And really if i want to match 'on@' then i want it to match even if
 1077           * *showname* is set! */
 1078          if(again){
 1079             again = FAL0;
 1080             goto jagain;
 1081          }
 1082       }
 1083    }
 1084 jleave:
 1085    NYD2_LEAVE;
 1086    return rv;
 1087 }
 1088 
 1089 static bool_t
 1090 a_msg_match_mid(struct message *mp, char const *id,
 1091       enum a_msg_idfield idfield){
 1092    char const *cp;
 1093    bool_t rv;
 1094    NYD2_ENTER;
 1095 
 1096    rv = FAL0;
 1097 
 1098    if((cp = hfield1("message-id", mp)) != NULL){
 1099       switch(idfield){
 1100       case a_MSG_ID_REFERENCES:
 1101          if(!msgidcmp(id, cp))
 1102             rv = TRU1;
 1103          break;
 1104       case a_MSG_ID_IN_REPLY_TO:{
 1105          struct name *np;
 1106 
 1107          if((np = extract(id, GREF)) != NULL)
 1108             do{
 1109                if(!msgidcmp(np->n_name, cp)){
 1110                   rv = TRU1;
 1111                   break;
 1112                }
 1113             }while((np = np->n_flink) != NULL);
 1114          break;
 1115       }
 1116       }
 1117    }
 1118    NYD2_LEAVE;
 1119    return rv;
 1120 }
 1121 
 1122 static bool_t
 1123 a_msg_match_dash(struct message *mp, char const *str){
 1124    static char lastscan[128];
 1125 
 1126    struct str in, out;
 1127    char *hfield, *hbody;
 1128    bool_t rv;
 1129    NYD2_ENTER;
 1130 
 1131    rv = FAL0;
 1132 
 1133    if(*++str == '\0')
 1134       str = lastscan;
 1135    else
 1136       n_strscpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
 1137 
 1138    /* Now look, ignoring case, for the word in the string */
 1139    if(ok_blook(searchheaders) && (hfield = strchr(str, ':'))){
 1140       size_t l;
 1141 
 1142       l = PTR2SIZE(hfield - str);
 1143       hfield = n_lofi_alloc(l +1);
 1144       memcpy(hfield, str, l);
 1145       hfield[l] = '\0';
 1146       hbody = hfieldX(hfield, mp);
 1147       n_lofi_free(hfield);
 1148       hfield = n_UNCONST(str + l + 1);
 1149    }else{
 1150       hfield = n_UNCONST(str);
 1151       hbody = hfield1("subject", mp);
 1152    }
 1153    if(hbody == NULL)
 1154       goto jleave;
 1155 
 1156    in.l = strlen(in.s = hbody);
 1157    mime_fromhdr(&in, &out, TD_ICONV);
 1158    rv = substr(out.s, hfield);
 1159    n_free(out.s);
 1160 jleave:
 1161    NYD2_LEAVE;
 1162    return rv;
 1163 }
 1164 
 1165 static bool_t
 1166 a_msg_match_at(struct message *mp, struct search_expr *sep){
 1167    char const *field;
 1168    bool_t rv;
 1169    NYD2_ENTER;
 1170 
 1171    /* Namelist regex only matches headers.
 1172     * And there are the special cases header/<, "body"/> and "text"/=, the
 1173     * latter two of which need to be handled in here */
 1174    if((field = sep->ss_field) != NULL){
 1175       if(!asccasecmp(field, "body") || (field[1] == '\0' && field[0] == '>')){
 1176          rv = FAL0;
 1177 jmsg:
 1178          rv = message_match(mp, sep, rv);
 1179          goto jleave;
 1180       }else if(!asccasecmp(field, "text") ||
 1181             (field[1] == '\0' && field[0] == '=')){
 1182          rv = TRU1;
 1183          goto jmsg;
 1184       }
 1185    }
 1186 
 1187    rv = n_header_match(mp, sep);
 1188 jleave:
 1189    NYD2_LEAVE;
 1190    return rv;
 1191 }
 1192 
 1193 static void
 1194 a_msg_unmark(int mesg){
 1195    size_t i;
 1196    NYD2_ENTER;
 1197 
 1198    i = (size_t)mesg;
 1199    if(i < 1 || UICMP(z, i, >, msgCount))
 1200       n_panic(_("Bad message number to unmark"));
 1201    message[--i].m_flag &= ~MMARK;
 1202    NYD2_LEAVE;
 1203 }
 1204 
 1205 static int
 1206 a_msg_metamess(int meta, int f)
 1207 {
 1208    int c, m;
 1209    struct message *mp;
 1210    NYD2_ENTER;
 1211 
 1212    c = meta;
 1213    switch (c) {
 1214    case '^': /* First 'good' message left */
 1215       mp = mb.mb_threaded ? threadroot : message;
 1216       while (PTRCMP(mp, <, message + msgCount)) {
 1217          if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
 1218             c = (int)PTR2SIZE(mp - message + 1);
 1219             goto jleave;
 1220          }
 1221          if (mb.mb_threaded) {
 1222             mp = next_in_thread(mp);
 1223             if (mp == NULL)
 1224                break;
 1225          } else
 1226             ++mp;
 1227       }
 1228       if (!(n_pstate & n_PS_HOOK_MASK))
 1229          n_err(_("No applicable messages\n"));
 1230       goto jem1;
 1231 
 1232    case '$': /* Last 'good message left */
 1233       mp = mb.mb_threaded
 1234             ? this_in_thread(threadroot, -1) : message + msgCount - 1;
 1235       while (mp >= message) {
 1236          if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
 1237             c = (int)PTR2SIZE(mp - message + 1);
 1238             goto jleave;
 1239          }
 1240          if (mb.mb_threaded) {
 1241             mp = prev_in_thread(mp);
 1242             if (mp == NULL)
 1243                break;
 1244          } else
 1245             --mp;
 1246       }
 1247       if (!(n_pstate & n_PS_HOOK_MASK))
 1248          n_err(_("No applicable messages\n"));
 1249       goto jem1;
 1250 
 1251    case '.':
 1252       /* Current message */
 1253       m = dot - message + 1;
 1254       if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
 1255          n_err(_("%d: inappropriate message\n"), m);
 1256          goto jem1;
 1257       }
 1258       c = m;
 1259       break;
 1260 
 1261    case ';':
 1262       /* Previously current message */
 1263       if (prevdot == NULL) {
 1264          n_err(_("No previously current message\n"));
 1265          goto jem1;
 1266       }
 1267       m = prevdot - message + 1;
 1268       if ((prevdot->m_flag & MHIDDEN) ||
 1269             (prevdot->m_flag & MDELETED) != (ui32_t)f) {
 1270          n_err(_("%d: inappropriate message\n"), m);
 1271          goto jem1;
 1272       }
 1273       c = m;
 1274       break;
 1275 
 1276    default:
 1277       n_err(_("Unknown selector: %c\n"), c);
 1278       goto jem1;
 1279    }
 1280 jleave:
 1281    NYD2_LEAVE;
 1282    return c;
 1283 jem1:
 1284    c = -1;
 1285    goto jleave;
 1286 }
 1287 
 1288 static void
 1289 a_msg__threadmark(struct message *self, int f){
 1290    NYD2_ENTER;
 1291    if(!(self->m_flag & MHIDDEN) &&
 1292          (f == MDELETED || !(self->m_flag & MDELETED) || a_msg_list_saw_d))
 1293       self->m_flag |= MMARK;
 1294 
 1295    if((self = self->m_child) != NULL){
 1296       goto jcall;
 1297       while((self = self->m_younger) != NULL)
 1298          if(self->m_child != NULL)
 1299 jcall:
 1300             a_msg__threadmark(self, f);
 1301          else
 1302             self->m_flag |= MMARK;
 1303    }
 1304    NYD2_LEAVE;
 1305 }
 1306 
 1307 FL FILE *
 1308 setinput(struct mailbox *mp, struct message *m, enum needspec need){
 1309    enum okay ok;
 1310    FILE *rv;
 1311    NYD_ENTER;
 1312 
 1313    rv = NULL;
 1314 
 1315    switch(need){
 1316    case NEED_HEADER:
 1317       ok = (m->m_content_info & CI_HAVE_HEADER) ? OKAY
 1318             : a_msg_get_header(m);
 1319       break;
 1320    case NEED_BODY:
 1321       ok = (m->m_content_info & CI_HAVE_BODY) ? OKAY : get_body(m);
 1322       break;
 1323    default:
 1324    case NEED_UNSPEC:
 1325       ok = OKAY;
 1326       break;
 1327    }
 1328    if(ok != OKAY)
 1329       goto jleave;
 1330 
 1331    fflush(mp->mb_otf);
 1332    if(fseek(mp->mb_itf, (long)mailx_positionof(m->m_block, m->m_offset),
 1333          SEEK_SET) == -1){
 1334       n_perr(_("fseek"), 0);
 1335       n_panic(_("temporary file seek"));
 1336    }
 1337    rv = mp->mb_itf;
 1338 jleave:
 1339    NYD_LEAVE;
 1340    return rv;
 1341 }
 1342 
 1343 FL enum okay
 1344 get_body(struct message *mp){
 1345    enum okay rv;
 1346    NYD_ENTER;
 1347    n_UNUSED(mp);
 1348 
 1349    switch(mb.mb_type){
 1350    case MB_FILE:
 1351    case MB_MAILDIR:
 1352       rv = OKAY;
 1353       break;
 1354 #ifdef HAVE_POP3
 1355    case MB_POP3:
 1356       rv = pop3_body(mp);
 1357       break;
 1358 #endif
 1359 #ifdef HAVE_IMAP
 1360    case MB_IMAP:
 1361    case MB_CACHE:
 1362       rv = imap_body(mp);
 1363       break;
 1364 #endif
 1365    case MB_VOID:
 1366    default:
 1367       rv = STOP;
 1368       break;
 1369    }
 1370    NYD_LEAVE;
 1371    return rv;
 1372 }
 1373 
 1374 FL void
 1375 message_reset(void){
 1376    NYD_ENTER;
 1377    if(message != NULL){
 1378       n_free(message);
 1379       message = NULL;
 1380    }
 1381    msgCount = 0;
 1382    a_msg_mem_space = 0;
 1383    NYD_LEAVE;
 1384 }
 1385 
 1386 FL void
 1387 message_append(struct message *mp){
 1388    NYD_ENTER;
 1389    if(UICMP(z, msgCount + 1, >=, a_msg_mem_space)){
 1390       /* XXX remove _mem_space magics (or use s_Vector) */
 1391       a_msg_mem_space = ((a_msg_mem_space >= 128 &&
 1392                a_msg_mem_space <= 1000000)
 1393             ? a_msg_mem_space << 1 : a_msg_mem_space + 64);
 1394       message = n_realloc(message, a_msg_mem_space * sizeof(*message));
 1395    }
 1396    if(msgCount > 0){
 1397       if(mp != NULL)
 1398          message[msgCount - 1] = *mp;
 1399       else
 1400          memset(&message[msgCount - 1], 0, sizeof *message);
 1401    }
 1402    NYD_LEAVE;
 1403 }
 1404 
 1405 FL void
 1406 message_append_null(void){
 1407    NYD_ENTER;
 1408    if(msgCount == 0)
 1409       message_append(NULL);
 1410    setdot(message);
 1411    message[msgCount].m_size = 0;
 1412    message[msgCount].m_lines = 0;
 1413    NYD_LEAVE;
 1414 }
 1415 
 1416 FL bool_t
 1417 message_match(struct message *mp, struct search_expr const *sep,
 1418       bool_t with_headers){
 1419    char **line;
 1420    size_t *linesize, cnt;
 1421    FILE *fp;
 1422    bool_t rv;
 1423    NYD_ENTER;
 1424 
 1425    rv = FAL0;
 1426 
 1427    if((fp = Ftmp(NULL, "mpmatch", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
 1428       goto j_leave;
 1429 
 1430    if(sendmp(mp, fp, NULL, NULL, SEND_TOSRCH, NULL) < 0)
 1431       goto jleave;
 1432    fflush_rewind(fp);
 1433 
 1434    cnt = fsize(fp);
 1435    line = &termios_state.ts_linebuf; /* XXX line pool */
 1436    linesize = &termios_state.ts_linesize; /* XXX line pool */
 1437 
 1438    if(!with_headers)
 1439       while(fgetline(line, linesize, &cnt, NULL, fp, 0))
 1440          if(**line == '\n')
 1441             break;
 1442 
 1443    while(fgetline(line, linesize, &cnt, NULL, fp, 0)){
 1444 #ifdef HAVE_REGEX
 1445       if(sep->ss_bodyre != NULL){
 1446          if(regexec(sep->ss_bodyre, *line, 0,NULL, 0) == REG_NOMATCH)
 1447             continue;
 1448       }else
 1449 #endif
 1450             if(!substr(*line, sep->ss_body))
 1451          continue;
 1452       rv = TRU1;
 1453       break;
 1454    }
 1455 
 1456 jleave:
 1457    Fclose(fp);
 1458 j_leave:
 1459    NYD_LEAVE;
 1460    return rv;
 1461 }
 1462 
 1463 FL struct message *
 1464 setdot(struct message *mp){
 1465    NYD_ENTER;
 1466    if(dot != mp){
 1467       prevdot = dot;
 1468       n_pstate &= ~n_PS_DID_PRINT_DOT;
 1469    }
 1470    dot = mp;
 1471    uncollapse1(dot, 0);
 1472    NYD_LEAVE;
 1473    return dot;
 1474 }
 1475 
 1476 FL void
 1477 touch(struct message *mp){
 1478    NYD_ENTER;
 1479    mp->m_flag |= MTOUCH;
 1480    if(!(mp->m_flag & MREAD))
 1481       mp->m_flag |= MREAD | MSTATUS;
 1482    NYD_LEAVE;
 1483 }
 1484 
 1485 FL int
 1486 n_getmsglist(char const *buf, int *vector, int flags,
 1487    struct n_cmd_arg **capp_or_null)
 1488 {
 1489    int *ip, mc;
 1490    struct message *mp;
 1491    NYD_ENTER;
 1492 
 1493    n_pstate &= ~n_PS_ARGLIST_MASK;
 1494    n_pstate |= n_PS_MSGLIST_DIRECT;
 1495    a_msg_list_last_saw_d = a_msg_list_saw_d;
 1496    a_msg_list_saw_d = FAL0;
 1497 
 1498    *vector = 0;
 1499    if(capp_or_null != NULL)
 1500       *capp_or_null = NULL;
 1501    if(*buf == '\0'){
 1502       mc = 0;
 1503       goto jleave;
 1504    }
 1505 
 1506    /* TODO Parse the message spec into an ARGV; this should not happen here,
 1507     * TODO but instead cmd_arg_parse() should feed in the list of parsed tokens
 1508     * TODO to getmsglist(); as of today there are multiple getmsglist() users
 1509     * TODO though, and they need to deal with that, then, too */
 1510    /* C99 */{
 1511       n_CMD_ARG_DESC_SUBCLASS_DEF(getmsglist, 1, pseudo_cad){
 1512          {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_OPTION |
 1513                n_CMD_ARG_DESC_GREEDY | n_CMD_ARG_DESC_HONOUR_STOP,
 1514             n_SHEXP_PARSE_TRIM_IFSSPACE | n_SHEXP_PARSE_IFS_VAR |
 1515                n_SHEXP_PARSE_IGNORE_EMPTY}
 1516       }n_CMD_ARG_DESC_SUBCLASS_DEF_END;
 1517       struct n_cmd_arg_ctx cac;
 1518 
 1519       cac.cac_desc = n_CMD_ARG_DESC_SUBCLASS_CAST(&pseudo_cad);
 1520       cac.cac_indat = buf;
 1521       cac.cac_inlen = UIZ_MAX;
 1522       cac.cac_msgflag = flags;
 1523       cac.cac_msgmask = 0;
 1524       if(!n_cmd_arg_parse(&cac)){
 1525          mc = -1;
 1526          goto jleave;
 1527       }else if(cac.cac_no == 0){
 1528          mc = 0;
 1529          goto jleave;
 1530       }else{
 1531          /* Is this indeed a (maybe optional) message list and a target? */
 1532          if(capp_or_null != NULL){
 1533             struct n_cmd_arg *cap, **lcapp;
 1534 
 1535             if((cap = cac.cac_arg)->ca_next == NULL){
 1536                *capp_or_null = cap;
 1537                mc = 0;
 1538                goto jleave;
 1539             }
 1540             for(;;){
 1541                lcapp = &cap->ca_next;
 1542                if((cap = *lcapp)->ca_next == NULL)
 1543                   break;
 1544             }
 1545             *capp_or_null = cap;
 1546             *lcapp = NULL;
 1547 
 1548             /* In the list-and-target mode we have to take special care, since
 1549              * some commands use special call conventions historically (use the
 1550              * MBOX, search for a message, whatever).
 1551              * Thus, to allow things like "certsave '' bla" or "save '' ''",
 1552              * watch out for two argument form with empty token first.
 1553              * This special case is documented at the prototype */
 1554             if(cac.cac_arg->ca_next == NULL &&
 1555                   cac.cac_arg->ca_arg.ca_str.s[0] == '\0'){
 1556                mc = 0;
 1557                goto jleave;
 1558             }
 1559          }
 1560 
 1561          if(msgCount == 0){
 1562             mc = 0;
 1563             goto jleave;
 1564          }else if((mc = a_msg_markall(buf, cac.cac_arg, flags)) < 0){
 1565             mc = -1;
 1566             goto jleave;
 1567          }
 1568       }
 1569    }
 1570 
 1571    ip = vector;
 1572    if(n_pstate & n_PS_HOOK_NEWMAIL){
 1573       mc = 0;
 1574       for(mp = message; mp < &message[msgCount]; ++mp)
 1575          if(mp->m_flag & MMARK){
 1576             if(!(mp->m_flag & MNEWEST))
 1577                a_msg_unmark((int)PTR2SIZE(mp - message + 1));
 1578             else
 1579                ++mc;
 1580          }
 1581       if(mc == 0){
 1582          mc = -1;
 1583          goto jleave;
 1584       }
 1585    }
 1586 
 1587    if(mb.mb_threaded == 0){
 1588       for(mp = message; mp < &message[msgCount]; ++mp)
 1589          if(mp->m_flag & MMARK)
 1590             *ip++ = (int)PTR2SIZE(mp - message + 1);
 1591    }else{
 1592       for(mp = threadroot; mp != NULL; mp = next_in_thread(mp))
 1593          if(mp->m_flag & MMARK)
 1594             *ip++ = (int)PTR2SIZE(mp - message + 1);
 1595    }
 1596    *ip = 0;
 1597    mc = (int)PTR2SIZE(ip - vector);
 1598    if(mc != 1)
 1599       n_pstate &= ~n_PS_MSGLIST_DIRECT;
 1600 jleave:
 1601    NYD_LEAVE;
 1602    return mc;
 1603 }
 1604 
 1605 FL int
 1606 first(int f, int m)
 1607 {
 1608    struct message *mp;
 1609    int rv;
 1610    NYD_ENTER;
 1611 
 1612    if (msgCount == 0) {
 1613       rv = 0;
 1614       goto jleave;
 1615    }
 1616 
 1617    f &= MDELETED;
 1618    m &= MDELETED;
 1619    for (mp = dot;
 1620          mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
 1621          mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
 1622       if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
 1623          rv = (int)PTR2SIZE(mp - message + 1);
 1624          goto jleave;
 1625       }
 1626    }
 1627 
 1628    if (dot > message) {
 1629       for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
 1630             mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
 1631          if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
 1632             rv = (int)PTR2SIZE(mp - message + 1);
 1633             goto jleave;
 1634          }
 1635       }
 1636    }
 1637    rv = 0;
 1638 jleave:
 1639    NYD_LEAVE;
 1640    return rv;
 1641 }
 1642 
 1643 FL void
 1644 mark(int mno, int f){
 1645    struct message *mp;
 1646    int i;
 1647    NYD_ENTER;
 1648 
 1649    i = mno;
 1650    if(i < 1 || i > msgCount)
 1651       n_panic(_("Bad message number to mark"));
 1652    mp = &message[--i];
 1653 
 1654    if(mb.mb_threaded == 1 && a_msg_threadflag)
 1655       a_msg__threadmark(mp, f);
 1656    else{
 1657       assert(!(mp->m_flag & MHIDDEN));
 1658       mp->m_flag |= MMARK;
 1659    }
 1660    NYD_LEAVE;
 1661 }
 1662 
 1663 /* s-it-mode */