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