streamline headers
[apps/madmutt.git] / lib-ui / hdrline.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000,2002 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #include <lib-ui/lib-ui.h>
11
12 #include <lib-mime/mime.h>
13 #include <lib-mx/mx.h>
14
15 #include "mutt.h"
16 #include "alias.h"
17 #include "crypt.h"
18 #include "sort.h"
19 #include "thread.h"
20 #include "charset.h"
21 #include "mutt_idna.h"
22
23 int mutt_is_mail_list (address_t * addr)
24 {
25   if (!rx_list_match (UnMailLists, addr->mailbox))
26     return rx_list_match (MailLists, addr->mailbox);
27   return 0;
28 }
29
30 int mutt_is_subscribed_list (address_t * addr)
31 {
32   if (!rx_list_match (UnMailLists, addr->mailbox)
33       && !rx_list_match (UnSubscribedLists, addr->mailbox))
34     return rx_list_match (SubscribedLists, addr->mailbox);
35   return 0;
36 }
37
38 /* Search for a mailing list in the list of addresses pointed to by adr.
39  * If one is found, print pfx and the name of the list into buf, then
40  * return 1.  Otherwise, simply return 0.
41  */
42 static int
43 check_for_mailing_list (address_t * adr, const char *pfx, char *buf, int buflen)
44 {
45   for (; adr; adr = adr->next) {
46     if (mutt_is_subscribed_list (adr)) {
47       if (pfx && buf && buflen)
48         snprintf (buf, buflen, "%s%s", pfx, mutt_get_name (adr));
49       return 1;
50     }
51   }
52   return 0;
53 }
54
55 /* Search for a mailing list in the list of addresses pointed to by adr.
56  * If one is found, print the address of the list into buf, then return 1.
57  * Otherwise, simply return 0.
58  */
59 static int check_for_mailing_list_addr (address_t * adr, char *buf, int buflen)
60 {
61   for (; adr; adr = adr->next) {
62     if (mutt_is_subscribed_list (adr)) {
63       if (buf && buflen)
64         snprintf (buf, buflen, "%s", adr->mailbox);
65       return 1;
66     }
67   }
68   return 0;
69 }
70
71
72 static int first_mailing_list (char *buf, ssize_t buflen, address_t * a)
73 {
74   for (; a; a = a->next) {
75     if (mutt_is_subscribed_list (a)) {
76       mutt_save_path (buf, buflen, a);
77       return 1;
78     }
79   }
80   return 0;
81 }
82
83 static void make_from (ENVELOPE * hdr, char *buf, ssize_t len, int do_lists)
84 {
85   int me;
86
87   me = mutt_addr_is_user (hdr->from);
88
89   if (do_lists || me) {
90     if (check_for_mailing_list (hdr->to, "To ", buf, len))
91       return;
92     if (check_for_mailing_list (hdr->cc, "Cc ", buf, len))
93       return;
94   }
95
96   if (me && hdr->to)
97     snprintf (buf, len, "To %s", mutt_get_name (hdr->to));
98   else if (me && hdr->cc)
99     snprintf (buf, len, "Cc %s", mutt_get_name (hdr->cc));
100   else if (hdr->from)
101     m_strcpy(buf, len, mutt_get_name(hdr->from));
102   else
103     *buf = 0;
104 }
105
106 static void make_from_addr (ENVELOPE * hdr, char *buf, ssize_t len,
107                             int do_lists)
108 {
109   int me;
110
111   me = mutt_addr_is_user (hdr->from);
112
113   if (do_lists || me) {
114     if (check_for_mailing_list_addr (hdr->to, buf, len))
115       return;
116     if (check_for_mailing_list_addr (hdr->cc, buf, len))
117       return;
118   }
119
120   if (me && hdr->to)
121     snprintf (buf, len, "%s", hdr->to->mailbox);
122   else if (me && hdr->cc)
123     snprintf (buf, len, "%s", hdr->cc->mailbox);
124   else if (hdr->from)
125     m_strcpy(buf, len, hdr->from->mailbox);
126   else
127     *buf = 0;
128 }
129
130 static int user_in_addr (address_t * a)
131 {
132   for (; a; a = a->next)
133     if (mutt_addr_is_user (a))
134       return 1;
135   return 0;
136 }
137
138 /* Return values:
139  * 0: user is not in list
140  * 1: user is unique recipient
141  * 2: user is in the TO list
142  * 3: user is in the CC list
143  * 4: user is originator
144  * 5: sent to a subscribed mailinglist
145  */
146 int mutt_user_is_recipient (HEADER * h)
147 {
148   ENVELOPE *env = h->env;
149
150   if (!h->recip_valid) {
151     h->recip_valid = 1;
152
153     if (mutt_addr_is_user (env->from))
154       h->recipient = 4;
155     else if (user_in_addr (env->to)) {
156       if (env->to->next || env->cc)
157         h->recipient = 2;       /* non-unique recipient */
158       else
159         h->recipient = 1;       /* unique recipient */
160     }
161     else if (user_in_addr (env->cc))
162       h->recipient = 3;
163     else if (check_for_mailing_list (env->to, NULL, NULL, 0))
164       h->recipient = 5;
165     else if (check_for_mailing_list (env->cc, NULL, NULL, 0))
166       h->recipient = 5;
167     else
168       h->recipient = 0;
169   }
170
171   return h->recipient;
172 }
173
174 /* %a = address of author
175  * %A = reply-to address (if present; otherwise: address of author
176  * %b = filename of the originating folder
177  * %B = the list to which the letter was sent
178  * %c = size of message in bytes
179  * %C = current message number
180  * %d = date and time of message using $date_format and sender's timezone
181  * %D = date and time of message using $date_format and local timezone
182  * %e = current message number in thread
183  * %E = number of messages in current thread
184  * %f = entire from line
185  * %F = like %n, unless from self
186  * %g = newsgroup name (if compiled with nntp support)
187  * %i = message-id
188  * %I = initials of author
189  * %l = number of lines in the message
190  * %L = like %F, except `lists' are displayed first
191  * %m = number of messages in the mailbox
192  * %n = name of author
193  * %N = score
194  * %O = like %L, except using address instead of name
195  * %s = subject
196  * %S = short message status (e.g., N/O/D/!/r/-)
197  * %t = `to:' field (recipients)
198  * %T = $to_chars
199  * %u = user (login) name of author
200  * %v = first name of author, unless from self
201  * %W = where user is (organization)
202  * %X = number of MIME attachments
203  * %y = `x-label:' field (if present)
204  * %Y = `x-label:' field (if present, tree unfolded, and != parent's x-label)
205  * %Z = status flags    */
206
207 struct hdr_format_info {
208   CONTEXT *ctx;
209   HEADER *hdr;
210 };
211
212 static const char *
213 hdr_format_str(char *dest, ssize_t destlen,
214                char op, const char *src, const char *prefix,
215                const char *ifstr, const char *elstr,
216                anytype data, format_flag flags)
217 {
218   struct hdr_format_info *hfi = data.ptr;
219   HEADER *hdr, *htmp;
220   CONTEXT *ctx;
221   char fmt[STRING], buf2[STRING], ch, *p;
222   int do_locales, i, c;
223   int optional = (flags & M_FORMAT_OPTIONAL);
224   int threads = ((Sort & SORT_MASK) == SORT_THREADS);
225   int is_index = (flags & M_FORMAT_INDEX);
226
227 #define THREAD_NEW (threads && hdr->collapsed && hdr->num_hidden > 1 && mutt_thread_contains_unread (ctx, hdr) == 1)
228 #define THREAD_OLD (threads && hdr->collapsed && hdr->num_hidden > 1 && mutt_thread_contains_unread (ctx, hdr) == 2)
229   ssize_t len;
230
231   hdr = hfi->hdr;
232   ctx = hfi->ctx;
233
234   dest[0] = 0;
235   switch (op) {
236   case 'A':
237     if (hdr->env->reply_to && hdr->env->reply_to->mailbox) {
238       mutt_format_s (dest, destlen, prefix,
239                      mutt_addr_for_display (hdr->env->reply_to));
240       break;
241     }
242     /* fall through if 'A' returns nothing */
243
244   case 'a':
245     if (hdr->env->from && hdr->env->from->mailbox) {
246       mutt_format_s (dest, destlen, prefix,
247                      mutt_addr_for_display (hdr->env->from));
248     }
249     else
250       dest[0] = '\0';
251     break;
252
253   case 'B':
254     if (!first_mailing_list (dest, destlen, hdr->env->to) &&
255         !first_mailing_list (dest, destlen, hdr->env->cc))
256       dest[0] = 0;
257     if (dest[0]) {
258       m_strcpy(buf2, sizeof(buf2), dest);
259       mutt_format_s (dest, destlen, prefix, buf2);
260       break;
261     }
262     /* fall through if 'B' returns nothing */
263
264   case 'b':
265     if (ctx) {
266       if ((p = strrchr (ctx->path, '/')))
267         m_strcpy(dest, destlen, p + 1);
268       else
269         m_strcpy(dest, destlen, ctx->path);
270     }
271     else
272       m_strcpy(dest, destlen, "(null)");
273     m_strcpy(buf2, sizeof(buf2), dest);
274     mutt_format_s (dest, destlen, prefix, buf2);
275     break;
276
277   case 'c':
278     mutt_pretty_size (buf2, sizeof (buf2), (long) hdr->content->length);
279     mutt_format_s (dest, destlen, prefix, buf2);
280     break;
281
282   case 'C':
283     snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
284     snprintf (dest, destlen, fmt, hdr->msgno + 1);
285     break;
286
287   case 'd':
288   case 'D':
289   case '{':
290   case '[':
291   case '(':
292   case '<':
293
294     /* preprocess $date_format to handle %Z */
295     {
296       const char *cp;
297       struct tm *tm;
298       time_t T;
299
300       p = dest;
301
302       cp = (op == 'd' || op == 'D') ? (NONULL (DateFmt)) : src;
303       if (*cp == '!') {
304         do_locales = 0;
305         cp++;
306       }
307       else
308         do_locales = 1;
309
310       len = destlen - 1;
311       while (len > 0 && (((op == 'd' || op == 'D') && *cp) ||
312                          (op == '{' && *cp != '}') ||
313                          (op == '[' && *cp != ']') ||
314                          (op == '(' && *cp != ')') ||
315                          (op == '<' && *cp != '>'))) {
316         if (*cp == '%') {
317           cp++;
318           if ((*cp == 'Z' || *cp == 'z') && (op == 'd' || op == '{')) {
319             if (len >= 5) {
320               sprintf (p, "%c%02u%02u", hdr->zoccident ? '-' : '+',
321                        hdr->zhours, hdr->zminutes);
322               p += 5;
323               len -= 5;
324             }
325             else
326               break;            /* not enough space left */
327           }
328           else {
329             if (len >= 2) {
330               *p++ = '%';
331               *p++ = *cp;
332               len -= 2;
333             }
334             else
335               break;            /* not enough space */
336           }
337           cp++;
338         }
339         else {
340           *p++ = *cp++;
341           len--;
342         }
343       }
344       *p = 0;
345
346       if (do_locales && Locale)
347         setlocale (LC_TIME, Locale);
348
349       if (op == '[' || op == 'D')
350         tm = localtime (&hdr->date_sent);
351       else if (op == '(')
352         tm = localtime (&hdr->received);
353       else if (op == '<') {
354         T = time (NULL);
355         tm = localtime (&T);
356       }
357       else {
358         /* restore sender's time zone */
359         T = hdr->date_sent;
360         if (hdr->zoccident)
361           T -= (hdr->zhours * 3600 + hdr->zminutes * 60);
362         else
363           T += (hdr->zhours * 3600 + hdr->zminutes * 60);
364         tm = gmtime (&T);
365       }
366
367       strftime (buf2, sizeof (buf2), dest, tm);
368
369       if (do_locales)
370         setlocale (LC_TIME, "C");
371
372       mutt_format_s (dest, destlen, prefix, buf2);
373       if (len > 0 && op != 'd' && op != 'D')    /* Skip ending op */
374         src = cp + 1;
375     }
376     break;
377
378   case 'e':
379     snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
380     snprintf (dest, destlen, fmt, mutt_messages_in_thread (ctx, hdr, 1));
381     break;
382
383   case 'E':
384     if (!optional) {
385       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
386       snprintf (dest, destlen, fmt, mutt_messages_in_thread (ctx, hdr, 0));
387     }
388     else if (mutt_messages_in_thread (ctx, hdr, 0) <= 1)
389       optional = 0;
390     break;
391
392   case 'f':
393     buf2[0] = 0;
394     rfc822_addrcat(buf2, sizeof (buf2), hdr->env->from, 1);
395     mutt_format_s (dest, destlen, prefix, buf2);
396     break;
397
398   case 'F':
399     if (!optional) {
400       make_from (hdr->env, buf2, sizeof (buf2), 0);
401       mutt_format_s (dest, destlen, prefix, buf2);
402     }
403     else if (mutt_addr_is_user (hdr->env->from))
404       optional = 0;
405     break;
406
407   case 'H':
408     /* (Hormel) spam score */
409     if (optional)
410       optional = hdr->env->spam ? 1 : 0;
411
412     if (hdr->env->spam)
413       mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->spam->data));
414     else
415       mutt_format_s (dest, destlen, prefix, "");
416
417     break;
418
419   case 'i':
420     mutt_format_s (dest, destlen, prefix,
421                    hdr->env->message_id ? hdr->env->message_id : "<no.id>");
422     break;
423
424   case 'I':
425     {
426       int iflag = FALSE;
427       int j = 0;
428
429       for (i = 0; hdr->env->from && hdr->env->from->personal &&
430            hdr->env->from->personal[i] && j < STRING - 1; i++) {
431         if (isalpha ((int) hdr->env->from->personal[i])) {
432           if (!iflag) {
433             buf2[j++] = hdr->env->from->personal[i];
434             iflag = TRUE;
435           }
436         }
437         else
438           iflag = FALSE;
439       }
440
441       buf2[j] = '\0';
442     }
443     mutt_format_s (dest, destlen, prefix, buf2);
444     break;
445
446   case 'l':
447     if (!optional) {
448       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
449       snprintf (dest, destlen, fmt, (int) hdr->lines);
450     }
451     else if (hdr->lines <= 0)
452       optional = 0;
453     break;
454
455   case 'L':
456     if (!optional) {
457       make_from (hdr->env, buf2, sizeof (buf2), 1);
458       mutt_format_s (dest, destlen, prefix, buf2);
459     }
460     else if (!check_for_mailing_list (hdr->env->to, NULL, NULL, 0) &&
461              !check_for_mailing_list (hdr->env->cc, NULL, NULL, 0)) {
462       optional = 0;
463     }
464     break;
465
466   case 'm':
467     if (ctx) {
468       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
469       snprintf (dest, destlen, fmt, ctx->msgcount);
470     }
471     else
472       m_strcpy(dest, destlen, "(null)");
473     break;
474
475   case 'n':
476     mutt_format_s (dest, destlen, prefix, mutt_get_name (hdr->env->from));
477     break;
478
479   case 'N':
480     if (!optional) {
481       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
482       snprintf (dest, destlen, fmt, hdr->score);
483     }
484     else {
485       if (hdr->score == 0)
486         optional = 0;
487     }
488     break;
489
490   case 'O':
491     if (!optional) {
492       make_from_addr (hdr->env, buf2, sizeof (buf2), 1);
493       if ((p = strpbrk (buf2, "%@")))
494         *p = 0;
495       mutt_format_s (dest, destlen, prefix, buf2);
496     }
497     else if (!check_for_mailing_list_addr (hdr->env->to, NULL, 0) &&
498              !check_for_mailing_list_addr (hdr->env->cc, NULL, 0)) {
499       optional = 0;
500     }
501     break;
502
503   case 'M':
504     snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
505     if (!optional) {
506       if (threads && is_index && hdr->collapsed && hdr->num_hidden > 1)
507         snprintf (dest, destlen, fmt, hdr->num_hidden);
508       else if (is_index && threads)
509         mutt_format_s (dest, destlen, prefix, " ");
510       else
511         *dest = '\0';
512     }
513     else {
514       if (!(threads && is_index && hdr->collapsed && hdr->num_hidden > 1))
515         optional = 0;
516     }
517     break;
518
519   case 's':
520
521     if (flags & M_FORMAT_TREE && !hdr->collapsed) {
522       if (flags & M_FORMAT_FORCESUBJ) {
523         mutt_format_s (dest, destlen, "", NONULL (hdr->env->subject));
524         snprintf (buf2, sizeof (buf2), "%s%s", hdr->tree, dest);
525         mutt_format_s_tree (dest, destlen, prefix, buf2);
526       }
527       else
528         mutt_format_s_tree (dest, destlen, prefix, hdr->tree);
529     }
530     else
531       mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->subject));
532     break;
533
534   case 'S':
535     if (hdr->deleted)
536       ch = 'D';
537     else if (hdr->attach_del)
538       ch = 'd';
539     else if (hdr->tagged)
540       ch = '*';
541     else if (hdr->flagged)
542       ch = '!';
543     else if (hdr->replied)
544       ch = 'r';
545     else if (hdr->read && (ctx && ctx->msgnotreadyet != hdr->msgno))
546       ch = '-';
547     else if (hdr->old)
548       ch = 'O';
549     else
550       ch = 'N';
551
552     /* FOO - this is probably unsafe, but we are not likely to have such
553        a short string passed into this routine */
554     *dest = ch;
555     *(dest + 1) = 0;
556     break;
557
558   case 't':
559     buf2[0] = 0;
560     if (!check_for_mailing_list (hdr->env->to, "To ", buf2, sizeof (buf2)) &&
561         !check_for_mailing_list (hdr->env->cc, "Cc ", buf2, sizeof (buf2))) {
562       if (hdr->env->to)
563         snprintf (buf2, sizeof (buf2), "To %s", mutt_get_name (hdr->env->to));
564       else if (hdr->env->cc)
565         snprintf (buf2, sizeof (buf2), "Cc %s", mutt_get_name (hdr->env->cc));
566     }
567     mutt_format_s (dest, destlen, prefix, buf2);
568     break;
569
570   case 'T':
571     i = mutt_user_is_recipient(hdr);
572     assert (i >= 0); /* help compiler to see c is initialized */
573
574     if (Charset_is_utf8) {
575         const char *s = Tochars;
576
577         snprintf (fmt, sizeof (fmt), "%%%slc", prefix);
578         while (i-- >= 0) {
579             c = m_ustrgetc(s, &s);
580             if (c <= 0) {
581                 c = ' ';
582                 break;
583             }
584         }
585
586         snprintf(dest, destlen, fmt, c);
587     } else {
588         snprintf(fmt, sizeof (fmt), "%%%sc", prefix);
589         snprintf(dest, destlen, fmt, i < m_strlen(Tochars) ? Tochars[i] : ' ');
590     }
591     break;
592
593   case 'u':
594     if (hdr->env->from && hdr->env->from->mailbox) {
595       m_strcpy(buf2, sizeof(buf2), mutt_addr_for_display(hdr->env->from));
596       if ((p = strpbrk (buf2, "%@")))
597         *p = 0;
598     }
599     else
600       buf2[0] = 0;
601     mutt_format_s (dest, destlen, prefix, buf2);
602     break;
603
604   case 'v':
605     if (mutt_addr_is_user (hdr->env->from)) {
606       if (hdr->env->to)
607         mutt_format_s (buf2, sizeof (buf2), prefix,
608                        mutt_get_name (hdr->env->to));
609       else if (hdr->env->cc)
610         mutt_format_s (buf2, sizeof (buf2), prefix,
611                        mutt_get_name (hdr->env->cc));
612       else
613         *buf2 = 0;
614     }
615     else
616       mutt_format_s (buf2, sizeof (buf2), prefix,
617                      mutt_get_name (hdr->env->from));
618     if ((p = strpbrk (buf2, " %@")))
619       *p = 0;
620     mutt_format_s (dest, destlen, prefix, buf2);
621     break;
622
623   case 'W':
624     if (!optional)
625       mutt_format_s (dest, destlen, prefix,
626                      hdr->env->organization ? hdr->env->organization : "");
627     else if (!hdr->env->organization)
628       optional = 0;
629     break;
630
631   case 'X':
632     {
633       int count = 0;
634
635       /* The recursion allows messages without depth to return 0. */
636       if (optional)
637         optional = count != 0;
638
639       snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
640       snprintf (dest, destlen, fmt, count);
641     }
642     break;
643
644   case 'Z':
645
646     ch = ' ';
647
648     if (hdr->security & GOODSIGN)
649       ch = 'S';
650     else if (hdr->security & ENCRYPT)
651       ch = 'P';
652     else if (hdr->security & SIGN)
653       ch = 's';
654     else if (hdr->security & PGPKEY)
655       ch = 'K';
656
657     if (hdr->tagged) {
658         c = '*';
659     } else
660     if (hdr->flagged) {
661         c = '!';
662     } else {
663         i = mutt_user_is_recipient(hdr);
664         assert (i >= 0); /* help compiler to see c is initialized */
665
666         if (Charset_is_utf8) {
667             const char *s = Tochars;
668
669             snprintf (fmt, sizeof (fmt), "%%%slc", prefix);
670             while (i-- >= 0) {
671                 c = m_ustrgetc(s, &s);
672                 if (c <= 0) {
673                     c = ' ';
674                     break;
675                 }
676             }
677         } else {
678             c = i < m_strlen(Tochars) ? Tochars[i] : ' ';
679         }
680     }
681     snprintf(buf2, sizeof (buf2), Charset_is_utf8 ? "%c%c%lc" : "%c%c%c",
682              (THREAD_NEW ? 'n'
683               : (THREAD_OLD
684                  ?  'o'
685                  : ((hdr->read && (ctx && ctx->msgnotreadyet != hdr->msgno))
686                     ? (hdr->replied ? 'r' : ' ')
687                     : (hdr->old ?  'O' : 'N')))),
688               hdr->deleted ? 'D' : (hdr->attach_del ? 'd' : ch), c);
689     mutt_format_s (dest, destlen, prefix, buf2);
690     break;
691
692   case 'y':
693     if (optional)
694       optional = hdr->env->x_label ? 1 : 0;
695
696     mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->x_label));
697     break;
698
699   case 'Y':
700     if (hdr->env->x_label) {
701       i = 1;                    /* reduce reuse recycle */
702       htmp = NULL;
703       if (flags & M_FORMAT_TREE
704           && (hdr->thread->prev && hdr->thread->prev->message
705               && hdr->thread->prev->message->env->x_label))
706         htmp = hdr->thread->prev->message;
707       else if (flags & M_FORMAT_TREE
708                && (hdr->thread->parent && hdr->thread->parent->message
709                    && hdr->thread->parent->message->env->x_label))
710         htmp = hdr->thread->parent->message;
711       if (htmp && m_strcasecmp(hdr->env->x_label,
712                                    htmp->env->x_label) == 0)
713         i = 0;
714     }
715     else
716       i = 0;
717
718     if (optional)
719       optional = i;
720
721     if (i)
722       mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->x_label));
723     else
724       mutt_format_s (dest, destlen, prefix, "");
725
726     break;
727
728   default:
729     *dest = 0;
730     break;
731   }
732
733   if (flags & M_FORMAT_OPTIONAL)
734     m_strformat(dest, destlen, 0, optional ? ifstr: elstr,
735                 hdr_format_str, data, flags);
736
737   return src;
738 #undef THREAD_NEW
739 #undef THREAD_OLD
740 }
741
742 void
743 _mutt_make_string (char *dest, ssize_t destlen, const char *s, CONTEXT * ctx,
744                    HEADER * hdr, format_flag flags)
745 {
746   struct hdr_format_info hfi;
747
748   hfi.hdr = hdr;
749   hfi.ctx = ctx;
750
751   m_strformat(dest, destlen, getmaxx(main_w), s, hdr_format_str, &hfi, flags);
752 }