nail.h:ISQUOTE(): use n_WC_C() not L character constant prefix
[s-mailx.git] / folder.c
blob1bafaaf63eaed9104d752b6df7872d4b412a6b35
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 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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 wether 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"), name);
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 if(folderp[i -1] != '/' && mailp[i] == '/') /* XXX DIRSEP magic */
117 ++i;
118 mailp += i;
119 *dispp++ = '+';
124 /* We want to see the name of the folder .. on the screen */
125 i = strlen(mailp);
126 if (i < sizeof(displayname) -1)
127 memcpy(dispp, mailp, i +1);
128 else {
129 rv = FAL0;
130 /* Avoid disrupting multibyte sequences (if possible) */
131 #ifndef HAVE_C90AMEND1
132 j = sizeof(displayname) / 3 - 1;
133 i -= sizeof(displayname) - (1/* + */ + 3) - j;
134 #else
135 j = field_detect_clip(sizeof(displayname) / 3, mailp, i);
136 i = j + __narrow_suffix(mailp + j, i - j,
137 sizeof(displayname) - (1/* + */ + 3 + 1) - j);
138 #endif
139 snprintf(dispp, sizeof(displayname), "%.*s...%s",
140 (int)j, mailp, mailp + i);
142 NYD_LEAVE;
143 return rv;
146 FL int
147 setfile(char const *name, enum fedit_mode fm) /* TODO oh my god */
149 /* Note we don't 'userid(myname) != getuid()', preliminary steps are usually
150 * necessary to make a mailbox accessible by a different user, and if that
151 * has happened, let's just let the usual file perms decide */
152 static int shudclob;
154 struct stat stb;
155 size_t offset;
156 char const *who;
157 int rv, omsgCount = 0;
158 FILE *ibuf = NULL, *lckfp = NULL;
159 bool_t isdevnull = FAL0;
160 NYD_ENTER;
162 pstate &= ~PS_SETFILE_OPENED;
164 /* C99 */{
165 enum fexp_mode fexpm;
167 if((who = shortcut_expand(name)) != NULL){
168 fexpm = FEXP_NSHORTCUT | FEXP_NSHELL;
169 name = who;
170 }else
171 fexpm = FEXP_NSHELL;
173 if(name[0] == '%'){
174 fm |= FEDIT_SYSBOX; /* TODO fexpand() needs to tell is-valid-user! */
175 if(*(who = &name[1]) == ':')
176 ++who;
177 if(*who == '\0')
178 who = myname;
179 }else
180 who = myname;
182 if ((name = fexpand(name, fexpm)) == NULL)
183 goto jem1;
186 switch (which_protocol(name)) {
187 case PROTO_FILE:
188 if (temporary_protocol_ext != NULL)
189 name = savecat(name, temporary_protocol_ext);
190 isdevnull = ((options & OPT_BATCH_FLAG) && !strcmp(name, "/dev/null"));
191 #ifdef HAVE_REALPATH
192 do { /* TODO we need objects, goes away then */
193 # ifdef HAVE_REALPATH_NULL
194 char *cp;
196 if ((cp = realpath(name, NULL)) != NULL) {
197 name = savestr(cp);
198 (free)(cp);
200 # else
201 char cbuf[PATH_MAX];
203 if (realpath(name, cbuf) != NULL)
204 name = savestr(cbuf);
205 # endif
206 } while (0);
207 #endif
208 rv = 1;
209 break;
210 case PROTO_MAILDIR:
211 shudclob = 1;
212 rv = maildir_setfile(name, fm);
213 goto jleave;
214 #ifdef HAVE_POP3
215 case PROTO_POP3:
216 shudclob = 1;
217 rv = pop3_setfile(name, fm);
218 goto jleave;
219 #endif
220 default:
221 n_err(_("Cannot handle protocol: %s\n"), name);
222 goto jem1;
225 if ((ibuf = Zopen(name, "r")) == NULL) {
226 if ((fm & FEDIT_SYSBOX) && errno == ENOENT) {
227 if (strcmp(who, myname) && getpwnam(who) == NULL) {
228 n_err(_("\"%s\" is not a user of this system\n"), who);
229 goto jem2;
231 if (!(options & OPT_QUICKRUN_MASK) && ok_blook(bsdcompat))
232 n_err(_("No mail for %s\n"), who);
233 if (fm & FEDIT_NEWMAIL)
234 goto jleave;
235 if (ok_blook(emptystart)) {
236 if (!(options & OPT_QUICKRUN_MASK) && !ok_blook(bsdcompat))
237 n_perr(name, 0);
238 /* We must avoid returning -1 and causing program exit */
239 mb.mb_type = MB_VOID;
240 rv = 1;
241 goto jleave;
243 goto jem2;
244 } else if (fm & FEDIT_NEWMAIL)
245 goto jleave;
246 n_perr(name, 0);
247 goto jem1;
250 if (fstat(fileno(ibuf), &stb) == -1) {
251 if (fm & FEDIT_NEWMAIL)
252 goto jleave;
253 n_perr(_("fstat"), 0);
254 goto jem1;
257 if (S_ISREG(stb.st_mode) || isdevnull) {
258 /* EMPTY */
259 } else {
260 if (fm & FEDIT_NEWMAIL)
261 goto jleave;
262 errno = S_ISDIR(stb.st_mode) ? EISDIR : EINVAL;
263 n_perr(name, 0);
264 goto jem1;
267 /* Looks like all will be well. We must now relinquish our hold on the
268 * current set of stuff. Must hold signals while we are reading the new
269 * file, else we will ruin the message[] data structure */
271 hold_sigs(); /* TODO note on this one in quit.c:quit() */
272 if (shudclob && !(fm & FEDIT_NEWMAIL) && !quit()) {
273 rele_sigs();
274 goto jem2;
277 #ifdef HAVE_SOCKETS
278 if (!(fm & FEDIT_NEWMAIL) && mb.mb_sock.s_fd >= 0)
279 sclose(&mb.mb_sock); /* TODO sorry? VMAILFS->close(), thank you */
280 #endif
282 /* TODO There is no intermediate VOID box we've switched to: name may
283 * TODO point to the same box that we just have written, so any updates
284 * TODO we won't see! Reopen again in this case. RACY! Goes with VOID! */
285 /* TODO In addition: in case of compressed/hook boxes we lock a temporary! */
286 /* TODO We may uselessly open twice but quit() doesn't say wether we were
287 * TODO modified so we can't tell: Mailbox::is_modified() :-(( */
288 if (/*shudclob && !(fm & FEDIT_NEWMAIL) &&*/ !strcmp(name, mailname)) {
289 name = mailname;
290 Fclose(ibuf);
292 if ((ibuf = Zopen(name, "r")) == NULL ||
293 fstat(fileno(ibuf), &stb) == -1 ||
294 (!S_ISREG(stb.st_mode) && !isdevnull)) {
295 n_perr(name, 0);
296 rele_sigs();
297 goto jem2;
301 /* Copy the messages into /tmp and set pointers */
302 if (!(fm & FEDIT_NEWMAIL)) {
303 mb.mb_type = MB_FILE;
304 mb.mb_perm = (((options & OPT_R_FLAG) || (fm & FEDIT_RDONLY) ||
305 access(name, W_OK) < 0) ? 0 : MB_DELE | MB_EDIT);
306 if (shudclob) {
307 if (mb.mb_itf) {
308 fclose(mb.mb_itf);
309 mb.mb_itf = NULL;
311 if (mb.mb_otf) {
312 fclose(mb.mb_otf);
313 mb.mb_otf = NULL;
316 shudclob = 1;
317 if (fm & FEDIT_SYSBOX)
318 pstate &= ~PS_EDIT;
319 else
320 pstate |= PS_EDIT;
321 initbox(name);
322 offset = 0;
323 } else {
324 fseek(mb.mb_otf, 0L, SEEK_END);
325 /* TODO Doing this without holding a lock is.. And not err checking.. */
326 fseek(ibuf, mailsize, SEEK_SET);
327 offset = mailsize;
328 omsgCount = msgCount;
331 if (isdevnull)
332 lckfp = (FILE*)-1;
333 else if (!(pstate & PS_EDIT))
334 lckfp = n_dotlock(name, fileno(ibuf), FLT_READ, offset,0,
335 (fm & FEDIT_NEWMAIL ? 0 : UIZ_MAX));
336 else if (n_file_lock(fileno(ibuf), FLT_READ, offset,0,
337 (fm & FEDIT_NEWMAIL ? 0 : UIZ_MAX)))
338 lckfp = (FILE*)-1;
340 if (lckfp == NULL) {
341 if (!(fm & FEDIT_NEWMAIL)) {
342 char const *emsg = (pstate & PS_EDIT)
343 ? N_("Unable to lock mailbox, aborting operation")
344 : N_("Unable to (dot) lock mailbox, aborting operation");
345 n_perr(V_(emsg), 0);
347 rele_sigs();
348 if (!(fm & FEDIT_NEWMAIL))
349 goto jem1;
350 goto jleave;
353 mailsize = fsize(ibuf);
355 /* TODO This is too simple minded? We should regenerate an index file
356 * TODO to be able to truly tell wether *anything* has changed! */
357 if ((fm & FEDIT_NEWMAIL) && UICMP(z, mailsize, <=, offset)) {
358 rele_sigs();
359 goto jleave;
361 setptr(ibuf, offset);
362 setmsize(msgCount);
363 if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted) {
364 mb.mb_threaded = 0;
365 c_sort((void*)-1);
368 Fclose(ibuf);
369 ibuf = NULL;
370 if (lckfp != NULL && lckfp != (FILE*)-1) {
371 Pclose(lckfp, FAL0);
372 /*lckfp = NULL;*/
374 rele_sigs();
376 if (!(fm & FEDIT_NEWMAIL)) {
377 pstate &= ~PS_SAW_COMMAND;
378 pstate |= PS_SETFILE_OPENED;
381 if ((options & OPT_EXISTONLY) && !(options & OPT_HEADERLIST)) {
382 rv = (msgCount == 0);
383 goto jleave;
386 if ((!(pstate & PS_EDIT) || (fm & FEDIT_NEWMAIL)) && msgCount == 0) {
387 if (!(fm & FEDIT_NEWMAIL)) {
388 if (!ok_blook(emptystart))
389 n_err(_("No mail for %s\n"), who);
391 goto jleave;
394 if (fm & FEDIT_NEWMAIL)
395 newmailinfo(omsgCount);
397 rv = 0;
398 jleave:
399 if (ibuf != NULL) {
400 Fclose(ibuf);
401 if (lckfp != NULL && lckfp != (FILE*)-1)
402 Pclose(lckfp, FAL0);
404 NYD_LEAVE;
405 return rv;
406 jem2:
407 mb.mb_type = MB_VOID;
408 jem1:
409 rv = -1;
410 goto jleave;
413 FL int
414 newmailinfo(int omsgCount)
416 int mdot, i;
417 NYD_ENTER;
419 for (i = 0; i < omsgCount; ++i)
420 message[i].m_flag &= ~MNEWEST;
422 if (msgCount > omsgCount) {
423 for (i = omsgCount; i < msgCount; ++i)
424 message[i].m_flag |= MNEWEST;
425 printf(_("New mail has arrived.\n"));
426 if ((i = msgCount - omsgCount) == 1)
427 printf(_("Loaded 1 new message.\n"));
428 else
429 printf(_("Loaded %d new messages.\n"), i);
430 } else
431 printf(_("Loaded %d messages.\n"), msgCount);
433 check_folder_hook(TRU1);
435 mdot = getmdot(1);
437 if (ok_blook(header))
438 print_headers(omsgCount + 1, msgCount, FAL0);
439 NYD_LEAVE;
440 return mdot;
443 FL void
444 setmsize(int sz)
446 NYD_ENTER;
447 if (n_msgvec != NULL)
448 free(n_msgvec);
449 n_msgvec = scalloc(sz + 1, sizeof *n_msgvec);
450 NYD_LEAVE;
453 FL void
454 print_header_summary(char const *Larg)
456 size_t bot, top, i, j;
457 NYD_ENTER;
459 if (Larg != NULL) {
460 /* Avoid any messages XXX add a make_mua_silent() and use it? */
461 if ((options & (OPT_VERB | OPT_EXISTONLY)) == OPT_EXISTONLY) {
462 freopen("/dev/null", "w", stdout);
463 freopen("/dev/null", "w", stderr);
465 assert(n_msgvec != NULL);
466 i = (getmsglist(/*TODO make arg const */UNCONST(Larg), n_msgvec, 0) <= 0);
467 if (options & OPT_EXISTONLY) {
468 exit_status = (int)i;
469 goto jleave;
471 if (i)
472 goto jleave;
473 for (bot = msgCount, top = 0, i = 0; (j = n_msgvec[i]) != 0; ++i) {
474 if (bot > j)
475 bot = j;
476 if (top < j)
477 top = j;
479 } else
480 bot = 1, top = msgCount;
481 print_headers(bot, top, (Larg != NULL)); /* TODO should take iterator!! */
482 jleave:
483 NYD_LEAVE;
486 FL void
487 announce(int printheaders)
489 int vec[2], mdot;
490 NYD_ENTER;
492 mdot = newfileinfo();
493 vec[0] = mdot;
494 vec[1] = 0;
495 dot = message + mdot - 1;
496 if (printheaders && msgCount > 0 && ok_blook(header)) {
497 print_header_group(vec); /* XXX errors? */
499 NYD_LEAVE;
502 FL int
503 newfileinfo(void)
505 struct message *mp;
506 int u, n, mdot, d, s, hidden, moved;
507 NYD_ENTER;
509 if (mb.mb_type == MB_VOID) {
510 mdot = 1;
511 goto jleave;
514 mdot = getmdot(0);
515 s = d = hidden = moved =0;
516 for (mp = message, n = 0, u = 0; PTRCMP(mp, <, message + msgCount); ++mp) {
517 if (mp->m_flag & MNEW)
518 ++n;
519 if ((mp->m_flag & MREAD) == 0)
520 ++u;
521 if ((mp->m_flag & (MDELETED | MSAVED)) == (MDELETED | MSAVED))
522 ++moved;
523 if ((mp->m_flag & (MDELETED | MSAVED)) == MDELETED)
524 ++d;
525 if ((mp->m_flag & (MDELETED | MSAVED)) == MSAVED)
526 ++s;
527 if (mp->m_flag & MHIDDEN)
528 ++hidden;
531 /* If displayname gets truncated the user effectively has no option to see
532 * the full pathname of the mailbox, so print it at least for '? fi' */
533 printf(_("\"%s\": "),
534 (_update_mailname(NULL) ? displayname : mailname));
535 if (msgCount == 1)
536 printf(_("1 message"));
537 else
538 printf(_("%d messages"), msgCount);
539 if (n > 0)
540 printf(_(" %d new"), n);
541 if (u-n > 0)
542 printf(_(" %d unread"), u);
543 if (d > 0)
544 printf(_(" %d deleted"), d);
545 if (s > 0)
546 printf(_(" %d saved"), s);
547 if (moved > 0)
548 printf(_(" %d moved"), moved);
549 if (hidden > 0)
550 printf(_(" %d hidden"), hidden);
551 else if (mb.mb_perm == 0)
552 printf(_(" [Read only]"));
553 printf("\n");
554 jleave:
555 NYD_LEAVE;
556 return mdot;
559 FL int
560 getmdot(int nmail)
562 struct message *mp;
563 char *cp;
564 int mdot;
565 enum mflag avoid = MHIDDEN | MDELETED;
566 NYD_ENTER;
568 if (!nmail) {
569 if (ok_blook(autothread)) {
570 OBSOLETE(_("please use *autosort=thread* instead of *autothread*"));
571 c_thread(NULL);
572 } else if ((cp = ok_vlook(autosort)) != NULL) {
573 if (mb.mb_sorted != NULL)
574 free(mb.mb_sorted);
575 mb.mb_sorted = sstrdup(cp);
576 c_sort(NULL);
579 if (mb.mb_type == MB_VOID) {
580 mdot = 1;
581 goto jleave;
584 if (nmail)
585 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
586 if ((mp->m_flag & (MNEWEST | avoid)) == MNEWEST)
587 break;
589 if (!nmail || PTRCMP(mp, >=, message + msgCount)) {
590 if (mb.mb_threaded) {
591 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
592 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
593 break;
594 } else {
595 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
596 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
597 break;
601 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
602 if (mb.mb_threaded) {
603 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
604 if (mp->m_flag & MFLAGGED)
605 break;
606 } else {
607 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
608 if (mp->m_flag & MFLAGGED)
609 break;
613 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
614 if (mb.mb_threaded) {
615 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
616 if (!(mp->m_flag & (MREAD | avoid)))
617 break;
618 } else {
619 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
620 if (!(mp->m_flag & (MREAD | avoid)))
621 break;
625 if (nmail &&
626 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
627 mdot = (int)PTR2SIZE(mp - message + 1);
628 else if (ok_blook(showlast)) {
629 if (mb.mb_threaded) {
630 for (mp = this_in_thread(threadroot, -1); mp;
631 mp = prev_in_thread(mp))
632 if (!(mp->m_flag & avoid))
633 break;
634 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
635 } else {
636 for (mp = message + msgCount - 1; mp >= message; --mp)
637 if (!(mp->m_flag & avoid))
638 break;
639 mdot = (mp >= message) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
641 } else if (!nmail &&
642 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
643 mdot = (int)PTR2SIZE(mp - message + 1);
644 else if (mb.mb_threaded) {
645 for (mp = threadroot; mp; mp = next_in_thread(mp))
646 if (!(mp->m_flag & avoid))
647 break;
648 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : 1;
649 } else {
650 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
651 if (!(mp->m_flag & avoid))
652 break;
653 mdot = PTRCMP(mp, <, message + msgCount)
654 ? (int)PTR2SIZE(mp - message + 1) : 1;
656 jleave:
657 NYD_LEAVE;
658 return mdot;
661 FL void
662 initbox(char const *name)
664 char *tempMesg;
665 NYD_ENTER;
667 if (mb.mb_type != MB_VOID)
668 n_strscpy(prevfile, mailname, PATH_MAX);
670 /* TODO name always NE mailname (but goes away for objects anyway)
671 * TODO Well, not true no more except that in parens */
672 _update_mailname((name != mailname) ? name : NULL);
674 if ((mb.mb_otf = Ftmp(&tempMesg, "tmpmbox", OF_WRONLY | OF_HOLDSIGS)) ==
675 NULL) {
676 n_perr(_("temporary mail message file"), 0);
677 exit(EXIT_ERR);
679 if ((mb.mb_itf = safe_fopen(tempMesg, "r", NULL)) == NULL) {
680 n_perr(_("temporary mail message file"), 0);
681 exit(EXIT_ERR);
683 Ftmp_release(&tempMesg);
685 message_reset();
686 mb.mb_threaded = 0;
687 if (mb.mb_sorted != NULL) {
688 free(mb.mb_sorted);
689 mb.mb_sorted = NULL;
691 dot = prevdot = threadroot = NULL;
692 pstate &= ~PS_DID_PRINT_DOT;
693 NYD_LEAVE;
696 FL char const *
697 folder_query(void){
698 struct n_string s, *sp = &s;
699 char *cp;
700 char const *rv;
701 bool_t err;
702 NYD_ENTER;
704 /* *folder* is linked with *_folder_resolved*: we only use the latter */
705 for(err = FAL0;;){
706 if((rv = ok_vlook(_folder_resolved)) != NULL)
707 break;
709 /* POSIX says:
710 * If directory does not start with a <slash> ('/'), the contents
711 * of HOME shall be prefixed to it.
712 * And:
713 * If folder is unset or set to null, [.] filenames beginning with
714 * '+' shall refer to files in the current directory.
715 * We may have the result already */
716 rv = "";
717 err = FAL0;
719 if((cp = ok_vlook(folder)) == NULL)
720 goto jset;
722 /* Expand the *folder*; skip %: prefix for simplicity of use */
723 if(cp[0] == '%' && cp[1] == ':')
724 cp += 2;
725 if((err = (cp = fexpand(cp, FEXP_NSHELL)) == NULL) || *cp == '\0')
726 goto jset;
728 switch(which_protocol(cp)){
729 case PROTO_POP3:
730 n_err(_("*folder* can't be set to a flat, readonly POP3 account\n"));
731 err = TRU1;
732 goto jset;
733 default:
734 /* Further expansion desired */
735 break;
738 /* Prefix HOME as necessary */
739 if(*cp != '/'){
740 char const *home = ok_vlook(HOME);
741 size_t l1 = strlen(home), l2 = strlen(cp);
743 sp = n_string_creat_auto(sp);
744 sp = n_string_reserve(sp, l1 + 1 + l2 +1);
745 sp = n_string_push_buf(sp, home, l1);
746 sp = n_string_push_c(sp, '/');
747 sp = n_string_push_buf(sp, cp, l2);
748 cp = n_string_cp(sp);
751 rv = cp;
753 /* TODO Since our visual mailname is resolved via realpath(3) if available
754 * TODO to avoid that we loose track of our currently open folder in case
755 * TODO we chdir away, but still checks the leading path portion against
756 * TODO fold_query() to be able to abbreviate to the +FOLDER syntax if
757 * TODO possible, we need to realpath(3) the folder, too */
758 #ifdef HAVE_REALPATH
759 /* C99 */{
760 # ifndef HAVE_REALPATH_NULL
761 if(cp == sp->s_dat)
762 sp = n_string_drop_ownership(sp);
763 sp = n_string_reserve(sp, PATH_MAX +1);
764 # else
765 sp->s_dat = NULL;
766 # endif
768 if((sp->s_dat = realpath(cp, sp->s_dat)) != NULL){
769 # ifdef HAVE_REALPATH_NULL
770 sp->s_dat = savestr(cp = sp->s_dat);
771 (free)(cp);
772 # endif
773 rv = sp->s_dat;
774 }else if(errno == ENOENT)
775 rv = cp;
776 else{
777 n_err(_("Can't canonicalize *folder*: \"%s\"\n"), cp);
778 err = TRU1;
779 rv = "";
782 #endif /* HAVE_REALPATH */
784 jset:
785 /* C99 */{
786 bool_t reset = !(pstate & PS_ROOT);
788 pstate |= PS_ROOT;
789 ok_vset(_folder_resolved, rv);
790 if(reset)
791 pstate &= ~PS_ROOT;
795 if(err){
796 n_err(_("*folder* is not resolvable, using CWD\n"));
797 assert(rv != NULL && *rv == '\0');
799 NYD_LEAVE;
800 return rv;
803 /* s-it-mode */