a_go_evaluate(): fix un/signed comparison
[s-mailx.git] / folder.c
blob0fdb524eee006d6f1e7552bcd5830f601ecb0645
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 */
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 n_INLINE size_t __narrow_suffix(char const *cp, size_t cpl, size_t maxl);
49 #endif
51 /**/
52 static void a_folder_info(void);
54 /* Set up the input pointers while copying the mail file into /tmp */
55 static void a_folder_mbox_setptr(FILE *ibuf, off_t offset);
57 static bool_t
58 _update_mailname(char const *name) /* TODO 2MUCH work, cache, prop of Object! */
60 char const *foldp;
61 char *mailp, *dispp;
62 size_t i, j, foldlen;
63 bool_t rv;
64 NYD_ENTER;
66 /* Don't realpath(3) if it's only an update request */
67 if(name != NULL){
68 #ifdef HAVE_REALPATH
69 char const *adjname;
70 enum protocol p;
72 p = which_protocol(name, TRU1, TRU1, &adjname);
74 if(p == PROTO_FILE || p == PROTO_MAILDIR){
75 name = adjname;
76 if (realpath(name, mailname) == NULL && n_err_no != n_ERR_NOENT) {
77 n_err(_("Can't canonicalize %s\n"), n_shexp_quote_cp(name, FAL0));
78 goto jdocopy;
80 }else
81 jdocopy:
82 #endif
83 n_strscpy(mailname, name, sizeof(mailname));
86 mailp = mailname;
87 dispp = displayname;
89 /* Don't display an absolute path but "+FOLDER" if under *folder* */
90 if(*(foldp = n_folder_query()) != '\0'){
91 foldlen = strlen(foldp);
92 if(strncmp(foldp, mailp, foldlen))
93 foldlen = 0;
94 }else
95 foldlen = 0;
97 /* We want to see the name of the folder .. on the screen */
98 i = strlen(mailp);
99 if(i < sizeof(displayname) - 3 -1){
100 if(foldlen > 0){
101 *dispp++ = '+';
102 *dispp++ = '[';
103 memcpy(dispp, mailp, foldlen);
104 dispp += foldlen;
105 mailp += foldlen;
106 *dispp++ = ']';
107 memcpy(dispp, mailp, i -= foldlen);
108 dispp[i] = '\0';
109 }else
110 memcpy(dispp, mailp, i +1);
111 rv = TRU1;
112 }else{
113 /* Avoid disrupting multibyte sequences (if possible) */
114 #ifndef HAVE_C90AMEND1
115 j = sizeof(displayname) / 3 - 3;
116 i -= sizeof(displayname) - (1/* + */ + 3) - j;
117 #else
118 j = field_detect_clip(sizeof(displayname) / 3, mailp, i);
119 i = j + __narrow_suffix(mailp + j, i - j,
120 sizeof(displayname) - (1/* + */ + 3 + 1) - j);
121 #endif
122 snprintf(dispp, sizeof(displayname), "%s%.*s...%s",
123 (foldlen > 0 ? "[+]" : ""), (int)j, mailp, mailp + i);
124 rv = FAL0;
127 n_PS_ROOT_BLOCK((ok_vset(mailbox_resolved, mailname),
128 ok_vset(mailbox_display, displayname)));
129 NYD_LEAVE;
130 return rv;
133 #ifdef HAVE_C90AMEND1
134 n_INLINE size_t
135 __narrow_suffix(char const *cp, size_t cpl, size_t maxl)
137 int err;
138 size_t i, ok;
139 NYD_ENTER;
141 for (err = ok = i = 0; cpl > maxl || err;) {
142 int ml = mblen(cp, cpl);
143 if (ml < 0) { /* XXX _narrow_suffix(): mblen() error; action? */
144 (void)mblen(NULL, 0);
145 err = 1;
146 ml = 1;
147 } else {
148 if (!err)
149 ok = i;
150 err = 0;
151 if (ml == 0)
152 break;
154 cp += ml;
155 i += ml;
156 cpl -= ml;
158 NYD_LEAVE;
159 return ok;
161 #endif /* HAVE_C90AMEND1 */
163 static void
164 a_folder_info(void){
165 struct message *mp;
166 int u, n, d, s, hidden, moved;
167 NYD2_ENTER;
169 if(mb.mb_type == MB_VOID){
170 fprintf(n_stdout, _("(Currently no active mailbox)"));
171 goto jleave;
174 s = d = hidden = moved = 0;
175 for (mp = message, n = 0, u = 0; PTRCMP(mp, <, message + msgCount); ++mp) {
176 if (mp->m_flag & MNEW)
177 ++n;
178 if ((mp->m_flag & MREAD) == 0)
179 ++u;
180 if ((mp->m_flag & (MDELETED | MSAVED)) == (MDELETED | MSAVED))
181 ++moved;
182 if ((mp->m_flag & (MDELETED | MSAVED)) == MDELETED)
183 ++d;
184 if ((mp->m_flag & (MDELETED | MSAVED)) == MSAVED)
185 ++s;
186 if (mp->m_flag & MHIDDEN)
187 ++hidden;
190 /* If displayname gets truncated the user effectively has no option to see
191 * the full pathname of the mailbox, so print it at least for '? fi' */
192 fprintf(n_stdout, "%s: ", n_shexp_quote_cp(
193 (_update_mailname(NULL) ? displayname : mailname), FAL0));
194 if (msgCount == 1)
195 fprintf(n_stdout, _("1 message"));
196 else
197 fprintf(n_stdout, _("%d messages"), msgCount);
198 if (n > 0)
199 fprintf(n_stdout, _(" %d new"), n);
200 if (u-n > 0)
201 fprintf(n_stdout, _(" %d unread"), u);
202 if (d > 0)
203 fprintf(n_stdout, _(" %d deleted"), d);
204 if (s > 0)
205 fprintf(n_stdout, _(" %d saved"), s);
206 if (moved > 0)
207 fprintf(n_stdout, _(" %d moved"), moved);
208 if (hidden > 0)
209 fprintf(n_stdout, _(" %d hidden"), hidden);
210 if (mb.mb_perm == 0)
211 fprintf(n_stdout, _(" [Read-only]"));
212 #ifdef HAVE_IMAP
213 if (mb.mb_type == MB_CACHE)
214 fprintf(n_stdout, _(" [Disconnected]"));
215 #endif
217 jleave:
218 putc('\n', n_stdout);
219 NYD2_LEAVE;
222 static void
223 a_folder_mbox_setptr(FILE *ibuf, off_t offset) /* TODO Mailbox->setptr() */
225 struct message self;
226 char const *cp2;
227 char *linebuf, *cp;
228 int selfcnt, c;
229 bool_t need_rfc4155, maybe, inhead, from_;
230 size_t filesize, linesize, cnt;
231 NYD_ENTER;
233 memset(&self, 0, sizeof self);
234 self.m_flag = MUSED | MNEW | MNEWEST;
235 filesize = mailsize - offset;
236 offset = ftell(mb.mb_otf);
237 need_rfc4155 = ok_blook(mbox_rfc4155);
238 maybe = TRU1;
239 inhead = FAL0;
240 selfcnt = 0;
241 linebuf = NULL, linesize = 0; /* TODO string pool */
243 for (;;) {
244 if (fgetline(&linebuf, &linesize, &filesize, &cnt, ibuf, 0) == NULL) {
245 self.m_xsize = self.m_size;
246 self.m_xlines = self.m_lines;
247 self.m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
248 if (selfcnt > 0)
249 message_append(&self);
250 message_append_null();
251 if (linebuf != NULL)
252 n_free(linebuf);
253 break;
256 #ifdef notdef
257 if (linebuf[0] == '\0')
258 linebuf[0] = '.';
259 #endif
260 /* XXX Convert CRLF to LF; this should be rethought in that
261 * XXX CRLF input should possibly end as CRLF output? */
262 if (cnt >= 2 && linebuf[cnt - 1] == '\n' && linebuf[cnt - 2] == '\r')
263 linebuf[--cnt - 1] = '\n';
264 fwrite(linebuf, sizeof *linebuf, cnt, mb.mb_otf);
265 if (ferror(mb.mb_otf)) {
266 n_perr(_("/tmp"), 0);
267 exit(n_EXIT_ERR);
269 if (linebuf[cnt - 1] == '\n')
270 linebuf[cnt - 1] = '\0';
271 /* TODO In v15 this should use a/the flat MIME parser in order to ignore
272 * TODO "From " when MIME boundaries are active -- whereas this opens
273 * TODO another can of worms, it very likely is better than messing up
274 * TODO MIME because of a "From " line! */
275 if (maybe && linebuf[0] == 'F' &&
276 (from_ = is_head(linebuf, cnt, TRU1)) &&
277 (from_ == TRU1 || !need_rfc4155)) {
278 /* TODO char date[n_FROM_DATEBUF];
279 * TODO extract_date_from_from_(linebuf, cnt, date);
280 * TODO self.m_time = 10000; */
281 if (from_ == TRUM1) {
282 if (n_poption & n_PO_D_V)
283 n_err(_("Invalid MBOX \"From_ line\": %.*s\n"),
284 (int)cnt, linebuf);
285 else if (!(mb.mb_active & MB_FROM__WARNED))
286 n_err(_("MBOX mailbox contains non-conforming From_ line(s)!\n"
287 " Message boundaries may have been falsely detected!\n"
288 " Setting variable *mbox-rfc4155* and reopen should improve "
289 "the result.\n"
290 " If so, make changes permanent: \"copy * SOME-FILE\". "
291 "Then unset *mbox-rfc4155*\n"));
292 mb.mb_active |= MB_FROM__WARNED;
294 self.m_xsize = self.m_size;
295 self.m_xlines = self.m_lines;
296 self.m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
297 if (selfcnt++ > 0)
298 message_append(&self);
299 msgCount++;
300 self.m_flag = MUSED | MNEW | MNEWEST;
301 self.m_size = 0;
302 self.m_lines = 0;
303 self.m_block = mailx_blockof(offset);
304 self.m_offset = mailx_offsetof(offset);
305 inhead = TRU1;
306 } else if (linebuf[0] == 0) {
307 inhead = FAL0;
308 } else if (inhead) {
309 for (cp = linebuf, cp2 = "status";; ++cp) {
310 if ((c = *cp2++) == 0) {
311 while (c = *cp++, whitechar(c))
313 if (cp[-1] != ':')
314 break;
315 while ((c = *cp++) != '\0')
316 if (c == 'R')
317 self.m_flag |= MREAD;
318 else if (c == 'O')
319 self.m_flag &= ~MNEW;
320 break;
322 if (*cp != c && *cp != upperconv(c))
323 break;
325 for (cp = linebuf, cp2 = "x-status";; ++cp) {
326 if ((c = *cp2++) == 0) {
327 while ((c = *cp++, whitechar(c)))
329 if (cp[-1] != ':')
330 break;
331 while ((c = *cp++) != '\0')
332 if (c == 'F')
333 self.m_flag |= MFLAGGED;
334 else if (c == 'A')
335 self.m_flag |= MANSWERED;
336 else if (c == 'T')
337 self.m_flag |= MDRAFTED;
338 break;
340 if (*cp != c && *cp != upperconv(c))
341 break;
344 offset += cnt;
345 self.m_size += cnt;
346 ++self.m_lines;
347 maybe = (linebuf[0] == 0);
349 NYD_LEAVE;
352 FL int
353 setfile(char const *name, enum fedit_mode fm) /* TODO oh my god */
355 /* TODO This all needs to be converted to URL:: and Mailbox:: */
356 static int shudclob;
358 struct stat stb;
359 size_t offset;
360 char const *who, *orig_name;
361 int rv, omsgCount = 0;
362 FILE *ibuf = NULL, *lckfp = NULL;
363 bool_t isdevnull = FAL0;
364 NYD_ENTER;
366 n_pstate &= ~n_PS_SETFILE_OPENED;
368 /* C99 */{
369 enum fexp_mode fexpm;
371 if((who = shortcut_expand(name)) != NULL){
372 fexpm = FEXP_NSHORTCUT/* XXX | FEXP_NSHELL*/;
373 name = who;
374 }else
375 fexpm = FEXP_NSHELL;
377 if(name[0] == '%'){
378 char const *cp;
380 fm |= FEDIT_SYSBOX; /* TODO fexpand() needs to tell is-valid-user! */
381 if(*(who = &name[1]) == ':')
382 ++who;
383 if((cp = strrchr(who, '/')) != NULL)
384 who = &cp[1];
385 if(*who == '\0')
386 goto jlogname;
387 }else{
388 jlogname:
389 if(fm & FEDIT_ACCOUNT){
390 if((who = ok_vlook(account)) == NULL)
391 who = ACCOUNT_NULL;
392 who = savecatsep(_("account"), ' ', who);
393 }else
394 who = ok_vlook(LOGNAME);
397 if ((name = fexpand(name, fexpm)) == NULL)
398 goto jem1;
401 /* For at least substdate() users TODO -> eventloop tick */
402 time_current_update(&time_current, FAL0);
404 switch (which_protocol(orig_name = name, TRU1, TRU1, &name)) {
405 case PROTO_FILE:
406 isdevnull = ((n_poption & n_PO_BATCH_FLAG) &&
407 !strcmp(name, n_path_devnull));
408 #ifdef HAVE_REALPATH
409 do { /* TODO we need objects, goes away then */
410 # ifdef HAVE_REALPATH_NULL
411 char *cp;
413 if ((cp = realpath(name, NULL)) != NULL) {
414 name = savestr(cp);
415 (free)(cp);
417 # else
418 char cbuf[PATH_MAX];
420 if (realpath(name, cbuf) != NULL)
421 name = savestr(cbuf);
422 # endif
423 } while (0);
424 #endif
425 rv = 1;
426 break;
427 #ifdef HAVE_MAILDIR
428 case PROTO_MAILDIR:
429 shudclob = 1;
430 rv = maildir_setfile(who, name, fm);
431 goto jleave;
432 #endif
433 #ifdef HAVE_POP3
434 case PROTO_POP3:
435 shudclob = 1;
436 rv = pop3_setfile(who, orig_name, fm);
437 goto jleave;
438 #endif
439 #ifdef HAVE_IMAP
440 case PROTO_IMAP:
441 shudclob = 1;
442 if((fm & FEDIT_NEWMAIL) && mb.mb_type == MB_CACHE)
443 rv = 1;
444 else
445 rv = imap_setfile(who, orig_name, fm);
446 goto jleave;
447 #endif
448 default:
449 n_err(_("Cannot handle protocol: %s\n"), orig_name);
450 goto jem1;
453 if ((ibuf = n_fopen_any(savecat("file://", name), "r", NULL)) == NULL) {
454 int e = n_err_no;
456 if ((fm & FEDIT_SYSBOX) && e == n_ERR_NOENT) {
457 if (!(fm & FEDIT_ACCOUNT) && strcmp(who, ok_vlook(LOGNAME)) &&
458 getpwnam(who) == NULL) {
459 n_err(_("%s is not a user of this system\n"),
460 n_shexp_quote_cp(who, FAL0));
461 goto jem2;
463 if (!(n_poption & n_PO_QUICKRUN_MASK) && ok_blook(bsdcompat))
464 n_err(_("No mail for %s at %s\n"),
465 who, n_shexp_quote_cp(name, FAL0));
467 if (fm & FEDIT_NEWMAIL)
468 goto jleave;
470 mb.mb_type = MB_VOID;
471 if (ok_blook(emptystart)) {
472 if (!(n_poption & n_PO_QUICKRUN_MASK) && !ok_blook(bsdcompat))
473 n_perr(name, e);
474 /* We must avoid returning -1 and causing program exit */
475 rv = 1;
476 goto jleave;
478 n_perr(name, e);
479 goto jem1;
482 if (fstat(fileno(ibuf), &stb) == -1) {
483 if (fm & FEDIT_NEWMAIL)
484 goto jleave;
485 n_perr(_("fstat"), 0);
486 goto jem1;
489 if (S_ISREG(stb.st_mode) || isdevnull) {
490 /* EMPTY */
491 } else {
492 if (fm & FEDIT_NEWMAIL)
493 goto jleave;
494 n_err_no = S_ISDIR(stb.st_mode) ? n_ERR_ISDIR : n_ERR_INVAL;
495 n_perr(name, 0);
496 goto jem1;
499 if (shudclob && !(fm & FEDIT_NEWMAIL) && !quit(FAL0))
500 goto jem2;
502 hold_sigs();
504 #ifdef HAVE_SOCKETS
505 if (!(fm & FEDIT_NEWMAIL) && mb.mb_sock.s_fd >= 0)
506 sclose(&mb.mb_sock); /* TODO sorry? VMAILFS->close(), thank you */
507 #endif
509 /* TODO There is no intermediate VOID box we've switched to: name may
510 * TODO point to the same box that we just have written, so any updates
511 * TODO we won't see! Reopen again in this case. RACY! Goes with VOID! */
512 /* TODO In addition: in case of compressed/hook boxes we lock a temporary! */
513 /* TODO We may uselessly open twice but quit() doesn't say whether we were
514 * TODO modified so we can't tell: Mailbox::is_modified() :-(( */
515 if (/*shudclob && !(fm & FEDIT_NEWMAIL) &&*/ !strcmp(name, mailname)) {
516 name = mailname;
517 Fclose(ibuf);
519 if ((ibuf = n_fopen_any(name, "r", NULL)) == NULL ||
520 fstat(fileno(ibuf), &stb) == -1 ||
521 (!S_ISREG(stb.st_mode) && !isdevnull)) {
522 n_perr(name, 0);
523 rele_sigs();
524 goto jem2;
528 /* Copy the messages into /tmp and set pointers */
529 if (!(fm & FEDIT_NEWMAIL)) {
530 if (isdevnull) {
531 mb.mb_type = MB_VOID;
532 mb.mb_perm = 0;
533 } else {
534 mb.mb_type = MB_FILE;
535 mb.mb_perm = (((n_poption & n_PO_R_FLAG) || (fm & FEDIT_RDONLY) ||
536 access(name, W_OK) < 0) ? 0 : MB_DELE | MB_EDIT);
537 if (shudclob) {
538 if (mb.mb_itf) {
539 fclose(mb.mb_itf);
540 mb.mb_itf = NULL;
542 if (mb.mb_otf) {
543 fclose(mb.mb_otf);
544 mb.mb_otf = NULL;
548 shudclob = 1;
549 if (fm & FEDIT_SYSBOX)
550 n_pstate &= ~n_PS_EDIT;
551 else
552 n_pstate |= n_PS_EDIT;
553 initbox(name);
554 offset = 0;
555 } else {
556 fseek(mb.mb_otf, 0L, SEEK_END);
557 /* TODO Doing this without holding a lock is.. And not err checking.. */
558 fseek(ibuf, mailsize, SEEK_SET);
559 offset = mailsize;
560 omsgCount = msgCount;
563 if (isdevnull)
564 lckfp = (FILE*)-1;
565 else if (!(n_pstate & n_PS_EDIT))
566 lckfp = n_dotlock(name, fileno(ibuf), FLT_READ, offset,0,
567 (fm & FEDIT_NEWMAIL ? 0 : UIZ_MAX));
568 else if (n_file_lock(fileno(ibuf), FLT_READ, offset,0,
569 (fm & FEDIT_NEWMAIL ? 0 : UIZ_MAX)))
570 lckfp = (FILE*)-1;
572 if (lckfp == NULL) {
573 if (!(fm & FEDIT_NEWMAIL)) {
574 char const *emsg = (n_pstate & n_PS_EDIT)
575 ? N_("Unable to lock mailbox, aborting operation")
576 : N_("Unable to (dot) lock mailbox, aborting operation");
577 n_perr(V_(emsg), 0);
579 rele_sigs();
580 if (!(fm & FEDIT_NEWMAIL))
581 goto jem1;
582 goto jleave;
585 mailsize = fsize(ibuf);
587 /* TODO This is too simple minded? We should regenerate an index file
588 * TODO to be able to truly tell whether *anything* has changed! */
589 if ((fm & FEDIT_NEWMAIL) && UICMP(z, mailsize, <=, offset)) {
590 rele_sigs();
591 goto jleave;
593 a_folder_mbox_setptr(ibuf, offset);
594 setmsize(msgCount);
595 if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted) {
596 mb.mb_threaded = 0;
597 c_sort((void*)-1);
600 Fclose(ibuf);
601 ibuf = NULL;
602 if (lckfp != NULL && lckfp != (FILE*)-1) {
603 Pclose(lckfp, FAL0);
604 /*lckfp = NULL;*/
607 if (!(fm & FEDIT_NEWMAIL)) {
608 n_pstate &= ~n_PS_SAW_COMMAND;
609 n_pstate |= n_PS_SETFILE_OPENED;
612 rele_sigs();
614 rv = (msgCount == 0);
615 if(rv)
616 n_err_no = n_ERR_NODATA;
618 if(n_poption & n_PO_EXISTONLY)
619 goto jleave;
621 if(rv){
622 if(!(n_pstate & n_PS_EDIT) || (fm & FEDIT_NEWMAIL)){
623 if(!(fm & FEDIT_NEWMAIL)){
624 if (!ok_blook(emptystart))
625 n_err(_("No mail for %s at %s\n"),
626 who, n_shexp_quote_cp(name, FAL0));
628 goto jleave;
632 if(fm & FEDIT_NEWMAIL)
633 newmailinfo(omsgCount);
634 jleave:
635 if (ibuf != NULL) {
636 Fclose(ibuf);
637 if (lckfp != NULL && lckfp != (FILE*)-1)
638 Pclose(lckfp, FAL0);
640 NYD_LEAVE;
641 return rv;
642 jem2:
643 mb.mb_type = MB_VOID;
644 jem1:
645 n_err_no = n_ERR_NOTOBACCO;
646 rv = -1;
647 goto jleave;
650 FL int
651 newmailinfo(int omsgCount)
653 int mdot, i;
654 NYD_ENTER;
656 for (i = 0; i < omsgCount; ++i)
657 message[i].m_flag &= ~MNEWEST;
659 if (msgCount > omsgCount) {
660 for (i = omsgCount; i < msgCount; ++i)
661 message[i].m_flag |= MNEWEST;
662 fprintf(n_stdout, _("New mail has arrived.\n"));
663 if ((i = msgCount - omsgCount) == 1)
664 fprintf(n_stdout, _("Loaded 1 new message.\n"));
665 else
666 fprintf(n_stdout, _("Loaded %d new messages.\n"), i);
667 } else
668 fprintf(n_stdout, _("Loaded %d messages.\n"), msgCount);
670 temporary_folder_hook_check(TRU1);
672 mdot = getmdot(1);
674 if(ok_blook(header) && (i = omsgCount + 1) <= msgCount){
675 #ifdef HAVE_IMAP
676 if(mb.mb_type == MB_IMAP)
677 imap_getheaders(i, msgCount); /* TODO not here */
678 #endif
679 for(omsgCount = 0; i <= msgCount; ++omsgCount, ++i)
680 n_msgvec[omsgCount] = i;
681 n_msgvec[omsgCount] = 0;
682 print_headers(n_msgvec, FAL0, FAL0);
684 NYD_LEAVE;
685 return mdot;
688 FL void
689 setmsize(int sz)
691 NYD_ENTER;
692 if (n_msgvec != NULL)
693 n_free(n_msgvec);
694 n_msgvec = n_calloc(sz +1, sizeof *n_msgvec);
695 NYD_LEAVE;
698 FL void
699 print_header_summary(char const *Larg)
701 size_t i;
702 NYD_ENTER;
704 getmdot(0);
705 #ifdef HAVE_IMAP
706 if(mb.mb_type == MB_IMAP)
707 imap_getheaders(0, msgCount); /* TODO not here */
708 #endif
709 assert(n_msgvec != NULL);
711 if (Larg != NULL) {
712 /* Avoid any messages XXX add a make_mua_silent() and use it? */
713 if ((n_poption & (n_PO_VERB | n_PO_EXISTONLY)) == n_PO_EXISTONLY) {
714 n_stdout = freopen(n_path_devnull, "w", stdout);
715 n_stderr = freopen(n_path_devnull, "w", stderr);
717 i = (n_getmsglist(n_shexp_quote_cp(Larg, FAL0), n_msgvec, 0, NULL) <= 0);
718 if (n_poption & n_PO_EXISTONLY)
719 n_exit_status = (int)i;
720 else if(i == 0)
721 print_headers(n_msgvec, TRU1, FAL0); /* TODO should be iterator! */
722 } else {
723 i = 0;
724 if(!mb.mb_threaded){
725 for(; UICMP(z, i, <, msgCount); ++i)
726 n_msgvec[i] = i + 1;
727 }else{
728 struct message *mp;
730 for(mp = threadroot; mp; ++i, mp = next_in_thread(mp))
731 n_msgvec[i] = (int)PTR2SIZE(mp - message + 1);
733 print_headers(n_msgvec, FAL0, TRU1); /* TODO should be iterator! */
735 NYD_LEAVE;
738 FL void
739 n_folder_announce(enum n_announce_flags af){
740 int vec[2], mdot;
741 NYD_ENTER;
743 mdot = (mb.mb_type == MB_VOID) ? 1 : getmdot(0);
744 dot = &message[mdot - 1];
746 if(af != n_ANNOUNCE_NONE && ok_blook(header) &&
747 ((af & n_ANNOUNCE_MAIN_CALL) ||
748 ((af & n_ANNOUNCE_CHANGE) && !ok_blook(posix))))
749 af |= n_ANNOUNCE_STATUS | n__ANNOUNCE_HEADER;
751 if(af & n_ANNOUNCE_STATUS){
752 a_folder_info();
753 af |= n__ANNOUNCE_ANY;
756 if(af & n__ANNOUNCE_HEADER){
757 if(!(af & n_ANNOUNCE_MAIN_CALL) && ok_blook(bsdannounce))
758 n_OBSOLETE(_("*bsdannounce* is now default behaviour"));
759 vec[0] = mdot;
760 vec[1] = 0;
761 print_header_group(vec); /* XXX errors? */
762 af |= n__ANNOUNCE_ANY;
765 if(af & n__ANNOUNCE_ANY)
766 fflush(n_stdout);
767 NYD_LEAVE;
770 FL int
771 getmdot(int nmail)
773 struct message *mp;
774 char *cp;
775 int mdot;
776 enum mflag avoid = MHIDDEN | MDELETED;
777 NYD_ENTER;
779 if (!nmail) {
780 if (ok_blook(autothread)) {
781 n_OBSOLETE(_("please use *autosort=thread* instead of *autothread*"));
782 c_thread(NULL);
783 } else if ((cp = ok_vlook(autosort)) != NULL) {
784 if (mb.mb_sorted != NULL)
785 n_free(mb.mb_sorted);
786 mb.mb_sorted = sstrdup(cp);
787 c_sort(NULL);
790 if (mb.mb_type == MB_VOID) {
791 mdot = 1;
792 goto jleave;
795 if (nmail)
796 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
797 if ((mp->m_flag & (MNEWEST | avoid)) == MNEWEST)
798 break;
800 if (!nmail || PTRCMP(mp, >=, message + msgCount)) {
801 if (mb.mb_threaded) {
802 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
803 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
804 break;
805 } else {
806 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
807 if ((mp->m_flag & (MNEW | avoid)) == MNEW)
808 break;
812 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
813 if (mb.mb_threaded) {
814 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
815 if (mp->m_flag & MFLAGGED)
816 break;
817 } else {
818 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
819 if (mp->m_flag & MFLAGGED)
820 break;
824 if ((mb.mb_threaded ? (mp == NULL) : PTRCMP(mp, >=, message + msgCount))) {
825 if (mb.mb_threaded) {
826 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
827 if (!(mp->m_flag & (MREAD | avoid)))
828 break;
829 } else {
830 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
831 if (!(mp->m_flag & (MREAD | avoid)))
832 break;
836 if (nmail &&
837 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
838 mdot = (int)PTR2SIZE(mp - message + 1);
839 else if (ok_blook(showlast)) {
840 if (mb.mb_threaded) {
841 for (mp = this_in_thread(threadroot, -1); mp;
842 mp = prev_in_thread(mp))
843 if (!(mp->m_flag & avoid))
844 break;
845 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
846 } else {
847 for (mp = message + msgCount - 1; mp >= message; --mp)
848 if (!(mp->m_flag & avoid))
849 break;
850 mdot = (mp >= message) ? (int)PTR2SIZE(mp - message + 1) : msgCount;
852 } else if (!nmail &&
853 (mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount)))
854 mdot = (int)PTR2SIZE(mp - message + 1);
855 else if (mb.mb_threaded) {
856 for (mp = threadroot; mp; mp = next_in_thread(mp))
857 if (!(mp->m_flag & avoid))
858 break;
859 mdot = (mp != NULL) ? (int)PTR2SIZE(mp - message + 1) : 1;
860 } else {
861 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
862 if (!(mp->m_flag & avoid))
863 break;
864 mdot = PTRCMP(mp, <, message + msgCount)
865 ? (int)PTR2SIZE(mp - message + 1) : 1;
867 jleave:
868 NYD_LEAVE;
869 return mdot;
872 FL void
873 initbox(char const *name)
875 bool_t err;
876 char *tempMesg;
877 NYD_ENTER;
879 if (mb.mb_type != MB_VOID)
880 n_strscpy(prevfile, mailname, PATH_MAX);
882 /* TODO name always NE mailname (but goes away for objects anyway)
883 * TODO Well, not true no more except that in parens */
884 _update_mailname((name != mailname) ? name : NULL);
886 err = FAL0;
887 if((mb.mb_otf = Ftmp(&tempMesg, "tmpmbox", OF_WRONLY | OF_HOLDSIGS)) ==
888 NULL){
889 n_perr(_("initbox: temporary mail message file, writer"), 0);
890 err = TRU1;
891 }else if((mb.mb_itf = safe_fopen(tempMesg, "r", NULL)) == NULL) {
892 n_perr(_("initbox: temporary mail message file, reader"), 0);
893 err = TRU1;
895 Ftmp_release(&tempMesg);
896 if(err)
897 exit(n_EXIT_ERR);
899 message_reset();
900 mb.mb_active = MB_NONE;
901 mb.mb_threaded = 0;
902 #ifdef HAVE_IMAP
903 mb.mb_flags = MB_NOFLAGS;
904 #endif
905 if (mb.mb_sorted != NULL) {
906 n_free(mb.mb_sorted);
907 mb.mb_sorted = NULL;
909 dot = prevdot = threadroot = NULL;
910 n_pstate &= ~n_PS_DID_PRINT_DOT;
911 NYD_LEAVE;
914 FL char const *
915 n_folder_query(void){
916 struct n_string s, *sp = &s;
917 enum protocol proto;
918 char *cp;
919 char const *rv, *adjcp;
920 bool_t err;
921 NYD_ENTER;
923 sp = n_string_creat_auto(sp);
925 /* *folder* is linked with *folder_resolved*: we only use the latter */
926 for(err = FAL0;;){
927 if((rv = ok_vlook(folder_resolved)) != NULL)
928 break;
930 /* POSIX says:
931 * If directory does not start with a <slash> ('/'), the contents
932 * of HOME shall be prefixed to it.
933 * And:
934 * If folder is unset or set to null, [.] filenames beginning with
935 * '+' shall refer to files in the current directory.
936 * We may have the result already */
937 rv = n_empty;
938 err = FAL0;
940 if((cp = ok_vlook(folder)) == NULL)
941 goto jset;
943 /* Expand the *folder*; skip %: prefix for simplicity of use */
944 if(cp[0] == '%' && cp[1] == ':')
945 cp += 2;
946 if((err = (cp = fexpand(cp, FEXP_NSPECIAL | FEXP_NFOLDER | FEXP_NSHELL)
947 ) == NULL) || *cp == '\0')
948 goto jset;
949 else{
950 size_t i;
952 for(i = strlen(cp);;){
953 if(--i == 0)
954 goto jset;
955 if(cp[i] != '/'){
956 cp[++i] = '\0';
957 break;
962 switch((proto = which_protocol(cp, FAL0, FAL0, &adjcp))){
963 case PROTO_POP3:
964 n_err(_("*folder* can't be set to a flat, read-only POP3 account\n"));
965 err = TRU1;
966 goto jset;
967 case PROTO_IMAP:
968 #ifdef HAVE_IMAP
969 rv = cp;
970 if(!strcmp(rv, protbase(rv)))
971 rv = savecatsep(rv, '/', n_empty);
972 #else
973 n_err(_("*folder*: IMAP support not compiled in\n"));
974 err = TRU1;
975 #endif
976 goto jset;
977 default:
978 /* Further expansion desired */
979 break;
982 /* Prefix HOME as necessary */
983 if(*adjcp != '/'){ /* XXX path_is_absolute() */
984 size_t l1, l2;
985 char const *home;
987 home = ok_vlook(HOME);
988 l1 = strlen(home);
989 l2 = strlen(cp);
991 sp = n_string_reserve(sp, l1 + 1 + l2 +1);
992 if(cp != adjcp){
993 size_t i;
995 sp = n_string_push_buf(sp, cp, i = PTR2SIZE(adjcp - cp));
996 cp += i;
997 l2 -= i;
999 sp = n_string_push_buf(sp, home, l1);
1000 sp = n_string_push_c(sp, '/');
1001 sp = n_string_push_buf(sp, cp, l2);
1002 cp = n_string_cp(sp);
1003 sp = n_string_drop_ownership(sp);
1006 /* TODO Since our visual mailname is resolved via realpath(3) if available
1007 * TODO to avoid that we loose track of our currently open folder in case
1008 * TODO we chdir away, but still checks the leading path portion against
1009 * TODO n_folder_query() to be able to abbreviate to the +FOLDER syntax if
1010 * TODO possible, we need to realpath(3) the folder, too */
1011 #ifndef HAVE_REALPATH
1012 rv = cp;
1013 #else
1014 assert(sp->s_len == 0 && sp->s_dat == NULL);
1015 # ifndef HAVE_REALPATH_NULL
1016 sp = n_string_reserve(sp, PATH_MAX +1);
1017 # endif
1019 if((sp->s_dat = realpath(cp, sp->s_dat)) != NULL){
1020 # ifdef HAVE_REALPATH_NULL
1021 n_string_cp(sp = n_string_assign_cp(sp, cp = sp->s_dat));
1022 (free)(cp);
1023 # endif
1024 rv = sp->s_dat;
1025 }else if(n_err_no == n_ERR_NOENT)
1026 rv = cp;
1027 else{
1028 n_err(_("Can't canonicalize *folder*: %s\n"),
1029 n_shexp_quote_cp(cp, FAL0));
1030 err = TRU1;
1031 rv = n_empty;
1033 sp = n_string_drop_ownership(sp);
1034 #endif /* HAVE_REALPATH */
1036 /* Always append a solidus to our result path upon success */
1037 if(!err){
1038 size_t i;
1040 if(rv[(i = strlen(rv)) - 1] != '/'){
1041 sp = n_string_reserve(sp, i + 1 +1);
1042 sp = n_string_push_buf(sp, rv, i);
1043 sp = n_string_push_c(sp, '/');
1044 rv = n_string_cp(sp);
1045 sp = n_string_drop_ownership(sp);
1049 jset:
1050 n_PS_ROOT_BLOCK(ok_vset(folder_resolved, rv));
1053 if(err){
1054 n_err(_("*folder* is not resolvable, using CWD\n"));
1055 assert(rv != NULL && *rv == '\0');
1057 NYD_LEAVE;
1058 return rv;
1061 FL int
1062 n_folder_mbox_prepare_append(FILE *fout, struct stat *st_or_null){
1063 /* TODO n_folder_mbox_prepare_append -> Mailbox->append() */
1064 struct stat stb;
1065 char buf[2];
1066 int rv;
1067 bool_t needsep;
1068 NYD2_ENTER;
1070 if(!fseek(fout, -2L, SEEK_END)){
1071 if(fread(buf, sizeof *buf, 2, fout) != 2)
1072 goto jerrno;
1073 needsep = (buf[0] != '\n' || buf[1] != '\n');
1074 }else{
1075 rv = n_err_no;
1077 if(st_or_null == NULL){
1078 st_or_null = &stb;
1079 if(fstat(fileno(fout), st_or_null))
1080 goto jerrno;
1083 if(st_or_null->st_size >= 2)
1084 goto jleave;
1085 if(st_or_null->st_size == 0){
1086 rv = n_ERR_NONE;
1087 goto jleave;
1090 if(fseek(fout, -1L, SEEK_END))
1091 goto jerrno;
1092 if(fread(buf, sizeof *buf, 1, fout) != 1)
1093 goto jerrno;
1094 needsep = (buf[0] != '\n');
1097 rv = n_ERR_NONE;
1098 if(fflush(fout) || (needsep && putc('\n', fout) == EOF))
1099 jerrno:
1100 rv = n_err_no;
1101 jleave:
1102 NYD2_LEAVE;
1103 return rv;
1106 /* s-it-mode */