"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.10/message.c" (25 Mar 2018, 44968 Bytes) of package /linux/misc/s-nail-14.9.10.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 last Fossies "Diffs" side-by-side code changes report: 14.9.6_vs_14.9.7.

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