Show the Content-Description:, as applicable
[s-mailx.git] / folder.c
blob3af43169cc081e64379a43d12629ceaf11a4cbde
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Folder (mailbox) initialization, newmail announcement and related.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE folder
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 #include <pwd.h>
44 /* Update mailname (if name != NULL) and displayname, return whether displayname
45 * was large enough to swallow mailname */
46 static bool_t _update_mailname(char const *name);
47 #ifdef HAVE_C90AMEND1 /* TODO unite __narrow_suffix() into one fun! */
48 SINLINE size_t __narrow_suffix(char const *cp, size_t cpl, size_t maxl);
49 #endif
51 #ifdef HAVE_C90AMEND1
52 SINLINE size_t
53 __narrow_suffix(char const *cp, size_t cpl, size_t maxl)
55 int err;
56 size_t i, ok;
57 NYD_ENTER;
59 for (err = ok = i = 0; cpl > maxl || err;) {
60 int ml = mblen(cp, cpl);
61 if (ml < 0) { /* XXX _narrow_suffix(): mblen() error; action? */
62 (void)mblen(NULL, 0);
63 err = 1;
64 ml = 1;
65 } else {
66 if (!err)
67 ok = i;
68 err = 0;
69 if (ml == 0)
70 break;
72 cp += ml;
73 i += ml;
74 cpl -= ml;
76 NYD_LEAVE;
77 return ok;
79 #endif /* HAVE_C90AMEND1 */
81 static bool_t
82 _update_mailname(char const *name)
84 char *mailp, *dispp;
85 size_t i, j;
86 bool_t rv = TRU1;
87 NYD_ENTER;
89 /* Don't realpath(3) if it's only an update request */
90 if (name != NULL) {
91 #ifdef HAVE_REALPATH
92 enum protocol p = which_protocol(name);
94 if (p == PROTO_FILE || p == PROTO_MAILDIR) {
95 if (realpath(name, mailname) == NULL && errno != ENOENT) {
96 n_err(_("Can't canonicalize %s\n"), n_shexp_quote_cp(name, FAL0));
97 rv = FAL0;
98 goto jdocopy;
100 } else
101 jdocopy:
102 #endif
103 n_strscpy(mailname, name, sizeof(mailname));
106 mailp = mailname;
107 dispp = displayname;
109 /* Don't display an absolute path but "+FOLDER" if under *folder* */
110 /* C99 */{
111 char const *folderp;
113 if(*(folderp = folder_query()) != '\0'){
114 i = strlen(folderp);
115 if(!strncmp(folderp, mailp, i)){
116 mailp += i;
117 *dispp++ = '+';
122 /* We want to see the name of the folder .. on the screen */
123 i = strlen(mailp);
124 if (i < sizeof(displayname) -1)
125 memcpy(dispp, mailp, i +1);
126 else {
127 rv = FAL0;
128 /* Avoid disrupting multibyte sequences (if possible) */
129 #ifndef HAVE_C90AMEND1
130 j = sizeof(displayname) / 3 - 1;
131 i -= sizeof(displayname) - (1/* + */ + 3) - j;
132 #else
133 j = field_detect_clip(sizeof(displayname) / 3, mailp, i);
134 i = j + __narrow_suffix(mailp + j, i - j,
135 sizeof(displayname) - (1/* + */ + 3 + 1) - j);
136 #endif
137 snprintf(dispp, sizeof(displayname), "%.*s...%s",
138 (int)j, mailp, mailp + i);
141 /* C99 */{
142 bool_t reset = !(pstate & PS_ROOT);
144 pstate |= PS_ROOT;
145 ok_vset(_mailbox_resolved, mailname);
146 ok_vset(_mailbox_display, displayname);
147 if(reset)
148 pstate &= ~PS_ROOT;
150 NYD_LEAVE;
151 return rv;
154 FL int
155 setfile(char const *name, enum fedit_mode fm) /* TODO oh my god */
157 /* Note we don't 'userid(myname) != getuid()', preliminary steps are usually
158 * necessary to make a mailbox accessible by a different user, and if that
159 * has happened, let's just let the usual file perms decide */
160 static int shudclob;
162 struct stat stb;
163 size_t offset;
164 char const *who;
165 int rv, omsgCount = 0;
166 FILE *ibuf = NULL, *lckfp = NULL;
167 bool_t isdevnull = FAL0;
168 NYD_ENTER;
170 pstate &= ~PS_SETFILE_OPENED;
172 /* C99 */{
173 enum fexp_mode fexpm;
175 if((who = shortcut_expand(name)) != NULL){
176 fexpm = FEXP_NSHORTCUT | FEXP_NSHELL;
177 name = who;
178 }else
179 fexpm = FEXP_NSHELL;
181 if(name[0] == '%'){
182 fm |= FEDIT_SYSBOX; /* TODO fexpand() needs to tell is-valid-user! */
183 if(*(who = &name[1]) == ':')
184 ++who;
185 if(*who == '\0')
186 who = myname;
187 }else
188 who = myname;
190 if ((name = fexpand(name, fexpm)) == NULL)
191 goto jem1;
194 /* For at least substdate() users */
195 time_current_update(&time_current, FAL0);
197 switch (which_protocol(name)) {
198 case PROTO_FILE:
199 if (temporary_protocol_ext != NULL)
200 name = savecat(name, temporary_protocol_ext);
201 isdevnull = ((options & OPT_BATCH_FLAG) && !strcmp(name, "/dev/null"));
202 #ifdef HAVE_REALPATH
203 do { /* TODO we need objects, goes away then */
204 # ifdef HAVE_REALPATH_NULL
205 char *cp;
207 if ((cp = realpath(name, NULL)) != NULL) {
208 name = savestr(cp);
209 (free)(cp);
211 # else
212 char cbuf[PATH_MAX];
214 if (realpath(name, cbuf) != NULL)
215 name = savestr(cbuf);
216 # endif
217 } while (0);
218 #endif
219 rv = 1;
220 break;
221 case PROTO_MAILDIR:
222 shudclob = 1;
223 rv = maildir_setfile(name, fm);
224 goto jleave;
225 #ifdef HAVE_POP3
226 case PROTO_POP3:
227 shudclob = 1;
228 rv = pop3_setfile(name, fm);
229 goto jleave;
230 #endif
231 default:
232 n_err(_("Cannot handle protocol: %s\n"), name);
233 goto jem1;
236 if ((ibuf = Zopen(name, "r")) == NULL) {
237 int e = errno;
239 if ((fm & FEDIT_SYSBOX) && e == ENOENT) {
240 if (strcmp(who, myname) && getpwnam(who) == NULL) {
241 n_err(_("%s is not a user of this system\n"),
242 n_shexp_quote_cp(who, FAL0));
243 goto jem2;
245 if (!(options & OPT_QUICKRUN_MASK) && ok_blook(bsdcompat))
246 n_err(_("No mail for %s\n"), who);
248 if (fm & FEDIT_NEWMAIL)
249 goto jleave;
251 mb.mb_type = MB_VOID;
252 if (ok_blook(emptystart)) {
253 if (!(options & OPT_QUICKRUN_MASK) && !ok_blook(bsdcompat))
254 n_perr(name, e);
255 /* We must avoid returning -1 and causing program exit */
256 rv = 1;
257 goto jleave;
259 n_perr(name, e);
260 goto jem1;
263 if (fstat(fileno(ibuf), &stb) == -1) {
264 if (fm & FEDIT_NEWMAIL)
265 goto jleave;
266 n_perr(_("fstat"), 0);
267 goto jem1;
270 if (S_ISREG(stb.st_mode) || isdevnull) {
271 /* EMPTY */
272 } else {
273 if (fm & FEDIT_NEWMAIL)
274 goto jleave;
275 errno = S_ISDIR(stb.st_mode) ? EISDIR : EINVAL;
276 n_perr(name, 0);
277 goto jem1;
280 if (shudclob && !(fm & FEDIT_NEWMAIL) && !quit(FAL0))
281 goto jem2;
283 hold_sigs();
285 #ifdef HAVE_SOCKETS
286 if (!(fm & FEDIT_NEWMAIL) && mb.mb_sock.s_fd >= 0)
287 sclose(&mb.mb_sock); /* TODO sorry? VMAILFS->close(), thank you */
288 #endif
290 /* TODO There is no intermediate VOID box we've switched to: name may
291 * TODO point to the same box that we just have written, so any updates
292 * TODO we won't see! Reopen again in this case. RACY! Goes with VOID! */
293 /* TODO In addition: in case of compressed/hook boxes we lock a temporary! */
294 /* TODO We may uselessly open twice but quit() doesn't say whether we were
295 * TODO modified so we can't tell: Mailbox::is_modified() :-(( */
296 if (/*shudclob && !(fm & FEDIT_NEWMAIL) &&*/ !strcmp(name, mailname)) {
297 name = mailname;
298 Fclose(ibuf);
300 if ((ibuf = Zopen(name, "r")) == NULL ||
301 fstat(fileno(ibuf), &stb) == -1 ||
302 (!S_ISREG(stb.st_mode) && !isdevnull)) {
303 n_perr(name, 0);
304 rele_sigs();
305 goto jem2;
309 /* Copy the messages into /tmp and set pointers */
310 if (!(fm & FEDIT_NEWMAIL)) {
311 if (isdevnull) {
312 mb.mb_type = MB_VOID;
313 mb.mb_perm = 0;
314 } else {
315 mb.mb_type = MB_FILE;
316 mb.mb_perm = (((options & OPT_R_FLAG) || (fm & FEDIT_RDONLY) ||
317 access(name, W_OK) < 0) ? 0 : MB_DELE | MB_EDIT);
318 if (shudclob) {
319 if (mb.mb_itf) {
320 fclose(mb.mb_itf);
321 mb.mb_itf = NULL;
323 if (mb.mb_otf) {
324 fclose(mb.mb_otf);
325 mb.mb_otf = NULL;
329 shudclob = 1;
330 if (fm & FEDIT_SYSBOX)
331 pstate &= ~PS_EDIT;
332 else
333 pstate |= PS_EDIT;
334 initbox(name);
335 offset = 0;
336 } else {
337 fseek(mb.mb_otf, 0L, SEEK_END);
338 /* TODO Doing this without holding a lock is.. And not err checking.. */
339 fseek(ibuf, mailsize, SEEK_SET);
340 offset = mailsize;
341 omsgCount = msgCount;
344 if (isdevnull)
345 lckfp = (FILE*)-1;
346 else if (!(pstate & PS_EDIT))
347 lckfp = n_dotlock(name, fileno(ibuf), FLT_READ, offset,0,
348 (fm & FEDIT_NEWMAIL ? 0 : UIZ_MAX));
349 else if (n_file_lock(fileno(ibuf), FLT_READ, offset,0,
350 (fm & FEDIT_NEWMAIL ? 0 : UIZ_MAX)))
351 lckfp = (FILE*)-1;
353 if (lckfp == NULL) {
354 if (!(fm & FEDIT_NEWMAIL)) {
355 char const *emsg = (pstate & PS_EDIT)
356 ? N_("Unable to lock mailbox, aborting operation")
357 : N_("Unable to (dot) lock mailbox, aborting operation");
358 n_perr(V_(emsg), 0);
360 rele_sigs();
361 if (!(fm & FEDIT_NEWMAIL))
362 goto jem1;
363 goto jleave;
366 mailsize = fsize(ibuf);
368 /* TODO This is too simple minded? We should regenerate an index file
369 * TODO to be able to truly tell whether *anything* has changed! */
370 if ((fm & FEDIT_NEWMAIL) && UICMP(z, mailsize, <=, offset)) {
371 rele_sigs();
372 goto jleave;
374 setptr(ibuf, offset);
375 setmsize(msgCount);
376 if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted) {
377 mb.mb_threaded = 0;
378 c_sort((void*)-1);
381 Fclose(ibuf);
382 ibuf = NULL;
383 if (lckfp != NULL && lckfp != (FILE*)-1) {
384 Pclose(lckfp, FAL0);
385 /*lckfp = NULL;*/
388 if (!(fm & FEDIT_NEWMAIL)) {
389 pstate &= ~PS_SAW_COMMAND;
390 pstate |= PS_SETFILE_OPENED;
393 rele_sigs();
395 if ((options & OPT_EXISTONLY) && !(options & OPT_HEADERLIST)) {
396 rv = (msgCount == 0);
397 goto jleave;
400 if ((!(pstate & PS_EDIT) || (fm & FEDIT_NEWMAIL)) && msgCount == 0) {
401 if (!(fm & FEDIT_NEWMAIL)) {
402 if (!ok_blook(emptystart))
403 n_err(_("No mail for %s\n"), who);
405 goto jleave;
408 if (fm & FEDIT_NEWMAIL)
409 newmailinfo(omsgCount);
411 rv = 0;
412 jleave:
413 if (ibuf != NULL) {
414 Fclose(ibuf);
415 if (lckfp != NULL && lckfp != (FILE*)-1)
416 Pclose(lckfp, FAL0);
418 NYD_LEAVE;
419 return rv;
420 jem2:
421 mb.mb_type = MB_VOID;
422 jem1:
423 rv = -1;
424 goto jleave;
427 FL int
428 newmailinfo(int omsgCount)
430 int mdot, i;
431 NYD_ENTER;
433 for (i = 0; i < omsgCount; ++i)
434 message[i].m_flag &= ~MNEWEST;
436 if (msgCount > omsgCount) {
437 for (i = omsgCount; i < msgCount; ++i)
438 message[i].m_flag |= MNEWEST;
439 printf(_("New mail has arrived.\n"));
440 if ((i = msgCount - omsgCount) == 1)
441 printf(_("Loaded 1 new message.\n"));
442 else
443 printf(_("Loaded %d new messages.\n"), i);
444 } else
445 printf(_("Loaded %d messages.\n"), msgCount);
447 check_folder_hook(TRU1);
449 mdot = getmdot(1);
451 if (ok_blook(header))
452 print_headers(omsgCount + 1, msgCount, FAL0);
453 NYD_LEAVE;
454 return mdot;
457 FL void
458 setmsize(int sz)
460 NYD_ENTER;
461 if (n_msgvec != NULL)
462 free(n_msgvec);
463 n_msgvec = scalloc(sz + 1, sizeof *n_msgvec);
464 NYD_LEAVE;
467 FL void
468 print_header_summary(char const *Larg)
470 size_t bot, top, i, j;
471 NYD_ENTER;
473 if (Larg != NULL) {
474 /* Avoid any messages XXX add a make_mua_silent() and use it? */
475 if ((options & (OPT_VERB | OPT_EXISTONLY)) == OPT_EXISTONLY) {
476 freopen("/dev/null", "w", stdout);
477 freopen("/dev/null", "w", stderr);
479 assert(n_msgvec != NULL);
480 i = (getmsglist(/*TODO make const */n_UNCONST(Larg), n_msgvec, 0) <= 0);
481 if (options & OPT_EXISTONLY) {
482 exit_status = (int)i;
483 goto jleave;
485 if (i)
486 goto jleave;
487 for (bot = msgCount, top = 0, i = 0; (j = n_msgvec[i]) != 0; ++i) {
488 if (bot > j)
489 bot = j;
490 if (top < j)
491 top = j;
493 } else
494 bot = 1, top = msgCount;
495 print_headers(bot, top, (Larg != NULL)); /* TODO should take iterator!! */
496 jleave:
497 NYD_LEAVE;
500 FL void
501 announce(int printheaders)
503 int vec[2], mdot;
504 NYD_ENTER;
506 mdot = newfileinfo();
507 vec[0] = mdot;
508 vec[1] = 0;
509 dot = message + mdot - 1;
510 if (printheaders && msgCount > 0 && ok_blook(header)) {
511 print_header_group(vec); /* XXX errors? */
513 NYD_LEAVE;
516 FL int
517 newfileinfo(void)
519 struct message *mp;
520 int u, n, mdot, d, s, hidden, moved;
521 NYD_ENTER;
523 if (mb.mb_type == MB_VOID) {
524 mdot = 1;
525 goto jleave;
528 mdot = getmdot(0);
529 s = d = hidden = moved =0;
530 for (mp = message, n = 0, u = 0; PTRCMP(mp, <, message + msgCount); ++mp) {
531 if (mp->m_flag & MNEW)
532 ++n;
533 if ((mp->m_flag & MREAD) == 0)
534 ++u;
535 if ((mp->m_flag & (MDELETED | MSAVED)) == (MDELETED | MSAVED))
536 ++moved;
537 if ((mp->m_flag & (MDELETED | MSAVED)) == MDELETED)
538 ++d;
539 if ((mp->m_flag & (MDELETED | MSAVED)) == MSAVED)
540 ++s;
541 if (mp->m_flag & MHIDDEN)
542 ++hidden;
545 /* If displayname gets truncated the user effectively has no option to see
546 * the full pathname of the mailbox, so print it at least for '? fi' */
547 printf(_("%s: "), n_shexp_quote_cp(
548 (_update_mailname(NULL) ? displayname : mailname), FAL0));
549 if (msgCount == 1)
550 printf(_("1 message"));
551 else
552 printf(_("%d messages"), msgCount);
553 if (n > 0)
554 printf(_(" %d new"), n);
555 if (u-n > 0)
556 printf(_(" %d unread"), u);
557 if (d > 0)
558 printf(_(" %d deleted"), d);
559 if (s > 0)
560 printf(_(" %d saved"), s);
561 if (moved > 0)
562 printf(_(" %d moved"), moved);
563 if (hidden > 0)
564 printf(_(" %d hidden"), hidden);
565 else if (mb.mb_perm == 0)
566 printf(_(" [Read only]"));
567 printf("\n");
568 jleave:
569 NYD_LEAVE;
570 return mdot;
573 FL int
574 getmdot(int nmail)
576 struct message *mp;
577 char *cp;
578 int mdot;
579 enum mflag avoid = MHIDDEN | MDELETED;
580 NYD_ENTER;
582 if (!nmail) {
583 if (ok_blook(autothread)) {
584 OBSOLETE(_("please use *autosort=thread* instead of *autothread*"));
585 c_thread(NULL);
586 } else if ((cp = ok_vlook(autosort)) != NULL) {
587 if (mb.mb_sorted != NULL)
588 free(mb.mb_sorted);
589 mb.mb_sorted = sstrdup(cp);
590 c_sort(NULL);
593 if (mb.mb_type == MB_VOID) {
594 mdot = 1;
595 goto jleave;
598 if (nmail)
599 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
600 if ((mp->m_flag & (MNEWEST | avoid)) == MNEWEST)
601 break;
603 if (!nmail || PTRCMP(mp, >=, message + msgCount)) {
604 if (mb.mb_threaded) {
605 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
606 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
607 break;
608 } else {
609 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
610 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
611 break;
615 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
616 if (mb.mb_threaded) {
617 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
618 if (mp->m_flag & MFLAGGED)
619 break;
620 } else {
621 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
622 if (mp->m_flag & MFLAGGED)
623 break;
627 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
628 if (mb.mb_threaded) {
629 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
630 if (!(mp->m_flag & (MREAD | avoid)))
631 break;
632 } else {
633 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
634 if (!(mp->m_flag & (MREAD | avoid)))
635 break;
639 if (nmail &&
640 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
641 mdot = (int)PTR2SIZE(mp - message + 1);
642 else if (ok_blook(showlast)) {
643 if (mb.mb_threaded) {
644 for (mp = this_in_thread(threadroot, -1); mp;
645 mp = prev_in_thread(mp))
646 if (!(mp->m_flag & avoid))
647 break;
648 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
649 } else {
650 for (mp = message + msgCount - 1; mp >= message; --mp)
651 if (!(mp->m_flag & avoid))
652 break;
653 mdot = (mp >= message) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
655 } else if (!nmail &&
656 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
657 mdot = (int)PTR2SIZE(mp - message + 1);
658 else if (mb.mb_threaded) {
659 for (mp = threadroot; mp; mp = next_in_thread(mp))
660 if (!(mp->m_flag & avoid))
661 break;
662 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : 1;
663 } else {
664 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
665 if (!(mp->m_flag & avoid))
666 break;
667 mdot = PTRCMP(mp, <, message + msgCount)
668 ? (int)PTR2SIZE(mp - message + 1) : 1;
670 jleave:
671 NYD_LEAVE;
672 return mdot;
675 FL void
676 initbox(char const *name)
678 char *tempMesg;
679 NYD_ENTER;
681 if (mb.mb_type != MB_VOID)
682 n_strscpy(prevfile, mailname, PATH_MAX);
684 /* TODO name always NE mailname (but goes away for objects anyway)
685 * TODO Well, not true no more except that in parens */
686 _update_mailname((name != mailname) ? name : NULL);
688 if ((mb.mb_otf = Ftmp(&tempMesg, "tmpmbox", OF_WRONLY | OF_HOLDSIGS)) ==
689 NULL) {
690 n_perr(_("temporary mail message file"), 0);
691 exit(EXIT_ERR);
693 if ((mb.mb_itf = safe_fopen(tempMesg, "r", NULL)) == NULL) {
694 n_perr(_("temporary mail message file"), 0);
695 exit(EXIT_ERR);
697 Ftmp_release(&tempMesg);
699 message_reset();
700 mb.mb_threaded = 0;
701 if (mb.mb_sorted != NULL) {
702 free(mb.mb_sorted);
703 mb.mb_sorted = NULL;
705 dot = prevdot = threadroot = NULL;
706 pstate &= ~PS_DID_PRINT_DOT;
707 NYD_LEAVE;
710 FL char const *
711 folder_query(void){
712 struct n_string s, *sp = &s;
713 char *cp;
714 char const *rv;
715 bool_t err;
716 NYD_ENTER;
718 sp = n_string_creat_auto(sp);
720 /* *folder* is linked with *_folder_resolved*: we only use the latter */
721 for(err = FAL0;;){
722 if((rv = ok_vlook(_folder_resolved)) != NULL)
723 break;
725 /* POSIX says:
726 * If directory does not start with a <slash> ('/'), the contents
727 * of HOME shall be prefixed to it.
728 * And:
729 * If folder is unset or set to null, [.] filenames beginning with
730 * '+' shall refer to files in the current directory.
731 * We may have the result already */
732 rv = n_empty;
733 err = FAL0;
735 if((cp = ok_vlook(folder)) == NULL)
736 goto jset;
738 /* Expand the *folder*; skip %: prefix for simplicity of use */
739 if(cp[0] == '%' && cp[1] == ':')
740 cp += 2;
741 if((err = (cp = fexpand(cp, FEXP_NSPECIAL | FEXP_NFOLDER | FEXP_NSHELL)
742 ) == NULL) || *cp == '\0')
743 goto jset;
745 switch(which_protocol(cp)){
746 case PROTO_POP3:
747 n_err(_("*folder* can't be set to a flat, readonly POP3 account\n"));
748 err = TRU1;
749 goto jset;
750 default:
751 /* Further expansion desired */
752 break;
755 /* Prefix HOME as necessary */
756 if(*cp != '/'){ /* XXX path_is_absolute() */
757 size_t l1, l2;
758 char const *home;
760 home = ok_vlook(HOME);
761 l1 = strlen(home);
762 l2 = strlen(cp);
764 sp = n_string_reserve(sp, l1 + 1 + l2 +1);
765 sp = n_string_push_buf(sp, home, l1);
766 sp = n_string_push_c(sp, '/');
767 sp = n_string_push_buf(sp, cp, l2);
768 cp = n_string_cp(sp);
769 sp = n_string_drop_ownership(sp);
772 rv = cp;
774 /* TODO Since our visual mailname is resolved via realpath(3) if available
775 * TODO to avoid that we loose track of our currently open folder in case
776 * TODO we chdir away, but still checks the leading path portion against
777 * TODO folder_query() to be able to abbreviate to the +FOLDER syntax if
778 * TODO possible, we need to realpath(3) the folder, too */
779 #ifdef HAVE_REALPATH
780 assert(sp->s_len == 0 && sp->s_dat == NULL);
781 # ifndef HAVE_REALPATH_NULL
782 sp = n_string_reserve(sp, PATH_MAX +1);
783 # endif
785 if((sp->s_dat = realpath(cp, sp->s_dat)) != NULL){
786 # ifdef HAVE_REALPATH_NULL
787 n_string_cp(sp = n_string_assign_cp(sp, cp = sp->s_dat));
788 (free)(cp);
789 # endif
790 rv = sp->s_dat;
791 }else if(errno == ENOENT)
792 rv = cp;
793 else{
794 n_err(_("Can't canonicalize *folder*: %s\n"),
795 n_shexp_quote_cp(cp, FAL0));
796 err = TRU1;
797 rv = n_empty;
799 sp = n_string_drop_ownership(sp);
800 #endif /* HAVE_REALPATH */
802 /* Always append a solidus to our result path upon success */
803 if(!err){
804 size_t i;
806 if(rv[(i = strlen(rv)) - 1] != '/'){
807 sp = n_string_reserve(sp, i + 1 +1);
808 sp = n_string_push_buf(sp, rv, i);
809 sp = n_string_push_c(sp, '/');
810 rv = n_string_cp(sp);
811 sp = n_string_drop_ownership(sp);
815 jset:
816 /* C99 */{
817 bool_t reset = !(pstate & PS_ROOT);
819 pstate |= PS_ROOT;
820 ok_vset(_folder_resolved, rv);
821 if(reset)
822 pstate &= ~PS_ROOT;
826 if(err){
827 n_err(_("*folder* is not resolvable, using CWD\n"));
828 assert(rv != NULL && *rv == '\0');
830 NYD_LEAVE;
831 return rv;
834 /* s-it-mode */