From 854ecc5e92a29f14983fc3c9c2edeb4980949568 Mon Sep 17 00:00:00 2001 From: Eduardo Chappa Date: Mon, 21 May 2018 20:52:20 -0600 Subject: * Add the "g" option to the select command that works in IMAP servers that implement the X-GM-EXT-1 capability (such as the one offered by Gmail.) This allows users to do selection in Alpine as if they were doing a search in the web interface for Gmail. --- alpine/mailcmd.c | 162 +++++++++++++++++++++++++++-- imap/src/c-client/imap4r1.c | 244 +++++++++++++++++++++++++++++++++++++++++++- imap/src/c-client/imap4r1.h | 6 ++ imap/src/c-client/mail.c | 1 + imap/src/c-client/mail.h | 1 + pith/mailcmd.c | 6 ++ pith/pine.hlp | 7 ++ 7 files changed, 418 insertions(+), 9 deletions(-) diff --git a/alpine/mailcmd.c b/alpine/mailcmd.c index b741261b..8f395a1b 100644 --- a/alpine/mailcmd.c +++ b/alpine/mailcmd.c @@ -105,6 +105,7 @@ int select_by_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **); int select_by_thrd_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **); int select_by_date(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **); int select_by_text(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **); +int select_by_gm_content(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **); int select_by_size(MAILSTREAM *, SEARCHSET **); SEARCHSET *visible_searchset(MAILSTREAM *, MSGNO_S *); int select_by_status(MAILSTREAM *, SEARCHSET **); @@ -137,6 +138,8 @@ ESCKEY_S sel_opts1[] = { #define SEL_OPTS_THREAD 9 /* index number of "tHread" */ #define SEL_OPTS_THREAD_CH 'h' +#define SEL_OPTS_XGMEXT 10 /* index number of "boX" */ +#define SEL_OPTS_XGMEXT_CH 'g' char *sel_pmt2 = "SELECT criteria : "; static ESCKEY_S sel_opts2[] = { @@ -199,6 +202,51 @@ static ESCKEY_S sel_opts4[] = { {-1, 0, NULL, NULL} }; +static ESCKEY_S sel_opts5[] = { + /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur + means select the currently highlighted message; select by Number is by message + number; Status is by status of the message, for example the message might be + New or it might be Unseen or marked Important; Size has the Z upper case because + it is a Z command; Keyword is an alpine keyword that has been set by the user; + and Rule is an alpine rule */ + {'a', 'a', "A", N_("select All")}, + {'c', 'c', "C", N_("select Cur")}, + {'n', 'n', "N", N_("Number")}, + {'d', 'd', "D", N_("Date")}, + {'t', 't', "T", N_("Text")}, + {'s', 's', "S", N_("Status")}, + {'z', 'z', "Z", N_("siZe")}, + {'k', 'k', "K", N_("Keyword")}, + {'r', 'r', "R", N_("Rule")}, + {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")}, + {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("GmSearch")}, + {-1, 0, NULL, NULL}, + {-1, 0, NULL, NULL}, + {-1, 0, NULL, NULL}, + {-1, 0, NULL, NULL}, + {-1, 0, NULL, NULL} +}; + +static ESCKEY_S sel_opts6[] = { + {'a', 'a', "A", N_("select All")}, + /* TRANSLATORS: select currrently highlighted message Thread */ + {'c', 'c', "C", N_("select Curthrd")}, + {'n', 'n', "N", N_("Number")}, + {'d', 'd', "D", N_("Date")}, + {'t', 't', "T", N_("Text")}, + {'s', 's', "S", N_("Status")}, + {'z', 'z', "Z", N_("siZe")}, + {'k', 'k', "K", N_("Keyword")}, + {'r', 'r', "R", N_("Rule")}, + {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")}, + {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("Gmail")}, + {-1, 0, NULL, NULL}, + {-1, 0, NULL, NULL}, + {-1, 0, NULL, NULL}, + {-1, 0, NULL, NULL}, + {-1, 0, NULL, NULL} +}; + static char *sel_flag = N_("Select New, Deleted, Answered, Forwarded, or Important messages ? "); @@ -236,6 +284,8 @@ static ESCKEY_S sel_date_opt[] = { }; +static char *sel_x_gm_ext = + N_("Search: "); static char *sel_text = N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? "); static char *sel_text_not = @@ -6915,12 +6965,24 @@ aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_in mm_search_stream = state->mail_stream; mm_search_count = 0L; - sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2; + if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)) + sel_opts = THRD_INDX() ? sel_opts6 : sel_opts5; + else + sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2; + if(THREADING()){ sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH; } else{ - sel_opts[SEL_OPTS_THREAD].ch = -1; + if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)){ + sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_XGMEXT_CH; + sel_opts[SEL_OPTS_THREAD].rval = sel_opts[SEL_OPTS_XGMEXT].rval; + sel_opts[SEL_OPTS_THREAD].name = sel_opts[SEL_OPTS_XGMEXT].name; + sel_opts[SEL_OPTS_THREAD].label = sel_opts[SEL_OPTS_XGMEXT].label; + sel_opts[SEL_OPTS_XGMEXT].ch = -1; + } + else + sel_opts[SEL_OPTS_THREAD].ch = -1; } if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){ @@ -6944,8 +7006,13 @@ aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_in } sel_opts += 2; /* disable extra options */ - switch(q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP, - RB_NORM)){ + if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)) + q = double_radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP, + RB_NORM); + else + q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP, + RB_NORM); + switch(q){ case 'f' : /* flip selection */ msgno = 0L; for(i = 1L; i <= mn_get_total(msgmap); i++){ @@ -6981,8 +7048,12 @@ aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_in if(!q){ while(1){ - q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', - NO_HELP, RB_NORM|RB_RET_HELP); + if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)) + q = double_radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP, + RB_NORM); + else + q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP, + RB_NORM|RB_RET_HELP); if(q == 3){ helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE); @@ -7081,6 +7152,11 @@ aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_in rv = select_by_thread(state->mail_stream, msgmap, &limitsrch); break; + case 'g' : /* X-GM-EXT-1 */ + ret = 0; + rv = select_by_gm_content(state->mail_stream, msgmap, mn_get_cur(msgmap), &limitsrch); + break; + default : q_status_message(SM_ORDER | SM_DING, 3, 3, "Unsupported Select option"); @@ -8118,6 +8194,80 @@ select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET ** return(0); } +/* + * Select by searching in message headers or body + * using the x-gm-ext-1 capaility. This function + * reads the input from the user and passes it to + * the server directly. We need a x-gm-ext-1 variable + * in the search pgm to carry this information to the + * server. + * + * Sets searched bits in mail_elts + * + * Args limitsrch -- limit search to this searchset + * + * Returns 0 on success. + */ +int +select_by_gm_content(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch) +{ + int r, we_cancel = 0, rv; + char tmp[128]; + char namehdr[MAILTMPLEN]; + ESCKEY_S ekey[8]; + HelpType help; + + ps_global->mangled_footer = 1; + ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1; + + strncpy(tmp, sel_x_gm_ext, sizeof(tmp)-1); + tmp[sizeof(tmp)-1] = '\0'; + + namehdr[0] = '\0'; + + help = NO_HELP; + while (1){ + int flags = OE_APPEND_CURRENT; + + r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0, + sizeof(namehdr), tmp, ekey, help, &flags); + + if(r == 4) + continue; + + if(r == 3){ + help = (help == NO_HELP) ? h_select_by_gm_content : NO_HELP; + continue; + } + + if (r == 1){ + cmd_cancelled("Selection by content"); + return(1); + } + + removing_leading_white_space(namehdr); + + if ((namehdr[0] != '\0') + && isspace((unsigned char) namehdr[strlen(namehdr) - 1])) + removing_trailing_white_space(namehdr); + + if (namehdr[0] != '\0') + break; + } + + if(ps_global && ps_global->ttyo){ + blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); + ps_global->mangled_footer = 1; + } + + we_cancel = busy_cue(_("Selecting"), NULL, 1); + + rv = agg_text_select(stream, msgmap, 'g', namehdr, 0, 0, NULL, "utf-8", limitsrch); + if(we_cancel) + cancel_busy_cue(0); + + return(rv); +} /* * Select by searching in message headers or body. diff --git a/imap/src/c-client/imap4r1.c b/imap/src/c-client/imap4r1.c index 13aaadab..343e29bf 100644 --- a/imap/src/c-client/imap4r1.c +++ b/imap/src/c-client/imap4r1.c @@ -1,7 +1,7 @@ /* - * Copyright 2016 Eduardo Chappa + * Copyright 2016-2018 Eduardo Chappa * - * Last Edited: February 1, 2015 Eduardo Chappa + * Last Edited: May 5, 2018 Eduardo Chappa * */ /* ======================================================================== @@ -191,6 +191,7 @@ unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno); unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid); void imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags); long imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags); +long imap_search_x_gm_ext1 (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags); unsigned long *imap_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, SORTPGM *pgm,long flags); THREADNODE *imap_thread (MAILSTREAM *stream,char *type,char *charset, @@ -2023,6 +2024,10 @@ long imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags) char *s; IMAPPARSEDREPLY *reply; MESSAGECACHE *elt; + + if(LOCAL->cap.x_gm_ext1 && pgm && pgm->x_gm_ext1) + return imap_search_x_gm_ext1(stream, charset, pgm, flags); + if ((flags & SE_NOSERVER) || /* if want to do local search */ LOCAL->loser || /* or loser */ (!LEVELIMAP4 (stream) && /* or old server but new functions... */ @@ -3339,7 +3344,239 @@ IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s, } return NIL; /* success */ } - + +#define ADD_STRING(X, Y) { \ + if(remain > 0){ \ + sprintf (u, (X), (Y)); \ + len = strlen(u); \ + if(len < remain){ \ + remain -= len; \ + strncpy(t, u, strlen(u)); \ + t[strlen(u)] = '\0'; \ + t += strlen (t); \ + } \ + } \ + } + + +long imap_search_x_gm_ext1 (MAILSTREAM *stream, char *charset, SEARCHPGM *pgm, long flags) +{ + char *cmd = (flags & SE_UID) ? "UID SEARCH X-GM-RAW" : "SEARCH X-GM-RAW"; + char *p, *t, s[MAILTMPLEN+1], u[MAILTMPLEN], v[MAILTMPLEN]; + IMAPARG *args[4],apgm,aatt,achs;; + IMAPPARSEDREPLY *reply; + unsigned long i,j,k; + MESSAGECACHE *elt; + size_t remain = sizeof(s) - 1, len; + NETMBX mb; + + u[0] = s[0] = '\0'; + t = s; + args[1] = args[2] = args[3] = NIL; + + if(pgm->x_gm_ext1) + ADD_STRING(" %s", pgm->x_gm_ext1->text.data); + +#if 0 /* maybe later */ + if (pgm->larger) + ADD_STRING(" larger:%lu", pgm->larger); + + if (pgm->smaller) + ADD_STRING(" smaller:%lu", pgm->smaller); + + if (pgm->deleted) + ADD_STRING(" %s", "in:trash"); + + if (pgm->undeleted) + ADD_STRING(" %s", "-in:trash"); + + if (pgm->draft) + ADD_STRING(" %s", "in:drafts"); + + if (pgm->undraft) + ADD_STRING(" %s", "-in:drafts"); + + if (pgm->flagged) + ADD_STRING(" %s", "is:starred"); + + if (pgm->unflagged) + ADD_STRING(" %s", "-is:starred"); + + if (pgm->seen) + ADD_STRING(" %s", "-is:unread"); + + if (pgm->unseen) + ADD_STRING(" %s", "is:unread"); + + if (pgm->keyword){ + STRINGLIST *sl; + for(sl = pgm->keyword; remain > 0 && sl; sl = sl->next) + ADD_STRING(" label:%s", sl->text.data); + } + + if (pgm->unkeyword){ + STRINGLIST *sl; + for(sl = pgm->unkeyword; remain > 0 && sl; sl = sl->next) + ADD_STRING(" -label:%s", sl->text.data); + } + + if (pgm->sentbefore){ + unsigned short date = pgm->sentbefore; + sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9), + (date >> 5) & 0xf, date & 0x1f); + ADD_STRING(" before:%s", v); + } + + if (pgm->sentsince){ + unsigned short date = pgm->sentsince; + sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9), + (date >> 5) & 0xf, date & 0x1f); + ADD_STRING(" older:%s", v); + } + + if (pgm->before){ + unsigned short date = pgm->before; + sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9), + (date >> 5) & 0xf, date & 0x1f); + ADD_STRING(" before:%s", v); + } + + if (pgm->since){ + unsigned short date = pgm->since; + sprintf(v, "%d/%d/%d", BASEYEAR + (date >> 9), + (date >> 5) & 0xf, date & 0x1f); + ADD_STRING(" before:%s", v); + } + + if (pgm->older){ + sprintf(v, "%dd", pgm->older/86400); + ADD_STRING(" older_than:%s", v); + } + + if (pgm->younger){ + sprintf(v, "%dd", pgm->younger/86400); + ADD_STRING(" newer_than:%s", v); + } + + if(pgm->bcc){ + STRINGLIST *sl; + ADD_STRING("%s", pgm->bcc->next ? " {" : " "); + for(sl = pgm->bcc; remain > 0 && sl; sl = sl->next){ + ADD_STRING("bcc:%s", sl->text.data); + ADD_STRING("%s", sl->next ? " " : "}"); + } + } + + if(pgm->cc){ + STRINGLIST *sl; + ADD_STRING("%s", pgm->cc->next ? " {" : " "); + for(sl = pgm->cc; remain > 0 && sl; sl = sl->next){ + ADD_STRING("cc:%s", sl->text.data); + ADD_STRING("%s", sl->next ? " " : "}"); + } + } + + if(pgm->from){ + STRINGLIST *sl; + ADD_STRING("%s", pgm->from->next ? " {" : " "); + for(sl = pgm->from; remain > 0 && sl; sl = sl->next){ + ADD_STRING("from:%s", sl->text.data); + ADD_STRING("%s", sl->next ? " " : (pgm->from->next ? "}" : "")); + } + } + + if(pgm->to){ + STRINGLIST *sl; + ADD_STRING("%s", pgm->to->next ? " {" : " "); + for(sl = pgm->to; remain > 0 && sl; sl = sl->next){ + ADD_STRING("to:%s", sl->text.data); + ADD_STRING("%s", sl->next ? " " : (pgm->to->next ? "}" : "")); + } + } + + if(pgm->subject){ + STRINGLIST *sl; + ADD_STRING("%s", pgm->subject->next ? " {" : " "); + for(sl = pgm->subject; remain > 0 && sl; sl = sl->next){ + ADD_STRING("subject:(%s)", sl->text.data); + ADD_STRING("%s", sl->next ? " " : (pgm->subject->next ? "}" : "")); + } + } + + if(pgm->body){ + STRINGLIST *sl; + ADD_STRING("%s", pgm->body->next ? " {" : " "); + for(sl = pgm->body; remain > 0 && sl; sl = sl->next){ + ADD_STRING(" %s", sl->text.data); + ADD_STRING("%s", sl->next ? " " : (pgm->body->next ? "}" : "")); + } + } + + if (mail_valid_net_parse (stream->mailbox,&mb)){ + p = strchr(mb.mailbox, '/'); + ADD_STRING(" in:%s", p ? p+1 : "inbox"); + } + +#endif /* maybe later */ + + s[0] = '\"'; + strcat(t, "\""); + + apgm.type = ATOM; apgm.text = (void *) s; + args[0] = &apgm; + args[1] = NIL; + LOCAL->uidsearch = (flags & SE_UID) ? T : NIL; + reply = imap_send (stream,cmd,args); + LOCAL->uidsearch = NIL; + /* do locally if server won't grok */ + if (!strcmp (reply->key,"BAD")) { + if ((flags & SE_NOLOCAL) || + !mail_search_default (stream,charset,pgm,flags | SE_NOSERVER)) + return NIL; + } + else if (!imap_OK (stream,reply)) { + mm_log (reply->text,ERROR); + return NIL; + } + + /* can never pre-fetch with a short cache */ + if ((k = imap_prefetch) && !(flags & (SE_NOPREFETCH | SE_UID)) && + !stream->scache) { /* only if prefetching permitted */ + t = LOCAL->tmp; /* build sequence in temporary buffer */ + *t = '\0'; /* initially nothing */ + /* search through mailbox */ + for (i = 1; k && (i <= stream->nmsgs); ++i) + /* for searched messages with no envelope */ + if ((elt = mail_elt (stream,i)) && elt->searched && + !mail_elt (stream,i)->private.msg.env) { + /* prepend with comma if not first time */ + if (LOCAL->tmp[0]) *t++ = ','; + sprintf (t,"%lu",j = i);/* output message number */ + t += strlen (t); /* point at end of string */ + k--; /* count one up */ + /* search for possible end of range */ + while (k && (i < stream->nmsgs) && + (elt = mail_elt (stream,i+1))->searched && + !elt->private.msg.env) i++,k--; + if (i != j) { /* if a range */ + sprintf (t,":%lu",i); /* output delimiter and end of range */ + t += strlen (t); /* point at end of string */ + } + if ((t - LOCAL->tmp) > (IMAPTMPLEN - 50)) break; + } + if (LOCAL->tmp[0]) { /* anything to pre-fetch? */ + /* pre-fetch envelopes for the first imap_prefetch number of messages */ + if (!imap_OK (stream,reply = + imap_fetch (stream,t = cpystr (LOCAL->tmp),FT_NEEDENV + + ((flags & SE_NOHDRS) ? FT_NOHDRS : NIL) + + ((flags & SE_NEEDBODY) ? FT_NEEDBODY : NIL)))) + mm_log (reply->text,ERROR); + fs_give ((void **) &t); /* flush copy of sequence */ + } + } + return LONGT; +} + /* IMAP send search program * Accepts: MAIL stream * reply tag @@ -5663,6 +5900,7 @@ void imap_parse_capabilities (MAILSTREAM *stream,char *t) else if (!compare_cstring (t,"CATENATE")) LOCAL->cap.catenate = T; else if (!compare_cstring (t,"CONDSTORE")) LOCAL->cap.condstore = T; else if (!compare_cstring (t,"ESEARCH")) LOCAL->cap.esearch = T; + else if (!compare_cstring (t,"X-GM-EXT-1")) LOCAL->cap.x_gm_ext1 = T; else if (((t[0] == 'S') || (t[0] == 's')) && ((t[1] == 'O') || (t[1] == 'o')) && ((t[2] == 'R') || (t[2] == 'r')) && diff --git a/imap/src/c-client/imap4r1.h b/imap/src/c-client/imap4r1.h index 8ee8a186..520b6562 100644 --- a/imap/src/c-client/imap4r1.h +++ b/imap/src/c-client/imap4r1.h @@ -87,6 +87,7 @@ typedef struct imap_cap { unsigned int esearch : 1; /* server has ESEARCH (RFC 4731) */ unsigned int within : 1; /* server has WITHIN (RFC 5032) */ unsigned int extlevel; /* extension data level supported by server */ + unsigned int x_gm_ext1:1; /* special extension for gmail server */ /* supported authenticators */ unsigned int auth : MAXAUTHENTICATORS; THREADER *threader; /* list of threaders */ @@ -129,6 +130,11 @@ typedef struct imap_cap { #define LEVELACL(stream) imap_cap (stream)->acl +/* Has X-GM-EXT-1 extension */ + +#define XGMEXT1(stream) imap_cap(stream)->x_gm_ext1 + + /* Has QUOTA extension */ #define LEVELQUOTA(stream) imap_cap (stream)->quota diff --git a/imap/src/c-client/mail.c b/imap/src/c-client/mail.c index 02519e5a..b18f7732 100644 --- a/imap/src/c-client/mail.c +++ b/imap/src/c-client/mail.c @@ -5990,6 +5990,7 @@ void mail_free_searchpgm (SEARCHPGM **pgm) mail_free_stringlist (&(*pgm)->subject); mail_free_stringlist (&(*pgm)->text); mail_free_stringlist (&(*pgm)->to); + mail_free_stringlist (&(*pgm)->x_gm_ext1); fs_give ((void **) pgm); /* return program to free storage */ } } diff --git a/imap/src/c-client/mail.h b/imap/src/c-client/mail.h index 11ebdbc9..fc3f3862 100644 --- a/imap/src/c-client/mail.h +++ b/imap/src/c-client/mail.h @@ -996,6 +996,7 @@ SEARCHPGM { /* search program */ STRINGLIST *subject; /* text in subject */ STRINGLIST *text; /* text in headers and body */ STRINGLIST *to; /* to recipients */ + STRINGLIST *x_gm_ext1; /* use X-GM-EXT-1 extension */ unsigned long larger; /* larger than this size */ unsigned long smaller; /* smaller than this size */ unsigned long older; /* older than this interval */ diff --git a/pith/mailcmd.c b/pith/mailcmd.c index 609578fc..c5544f20 100644 --- a/pith/mailcmd.c +++ b/pith/mailcmd.c @@ -2342,6 +2342,12 @@ agg_text_select(MAILSTREAM *stream, MSGNO_S *msgmap, char type, char *namehdr, if(!mepgm) switch(type){ + case 'g' : /* X-GM-EXT-1 */ + pgm->x_gm_ext1 = mail_newstringlist(); + pgm->x_gm_ext1->text.data = (unsigned char *) cpystr(namehdr); + pgm->x_gm_ext1->text.size = strlen(namehdr); + break; + case 'h' : /* Any header */ pgm->header = mail_newsearchheader (namehdr, sstring); break; diff --git a/pith/pine.hlp b/pith/pine.hlp index a1d8ea1f..104ffa73 100644 --- a/pith/pine.hlp +++ b/pith/pine.hlp @@ -196,6 +196,10 @@ these contributed by Helmut Grohne. See password file encryption key. This allows users to use their password file without entering a master password. +
  • Add the "g" option to the select command that works in IMAP +servers that implement the X-GM-EXT-1 capability (such as the one offered +by Gmail.) This allows users to do selection in Alpine as if they were +doing a search in the web interface for Gmail.

    @@ -36847,6 +36851,9 @@ attachment, or ^C to cancel. ========== h_select_by_num ========== Enter a list of message numbers (or number ranges), or ^C to cancel. "end" is the last message. "." is the current message. Example: 1-.,7-9,11,19,35-end +========== h_select_by_gm_content ========== +Enter your search key in the same way that you would enter a search +key in the web interface for Gmail. ========== h_select_by_thrdnum ========== Enter a list of message numbers (or number ranges), or ^C to cancel. "end" is the last message. "." is the current message. Example: 1-.,7-9,11,19,35-end -- cgit v1.2.3-70-g09d2