make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / folder.c
blobc586cebd49c74a3893b230c5f3c0b9906f95421c
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 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 * SPDX-License-Identifier: BSD-3-Clause
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
36 #undef n_FILE
37 #define n_FILE folder
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 #include <pwd.h>
45 /* Update mailname (if name != NULL) and displayname, return whether displayname
46 * was large enough to swallow mailname */
47 static bool_t _update_mailname(char const *name);
48 #ifdef HAVE_C90AMEND1 /* TODO unite __narrow_suffix() into one fun! */
49 n_INLINE size_t __narrow_suffix(char const *cp, size_t cpl, size_t maxl);
50 #endif
52 /**/
53 static void a_folder_info(void);
55 /* Set up the input pointers while copying the mail file into /tmp */
56 static void a_folder_mbox_setptr(FILE *ibuf, off_t offset);
58 static bool_t
59 _update_mailname(char const *name) /* TODO 2MUCH work, cache, prop of Object! */
61 char const *foldp;
62 char *mailp, *dispp;
63 size_t i, j, foldlen;
64 bool_t rv;
65 NYD_ENTER;
67 /* Don't realpath(3) if it's only an update request */
68 if(name != NULL){
69 #ifdef HAVE_REALPATH
70 char const *adjname;
71 enum protocol p;
73 p = which_protocol(name, TRU1, TRU1, &adjname);
75 if(p == PROTO_FILE || p == PROTO_MAILDIR){
76 name = adjname;
77 if (realpath(name, mailname) == NULL && n_err_no != n_ERR_NOENT) {
78 n_err(_("Can't canonicalize %s\n"), n_shexp_quote_cp(name, FAL0));
79 goto jdocopy;
81 }else
82 jdocopy:
83 #endif
84 n_strscpy(mailname, name, sizeof(mailname));
87 mailp = mailname;
88 dispp = displayname;
90 /* Don't display an absolute path but "+FOLDER" if under *folder* */
91 if(*(foldp = n_folder_query()) != '\0'){
92 foldlen = strlen(foldp);
93 if(strncmp(foldp, mailp, foldlen))
94 foldlen = 0;
95 }else
96 foldlen = 0;
98 /* We want to see the name of the folder .. on the screen */
99 i = strlen(mailp);
100 if(i < sizeof(displayname) - 3 -1){
101 if(foldlen > 0){
102 *dispp++ = '+';
103 *dispp++ = '[';
104 memcpy(dispp, mailp, foldlen);
105 dispp += foldlen;
106 mailp += foldlen;
107 *dispp++ = ']';
108 memcpy(dispp, mailp, i -= foldlen);
109 dispp[i] = '\0';
110 }else
111 memcpy(dispp, mailp, i +1);
112 rv = TRU1;
113 }else{
114 /* Avoid disrupting multibyte sequences (if possible) */
115 #ifndef HAVE_C90AMEND1
116 j = sizeof(displayname) / 3 - 3;
117 i -= sizeof(displayname) - (1/* + */ + 3) - j;
118 #else
119 j = field_detect_clip(sizeof(displayname) / 3, mailp, i);
120 i = j + __narrow_suffix(mailp + j, i - j,
121 sizeof(displayname) - (1/* + */ + 3 + 1) - j);
122 #endif
123 snprintf(dispp, sizeof(displayname), "%s%.*s...%s",
124 (foldlen > 0 ? "[+]" : ""), (int)j, mailp, mailp + i);
125 rv = FAL0;
128 n_PS_ROOT_BLOCK((ok_vset(mailbox_resolved, mailname),
129 ok_vset(mailbox_display, displayname)));
130 NYD_LEAVE;
131 return rv;
134 #ifdef HAVE_C90AMEND1
135 n_INLINE size_t
136 __narrow_suffix(char const *cp, size_t cpl, size_t maxl)
138 int err;
139 size_t i, ok;
140 NYD_ENTER;
142 for (err = ok = i = 0; cpl > maxl || err;) {
143 int ml = mblen(cp, cpl);
144 if (ml < 0) { /* XXX _narrow_suffix(): mblen() error; action? */
145 (void)mblen(NULL, 0);
146 err = 1;
147 ml = 1;
148 } else {
149 if (!err)
150 ok = i;
151 err = 0;
152 if (ml == 0)
153 break;
155 cp += ml;
156 i += ml;
157 cpl -= ml;
159 NYD_LEAVE;
160 return ok;
162 #endif /* HAVE_C90AMEND1 */
164 static void
165 a_folder_info(void){
166 struct message *mp;
167 int u, n, d, s, hidden, moved;
168 NYD2_ENTER;
170 if(mb.mb_type == MB_VOID){
171 fprintf(n_stdout, _("(Currently no active mailbox)"));
172 goto jleave;
175 s = d = hidden = moved = 0;
176 for (mp = message, n = 0, u = 0; PTRCMP(mp, <, message + msgCount); ++mp) {
177 if (mp->m_flag & MNEW)
178 ++n;
179 if ((mp->m_flag & MREAD) == 0)
180 ++u;
181 if ((mp->m_flag & (MDELETED | MSAVED)) == (MDELETED | MSAVED))
182 ++moved;
183 if ((mp->m_flag & (MDELETED | MSAVED)) == MDELETED)
184 ++d;
185 if ((mp->m_flag & (MDELETED | MSAVED)) == MSAVED)
186 ++s;
187 if (mp->m_flag & MHIDDEN)
188 ++hidden;
191 /* If displayname gets truncated the user effectively has no option to see
192 * the full pathname of the mailbox, so print it at least for '? fi' */
193 fprintf(n_stdout, "%s: ", n_shexp_quote_cp(
194 (_update_mailname(NULL) ? displayname : mailname), FAL0));
195 if (msgCount == 1)
196 fprintf(n_stdout, _("1 message"));
197 else
198 fprintf(n_stdout, _("%d messages"), msgCount);
199 if (n > 0)
200 fprintf(n_stdout, _(" %d new"), n);
201 if (u-n > 0)
202 fprintf(n_stdout, _(" %d unread"), u);
203 if (d > 0)
204 fprintf(n_stdout, _(" %d deleted"), d);
205 if (s > 0)
206 fprintf(n_stdout, _(" %d saved"), s);
207 if (moved > 0)
208 fprintf(n_stdout, _(" %d moved"), moved);
209 if (hidden > 0)
210 fprintf(n_stdout, _(" %d hidden"), hidden);
211 if (mb.mb_perm == 0)
212 fprintf(n_stdout, _(" [Read-only]"));
213 #ifdef HAVE_IMAP
214 if (mb.mb_type == MB_CACHE)
215 fprintf(n_stdout, _(" [Disconnected]"));
216 #endif
218 jleave:
219 putc('\n', n_stdout);
220 NYD2_LEAVE;
223 static void
224 a_folder_mbox_setptr(FILE *ibuf, off_t offset) /* TODO Mailbox->setptr() */
226 struct message self;
227 char const *cp2;
228 char *linebuf, *cp;
229 int selfcnt, c;
230 bool_t need_rfc4155, maybe, inhead, from_;
231 size_t filesize, linesize, cnt;
232 NYD_ENTER;
234 memset(&self, 0, sizeof self);
235 self.m_flag = MUSED | MNEW | MNEWEST;
236 filesize = mailsize - offset;
237 offset = ftell(mb.mb_otf);
238 need_rfc4155 = ok_blook(mbox_rfc4155);
239 maybe = TRU1;
240 inhead = FAL0;
241 selfcnt = 0;
242 linebuf = NULL, linesize = 0; /* TODO string pool */
244 for (;;) {
245 if (fgetline(&linebuf, &linesize, &filesize, &cnt, ibuf, 0) == NULL) {
246 self.m_xsize = self.m_size;
247 self.m_xlines = self.m_lines;
248 self.m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
249 if (selfcnt > 0)
250 message_append(&self);
251 message_append_null();
252 if (linebuf != NULL)
253 n_free(linebuf);
254 break;
257 #ifdef notdef
258 if (linebuf[0] == '\0')
259 linebuf[0] = '.';
260 #endif
261 /* XXX Convert CRLF to LF; this should be rethought in that
262 * XXX CRLF input should possibly end as CRLF output? */
263 if (cnt >= 2 && linebuf[cnt - 1] == '\n' && linebuf[cnt - 2] == '\r')
264 linebuf[--cnt - 1] = '\n';
265 fwrite(linebuf, sizeof *linebuf, cnt, mb.mb_otf);
266 if (ferror(mb.mb_otf)) {
267 n_perr(_("/tmp"), 0);
268 exit(n_EXIT_ERR);
270 if (linebuf[cnt - 1] == '\n')
271 linebuf[cnt - 1] = '\0';
272 /* TODO In v15 this should use a/the flat MIME parser in order to ignore
273 * TODO "From " when MIME boundaries are active -- whereas this opens
274 * TODO another can of worms, it very likely is better than messing up
275 * TODO MIME because of a "From " line! */
276 if (maybe && linebuf[0] == 'F' &&
277 (from_ = is_head(linebuf, cnt, TRU1)) &&
278 (from_ == TRU1 || !need_rfc4155)) {
279 /* TODO char date[n_FROM_DATEBUF];
280 * TODO extract_date_from_from_(linebuf, cnt, date);
281 * TODO self.m_time = 10000; */
282 if (from_ == TRUM1) {
283 if (n_poption & n_PO_D_V)
284 n_err(_("Invalid MBOX \"From_ line\": %.*s\n"),
285 (int)cnt, linebuf);
286 else if (!(mb.mb_active & MB_FROM__WARNED))
287 n_err(_("MBOX mailbox contains non-conforming From_ line(s)!\n"
288 " Message boundaries may have been falsely detected!\n"
289 " Setting variable *mbox-rfc4155* and reopen should improve "
290 "the result.\n"
291 " If so, make changes permanent: \"copy * SOME-FILE\". "
292 "Then unset *mbox-rfc4155*\n"));
293 mb.mb_active |= MB_FROM__WARNED;
295 self.m_xsize = self.m_size;
296 self.m_xlines = self.m_lines;
297 self.m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
298 if (selfcnt++ > 0)
299 message_append(&self);
300 msgCount++;
301 self.m_flag = MUSED | MNEW | MNEWEST;
302 self.m_size = 0;
303 self.m_lines = 0;
304 self.m_block = mailx_blockof(offset);
305 self.m_offset = mailx_offsetof(offset);
306 inhead = TRU1;
307 } else if (linebuf[0] == 0) {
308 inhead = FAL0;
309 } else if (inhead) {
310 for (cp = linebuf, cp2 = "status";; ++cp) {
311 if ((c = *cp2++) == 0) {
312 while (c = *cp++, whitechar(c))
314 if (cp[-1] != ':')
315 break;
316 while ((c = *cp++) != '\0')
317 if (c == 'R')
318 self.m_flag |= MREAD;
319 else if (c == 'O')
320 self.m_flag &= ~MNEW;
321 break;
323 if (*cp != c && *cp != upperconv(c))
324 break;
326 for (cp = linebuf, cp2 = "x-status";; ++cp) {
327 if ((c = *cp2++) == 0) {
328 while ((c = *cp++, whitechar(c)))
330 if (cp[-1] != ':')
331 break;
332 while ((c = *cp++) != '\0')
333 if (c == 'F')
334 self.m_flag |= MFLAGGED;
335 else if (c == 'A')
336 self.m_flag |= MANSWERED;
337 else if (c == 'T')
338 self.m_flag |= MDRAFTED;
339 break;
341 if (*cp != c && *cp != upperconv(c))
342 break;
345 offset += cnt;
346 self.m_size += cnt;
347 ++self.m_lines;
348 maybe = (linebuf[0] == 0);
350 NYD_LEAVE;
353 FL int
354 setfile(char const *name, enum fedit_mode fm) /* TODO oh my god */
356 /* TODO This all needs to be converted to URL:: and Mailbox:: */
357 static int shudclob;
359 struct stat stb;
360 size_t offset;
361 char const *who, *orig_name;
362 int rv, omsgCount = 0;
363 FILE *ibuf = NULL, *lckfp = NULL;
364 bool_t isdevnull = FAL0;
365 NYD_ENTER;
367 n_pstate &= ~n_PS_SETFILE_OPENED;
369 /* C99 */{
370 enum fexp_mode fexpm;
372 if((who = shortcut_expand(name)) != NULL){
373 fexpm = FEXP_NSHORTCUT/* XXX | FEXP_NSHELL*/;
374 name = who;
375 }else
376 fexpm = FEXP_NSHELL;
378 if(name[0] == '%'){
379 char const *cp;
381 fm |= FEDIT_SYSBOX; /* TODO fexpand() needs to tell is-valid-user! */
382 if(*(who = &name[1]) == ':')
383 ++who;
384 if((cp = strrchr(who, '/')) != NULL)
385 who = &cp[1];
386 if(*who == '\0')
387 goto jlogname;
388 }else{
389 jlogname:
390 if(fm & FEDIT_ACCOUNT){
391 if((who = ok_vlook(account)) == NULL)
392 who = ACCOUNT_NULL;
393 who = savecatsep(_("account"), ' ', who);
394 }else
395 who = ok_vlook(LOGNAME);
398 if ((name = fexpand(name, fexpm)) == NULL)
399 goto jem1;
402 /* For at least substdate() users TODO -> eventloop tick */
403 time_current_update(&time_current, FAL0);
405 switch (which_protocol(orig_name = name, TRU1, TRU1, &name)) {
406 case PROTO_FILE:
407 isdevnull = ((n_poption & n_PO_BATCH_FLAG) &&
408 !strcmp(name, n_path_devnull));
409 #ifdef HAVE_REALPATH
410 do { /* TODO we need objects, goes away then */
411 # ifdef HAVE_REALPATH_NULL
412 char *cp;
414 if ((cp = realpath(name, NULL)) != NULL) {
415 name = savestr(cp);
416 (free)(cp);
418 # else
419 char cbuf[PATH_MAX];
421 if (realpath(name, cbuf) != NULL)
422 name = savestr(cbuf);
423 # endif
424 } while (0);
425 #endif
426 rv = 1;
427 break;
428 #ifdef HAVE_MAILDIR
429 case PROTO_MAILDIR:
430 shudclob = 1;
431 rv = maildir_setfile(who, name, fm);
432 goto jleave;
433 #endif
434 #ifdef HAVE_POP3
435 case PROTO_POP3:
436 shudclob = 1;
437 rv = pop3_setfile(who, orig_name, fm);
438 goto jleave;
439 #endif
440 #ifdef HAVE_IMAP
441 case PROTO_IMAP:
442 shudclob = 1;
443 if((fm & FEDIT_NEWMAIL) && mb.mb_type == MB_CACHE)
444 rv = 1;
445 else
446 rv = imap_setfile(who, orig_name, fm);
447 goto jleave;
448 #endif
449 default:
450 n_err(_("Cannot handle protocol: %s\n"), orig_name);
451 goto jem1;
454 if ((ibuf = n_fopen_any(savecat("file://", name), "r", NULL)) == NULL) {
455 int e = n_err_no;
457 if ((fm & FEDIT_SYSBOX) && e == n_ERR_NOENT) {
458 if (!(fm & FEDIT_ACCOUNT) && strcmp(who, ok_vlook(LOGNAME)) &&
459 getpwnam(who) == NULL) {
460 n_err(_("%s is not a user of this system\n"),
461 n_shexp_quote_cp(who, FAL0));
462 goto jem2;
464 if (!(n_poption & n_PO_QUICKRUN_MASK) && ok_blook(bsdcompat))
465 n_err(_("No mail for %s at %s\n"),
466 who, n_shexp_quote_cp(name, FAL0));
468 if (fm & FEDIT_NEWMAIL)
469 goto jleave;
471 if(mb.mb_digmsg != NULL)
472 n_dig_msg_on_mailbox_close(&mb);
473 mb.mb_type = MB_VOID;
475 if (ok_blook(emptystart)) {
476 if (!(n_poption & n_PO_QUICKRUN_MASK) && !ok_blook(bsdcompat))
477 n_perr(name, e);
478 /* We must avoid returning -1 and causing program exit */
479 rv = 1;
480 goto jleave;
482 n_perr(name, e);
483 goto jem1;
486 if (fstat(fileno(ibuf), &stb) == -1) {
487 if (fm & FEDIT_NEWMAIL)
488 goto jleave;
489 n_perr(_("fstat"), 0);
490 goto jem1;
493 if (S_ISREG(stb.st_mode) || isdevnull) {
494 /* EMPTY */
495 } else {
496 if (fm & FEDIT_NEWMAIL)
497 goto jleave;
498 n_err_no = S_ISDIR(stb.st_mode) ? n_ERR_ISDIR : n_ERR_INVAL;
499 n_perr(name, 0);
500 goto jem1;
503 if (shudclob && !(fm & FEDIT_NEWMAIL) && !quit(FAL0))
504 goto jem2;
506 hold_sigs();
508 #ifdef HAVE_SOCKETS
509 if (!(fm & FEDIT_NEWMAIL) && mb.mb_sock.s_fd >= 0)
510 sclose(&mb.mb_sock); /* TODO sorry? VMAILFS->close(), thank you */
511 #endif
513 /* TODO There is no intermediate VOID box we've switched to: name may
514 * TODO point to the same box that we just have written, so any updates
515 * TODO we won't see! Reopen again in this case. RACY! Goes with VOID! */
516 /* TODO In addition: in case of compressed/hook boxes we lock a temporary! */
517 /* TODO We may uselessly open twice but quit() doesn't say whether we were
518 * TODO modified so we can't tell: Mailbox::is_modified() :-(( */
519 if (/*shudclob && !(fm & FEDIT_NEWMAIL) &&*/ !strcmp(name, mailname)) {
520 name = mailname;
521 Fclose(ibuf);
523 if ((ibuf = n_fopen_any(name, "r", NULL)) == NULL ||
524 fstat(fileno(ibuf), &stb) == -1 ||
525 (!S_ISREG(stb.st_mode) && !isdevnull)) {
526 n_perr(name, 0);
527 rele_sigs();
528 goto jem2;
532 /* Copy the messages into /tmp and set pointers */
533 if (!(fm & FEDIT_NEWMAIL)) {
534 if (isdevnull) {
535 mb.mb_type = MB_VOID;
536 mb.mb_perm = 0;
537 } else {
538 mb.mb_type = MB_FILE;
539 mb.mb_perm = (((n_poption & n_PO_R_FLAG) || (fm & FEDIT_RDONLY) ||
540 access(name, W_OK) < 0) ? 0 : MB_DELE | MB_EDIT);
541 if (shudclob) {
542 if (mb.mb_itf) {
543 fclose(mb.mb_itf);
544 mb.mb_itf = NULL;
546 if (mb.mb_otf) {
547 fclose(mb.mb_otf);
548 mb.mb_otf = NULL;
552 shudclob = 1;
553 if (fm & FEDIT_SYSBOX)
554 n_pstate &= ~n_PS_EDIT;
555 else
556 n_pstate |= n_PS_EDIT;
557 initbox(name);
558 offset = 0;
559 } else {
560 fseek(mb.mb_otf, 0L, SEEK_END);
561 /* TODO Doing this without holding a lock is.. And not err checking.. */
562 fseek(ibuf, mailsize, SEEK_SET);
563 offset = mailsize;
564 omsgCount = msgCount;
567 if (isdevnull)
568 lckfp = (FILE*)-1;
569 else if (!(n_pstate & n_PS_EDIT))
570 lckfp = n_dotlock(name, fileno(ibuf), FLT_READ, offset,0,
571 (fm & FEDIT_NEWMAIL ? 0 : UIZ_MAX));
572 else if (n_file_lock(fileno(ibuf), FLT_READ, offset,0,
573 (fm & FEDIT_NEWMAIL ? 0 : UIZ_MAX)))
574 lckfp = (FILE*)-1;
576 if (lckfp == NULL) {
577 if (!(fm & FEDIT_NEWMAIL)) {
578 #ifdef HAVE_UISTRINGS
579 char const * const emsg = (n_pstate & n_PS_EDIT)
580 ? N_("Unable to lock mailbox, aborting operation")
581 : N_("Unable to (dot) lock mailbox, aborting operation");
582 #endif
583 n_perr(V_(emsg), 0);
585 rele_sigs();
586 if (!(fm & FEDIT_NEWMAIL))
587 goto jem1;
588 goto jleave;
591 mailsize = fsize(ibuf);
593 /* TODO This is too simple minded? We should regenerate an index file
594 * TODO to be able to truly tell whether *anything* has changed! */
595 if ((fm & FEDIT_NEWMAIL) && UICMP(z, mailsize, <=, offset)) {
596 rele_sigs();
597 goto jleave;
599 a_folder_mbox_setptr(ibuf, offset);
600 setmsize(msgCount);
601 if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted) {
602 mb.mb_threaded = 0;
603 c_sort((void*)-1);
606 Fclose(ibuf);
607 ibuf = NULL;
608 if (lckfp != NULL && lckfp != (FILE*)-1) {
609 Pclose(lckfp, FAL0);
610 /*lckfp = NULL;*/
613 if (!(fm & FEDIT_NEWMAIL)) {
614 n_pstate &= ~n_PS_SAW_COMMAND;
615 n_pstate |= n_PS_SETFILE_OPENED;
618 rele_sigs();
620 rv = (msgCount == 0);
621 if(rv)
622 n_err_no = n_ERR_NODATA;
624 if(n_poption & n_PO_EXISTONLY)
625 goto jleave;
627 if(rv){
628 if(!(n_pstate & n_PS_EDIT) || (fm & FEDIT_NEWMAIL)){
629 if(!(fm & FEDIT_NEWMAIL)){
630 if (!ok_blook(emptystart))
631 n_err(_("No mail for %s at %s\n"),
632 who, n_shexp_quote_cp(name, FAL0));
634 goto jleave;
638 if(fm & FEDIT_NEWMAIL)
639 newmailinfo(omsgCount);
640 jleave:
641 if (ibuf != NULL) {
642 Fclose(ibuf);
643 if (lckfp != NULL && lckfp != (FILE*)-1)
644 Pclose(lckfp, FAL0);
646 NYD_LEAVE;
647 return rv;
648 jem2:
649 if(mb.mb_digmsg != NULL)
650 n_dig_msg_on_mailbox_close(&mb);
651 mb.mb_type = MB_VOID;
652 jem1:
653 n_err_no = n_ERR_NOTOBACCO;
654 rv = -1;
655 goto jleave;
658 FL int
659 newmailinfo(int omsgCount)
661 int mdot, i;
662 NYD_ENTER;
664 for (i = 0; i < omsgCount; ++i)
665 message[i].m_flag &= ~MNEWEST;
667 if (msgCount > omsgCount) {
668 for (i = omsgCount; i < msgCount; ++i)
669 message[i].m_flag |= MNEWEST;
670 fprintf(n_stdout, _("New mail has arrived.\n"));
671 if ((i = msgCount - omsgCount) == 1)
672 fprintf(n_stdout, _("Loaded 1 new message.\n"));
673 else
674 fprintf(n_stdout, _("Loaded %d new messages.\n"), i);
675 } else
676 fprintf(n_stdout, _("Loaded %d messages.\n"), msgCount);
678 temporary_folder_hook_check(TRU1);
680 mdot = getmdot(1);
682 if(ok_blook(header) && (i = omsgCount + 1) <= msgCount){
683 #ifdef HAVE_IMAP
684 if(mb.mb_type == MB_IMAP)
685 imap_getheaders(i, msgCount); /* TODO not here */
686 #endif
687 for(omsgCount = 0; i <= msgCount; ++omsgCount, ++i)
688 n_msgvec[omsgCount] = i;
689 n_msgvec[omsgCount] = 0;
690 print_headers(n_msgvec, FAL0, FAL0);
692 NYD_LEAVE;
693 return mdot;
696 FL void
697 setmsize(int sz)
699 NYD_ENTER;
700 if (n_msgvec != NULL)
701 n_free(n_msgvec);
702 n_msgvec = n_calloc(sz +1, sizeof *n_msgvec);
703 NYD_LEAVE;
706 FL void
707 print_header_summary(char const *Larg)
709 size_t i;
710 NYD_ENTER;
712 getmdot(0);
713 #ifdef HAVE_IMAP
714 if(mb.mb_type == MB_IMAP)
715 imap_getheaders(0, msgCount); /* TODO not here */
716 #endif
717 assert(n_msgvec != NULL);
719 if (Larg != NULL) {
720 /* Avoid any messages XXX add a make_mua_silent() and use it? */
721 if ((n_poption & (n_PO_VERB | n_PO_EXISTONLY)) == n_PO_EXISTONLY) {
722 n_stdout = freopen(n_path_devnull, "w", stdout);
723 n_stderr = freopen(n_path_devnull, "w", stderr);
725 i = (n_getmsglist(n_shexp_quote_cp(Larg, FAL0), n_msgvec, 0, NULL) <= 0);
726 if (n_poption & n_PO_EXISTONLY)
727 n_exit_status = (int)i;
728 else if(i == 0)
729 print_headers(n_msgvec, TRU1, FAL0); /* TODO should be iterator! */
730 } else {
731 i = 0;
732 if(!mb.mb_threaded){
733 for(; UICMP(z, i, <, msgCount); ++i)
734 n_msgvec[i] = i + 1;
735 }else{
736 struct message *mp;
738 for(mp = threadroot; mp; ++i, mp = next_in_thread(mp))
739 n_msgvec[i] = (int)PTR2SIZE(mp - message + 1);
741 print_headers(n_msgvec, FAL0, TRU1); /* TODO should be iterator! */
743 NYD_LEAVE;
746 FL void
747 n_folder_announce(enum n_announce_flags af){
748 int vec[2], mdot;
749 NYD_ENTER;
751 mdot = (mb.mb_type == MB_VOID) ? 1 : getmdot(0);
752 dot = &message[mdot - 1];
754 if(af != n_ANNOUNCE_NONE && ok_blook(header) &&
755 ((af & n_ANNOUNCE_MAIN_CALL) ||
756 ((af & n_ANNOUNCE_CHANGE) && !ok_blook(posix))))
757 af |= n_ANNOUNCE_STATUS | n__ANNOUNCE_HEADER;
759 if(af & n_ANNOUNCE_STATUS){
760 a_folder_info();
761 af |= n__ANNOUNCE_ANY;
764 if(af & n__ANNOUNCE_HEADER){
765 if(!(af & n_ANNOUNCE_MAIN_CALL) && ok_blook(bsdannounce))
766 n_OBSOLETE(_("*bsdannounce* is now default behaviour"));
767 vec[0] = mdot;
768 vec[1] = 0;
769 print_header_group(vec); /* XXX errors? */
770 af |= n__ANNOUNCE_ANY;
773 if(af & n__ANNOUNCE_ANY)
774 fflush(n_stdout);
775 NYD_LEAVE;
778 FL int
779 getmdot(int nmail)
781 struct message *mp;
782 char *cp;
783 int mdot;
784 enum mflag avoid = MHIDDEN | MDELETED;
785 NYD_ENTER;
787 if (!nmail) {
788 if (ok_blook(autothread)) {
789 n_OBSOLETE(_("please use *autosort=thread* instead of *autothread*"));
790 c_thread(NULL);
791 } else if ((cp = ok_vlook(autosort)) != NULL) {
792 if (mb.mb_sorted != NULL)
793 n_free(mb.mb_sorted);
794 mb.mb_sorted = sstrdup(cp);
795 c_sort(NULL);
798 if (mb.mb_type == MB_VOID) {
799 mdot = 1;
800 goto jleave;
803 if (nmail)
804 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
805 if ((mp->m_flag & (MNEWEST | avoid)) == MNEWEST)
806 break;
808 if (!nmail || PTRCMP(mp, >=, message + msgCount)) {
809 if (mb.mb_threaded) {
810 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
811 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
812 break;
813 } else {
814 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
815 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
816 break;
820 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
821 if (mb.mb_threaded) {
822 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
823 if (mp->m_flag & MFLAGGED)
824 break;
825 } else {
826 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
827 if (mp->m_flag & MFLAGGED)
828 break;
832 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
833 if (mb.mb_threaded) {
834 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
835 if (!(mp->m_flag & (MREAD | avoid)))
836 break;
837 } else {
838 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
839 if (!(mp->m_flag & (MREAD | avoid)))
840 break;
844 if (nmail &&
845 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
846 mdot = (int)PTR2SIZE(mp - message + 1);
847 else if (ok_blook(showlast)) {
848 if (mb.mb_threaded) {
849 for (mp = this_in_thread(threadroot, -1); mp;
850 mp = prev_in_thread(mp))
851 if (!(mp->m_flag & avoid))
852 break;
853 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
854 } else {
855 for (mp = message + msgCount - 1; mp >= message; --mp)
856 if (!(mp->m_flag & avoid))
857 break;
858 mdot = (mp >= message) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
860 } else if (!nmail &&
861 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
862 mdot = (int)PTR2SIZE(mp - message + 1);
863 else if (mb.mb_threaded) {
864 for (mp = threadroot; mp; mp = next_in_thread(mp))
865 if (!(mp->m_flag & avoid))
866 break;
867 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : 1;
868 } else {
869 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
870 if (!(mp->m_flag & avoid))
871 break;
872 mdot = PTRCMP(mp, <, message + msgCount)
873 ? (int)PTR2SIZE(mp - message + 1) : 1;
875 jleave:
876 NYD_LEAVE;
877 return mdot;
880 FL void
881 initbox(char const *name)
883 bool_t err;
884 char *tempMesg;
885 NYD_ENTER;
887 if (mb.mb_type != MB_VOID)
888 n_strscpy(prevfile, mailname, PATH_MAX);
890 /* TODO name always NE mailname (but goes away for objects anyway)
891 * TODO Well, not true no more except that in parens */
892 _update_mailname((name != mailname) ? name : NULL);
894 err = FAL0;
895 if((mb.mb_otf = Ftmp(&tempMesg, "tmpmbox", OF_WRONLY | OF_HOLDSIGS)) ==
896 NULL){
897 n_perr(_("initbox: temporary mail message file, writer"), 0);
898 err = TRU1;
899 }else if((mb.mb_itf = safe_fopen(tempMesg, "r", NULL)) == NULL) {
900 n_perr(_("initbox: temporary mail message file, reader"), 0);
901 err = TRU1;
903 Ftmp_release(&tempMesg);
904 if(err)
905 exit(n_EXIT_ERR);
907 message_reset();
908 mb.mb_active = MB_NONE;
909 mb.mb_threaded = 0;
910 #ifdef HAVE_IMAP
911 mb.mb_flags = MB_NOFLAGS;
912 #endif
913 if (mb.mb_sorted != NULL) {
914 n_free(mb.mb_sorted);
915 mb.mb_sorted = NULL;
917 dot = prevdot = threadroot = NULL;
918 n_pstate &= ~n_PS_DID_PRINT_DOT;
919 NYD_LEAVE;
922 FL char const *
923 n_folder_query(void){
924 struct n_string s, *sp = &s;
925 enum protocol proto;
926 char *cp;
927 char const *rv, *adjcp;
928 bool_t err;
929 NYD_ENTER;
931 sp = n_string_creat_auto(sp);
933 /* *folder* is linked with *folder_resolved*: we only use the latter */
934 for(err = FAL0;;){
935 if((rv = ok_vlook(folder_resolved)) != NULL)
936 break;
938 /* POSIX says:
939 * If directory does not start with a <slash> ('/'), the contents
940 * of HOME shall be prefixed to it.
941 * And:
942 * If folder is unset or set to null, [.] filenames beginning with
943 * '+' shall refer to files in the current directory.
944 * We may have the result already */
945 rv = n_empty;
946 err = FAL0;
948 if((cp = ok_vlook(folder)) == NULL)
949 goto jset;
951 /* Expand the *folder*; skip %: prefix for simplicity of use */
952 if(cp[0] == '%' && cp[1] == ':')
953 cp += 2;
954 if((err = (cp = fexpand(cp, FEXP_NSPECIAL | FEXP_NFOLDER | FEXP_NSHELL)
955 ) == NULL) || *cp == '\0')
956 goto jset;
957 else{
958 size_t i;
960 for(i = strlen(cp);;){
961 if(--i == 0)
962 goto jset;
963 if(cp[i] != '/'){
964 cp[++i] = '\0';
965 break;
970 switch((proto = which_protocol(cp, FAL0, FAL0, &adjcp))){
971 case PROTO_POP3:
972 n_err(_("*folder* can't be set to a flat, read-only POP3 account\n"));
973 err = TRU1;
974 goto jset;
975 case PROTO_IMAP:
976 #ifdef HAVE_IMAP
977 rv = cp;
978 if(!strcmp(rv, protbase(rv)))
979 rv = savecatsep(rv, '/', n_empty);
980 #else
981 n_err(_("*folder*: IMAP support not compiled in\n"));
982 err = TRU1;
983 #endif
984 goto jset;
985 default:
986 /* Further expansion desired */
987 break;
990 /* Prefix HOME as necessary */
991 if(*adjcp != '/'){ /* XXX path_is_absolute() */
992 size_t l1, l2;
993 char const *home;
995 home = ok_vlook(HOME);
996 l1 = strlen(home);
997 l2 = strlen(cp);
999 sp = n_string_reserve(sp, l1 + 1 + l2 +1);
1000 if(cp != adjcp){
1001 size_t i;
1003 sp = n_string_push_buf(sp, cp, i = PTR2SIZE(adjcp - cp));
1004 cp += i;
1005 l2 -= i;
1007 sp = n_string_push_buf(sp, home, l1);
1008 sp = n_string_push_c(sp, '/');
1009 sp = n_string_push_buf(sp, cp, l2);
1010 cp = n_string_cp(sp);
1011 sp = n_string_drop_ownership(sp);
1014 /* TODO Since our visual mailname is resolved via realpath(3) if available
1015 * TODO to avoid that we loose track of our currently open folder in case
1016 * TODO we chdir away, but still checks the leading path portion against
1017 * TODO n_folder_query() to be able to abbreviate to the +FOLDER syntax if
1018 * TODO possible, we need to realpath(3) the folder, too */
1019 #ifndef HAVE_REALPATH
1020 rv = cp;
1021 #else
1022 assert(sp->s_len == 0 && sp->s_dat == NULL);
1023 # ifndef HAVE_REALPATH_NULL
1024 sp = n_string_reserve(sp, PATH_MAX +1);
1025 # endif
1027 if((sp->s_dat = realpath(cp, sp->s_dat)) != NULL){
1028 # ifdef HAVE_REALPATH_NULL
1029 n_string_cp(sp = n_string_assign_cp(sp, cp = sp->s_dat));
1030 (free)(cp);
1031 # endif
1032 rv = sp->s_dat;
1033 }else if(n_err_no == n_ERR_NOENT)
1034 rv = cp;
1035 else{
1036 n_err(_("Can't canonicalize *folder*: %s\n"),
1037 n_shexp_quote_cp(cp, FAL0));
1038 err = TRU1;
1039 rv = n_empty;
1041 sp = n_string_drop_ownership(sp);
1042 #endif /* HAVE_REALPATH */
1044 /* Always append a solidus to our result path upon success */
1045 if(!err){
1046 size_t i;
1048 if(rv[(i = strlen(rv)) - 1] != '/'){
1049 sp = n_string_reserve(sp, i + 1 +1);
1050 sp = n_string_push_buf(sp, rv, i);
1051 sp = n_string_push_c(sp, '/');
1052 rv = n_string_cp(sp);
1053 sp = n_string_drop_ownership(sp);
1057 jset:
1058 n_PS_ROOT_BLOCK(ok_vset(folder_resolved, rv));
1061 if(err){
1062 n_err(_("*folder* is not resolvable, using CWD\n"));
1063 assert(rv != NULL && *rv == '\0');
1065 NYD_LEAVE;
1066 return rv;
1069 FL int
1070 n_folder_mbox_prepare_append(FILE *fout, struct stat *st_or_null){
1071 /* TODO n_folder_mbox_prepare_append -> Mailbox->append() */
1072 struct stat stb;
1073 char buf[2];
1074 int rv;
1075 bool_t needsep;
1076 NYD2_ENTER;
1078 if(!fseek(fout, -2L, SEEK_END)){
1079 if(fread(buf, sizeof *buf, 2, fout) != 2)
1080 goto jerrno;
1081 needsep = (buf[0] != '\n' || buf[1] != '\n');
1082 }else{
1083 rv = n_err_no;
1085 if(st_or_null == NULL){
1086 st_or_null = &stb;
1087 if(fstat(fileno(fout), st_or_null))
1088 goto jerrno;
1091 if(st_or_null->st_size >= 2)
1092 goto jleave;
1093 if(st_or_null->st_size == 0){
1094 rv = n_ERR_NONE;
1095 goto jleave;
1098 if(fseek(fout, -1L, SEEK_END))
1099 goto jerrno;
1100 if(fread(buf, sizeof *buf, 1, fout) != 1)
1101 goto jerrno;
1102 needsep = (buf[0] != '\n');
1105 rv = n_ERR_NONE;
1106 if(fflush(fout) || (needsep && putc('\n', fout) == EOF))
1107 jerrno:
1108 rv = n_err_no;
1109 jleave:
1110 NYD2_LEAVE;
1111 return rv;
1114 /* s-it-mode */