Fixes NB#122195, modest becames inaccessible with specially malformed email with...
[modest] / src / widgets / modest-header-view-render.c
1 /* Copyright (c) 2006, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include <tny-simple-list.h>
31 #include <modest-header-view.h>
32 #include <modest-header-view-priv.h>
33 #include <modest-defs.h>
34 #include <modest-icon-names.h>
35 #include <modest-text-utils.h>
36 #include <modest-tny-account-store.h>
37 #include <modest-tny-send-queue.h>
38 #include <modest-tny-folder.h>
39 #include <modest-tny-account.h>
40 #include <modest-runtime.h>
41 #include <glib/gi18n.h>
42 #include <modest-platform.h>
43 #include <string.h>
44
45 #ifdef MODEST_TOOLKIT_HILDON2
46 #define SMALL_ICON_SIZE MODEST_ICON_SIZE_SMALL
47 #else
48 #define SMALL_ICON_SIZE MODEST_ICON_SIZE_SMALL
49 #endif
50
51 #define MODEST_HEADER_VIEW_MAX_TEXT_LENGTH 128
52
53 static const gchar *
54 get_status_string (ModestTnySendQueueStatus status)
55 {
56         switch (status) {
57         case MODEST_TNY_SEND_QUEUE_WAITING:
58                 return _("mcen_li_outbox_waiting");
59                 break;
60         case MODEST_TNY_SEND_QUEUE_SENDING:
61                 return _("mcen_li_outbox_sending");
62                 break;
63         case MODEST_TNY_SEND_QUEUE_SUSPENDED:
64                 return _("mcen_li_outbox_suspended");
65                 break;
66         case MODEST_TNY_SEND_QUEUE_FAILED:
67                 return _("mcen_li_outbox_failed");
68                 break;
69         default:
70                 return "";
71                 break;
72         }
73 }
74
75 static GdkPixbuf*
76 get_pixbuf_for_flag (TnyHeaderFlags flag)
77 {
78         /* optimization */
79         static GdkPixbuf *deleted_pixbuf          = NULL;
80         static GdkPixbuf *seen_pixbuf             = NULL;
81         static GdkPixbuf *unread_pixbuf           = NULL;
82         static GdkPixbuf *attachments_pixbuf      = NULL;
83         static GdkPixbuf *high_pixbuf             = NULL;
84         static GdkPixbuf *low_pixbuf             = NULL;
85         
86         switch (flag) {
87         case TNY_HEADER_FLAG_DELETED:
88                 if (G_UNLIKELY(!deleted_pixbuf))
89                         deleted_pixbuf = modest_platform_get_icon (MODEST_HEADER_ICON_DELETED,
90                                                                    SMALL_ICON_SIZE);
91                 return deleted_pixbuf;
92         case TNY_HEADER_FLAG_SEEN:
93                 if (G_UNLIKELY(!seen_pixbuf))
94                         seen_pixbuf = modest_platform_get_icon (MODEST_HEADER_ICON_READ,
95                                                                 SMALL_ICON_SIZE);
96                 return seen_pixbuf;
97         case TNY_HEADER_FLAG_ATTACHMENTS:
98                 if (G_UNLIKELY(!attachments_pixbuf))
99                         attachments_pixbuf = modest_platform_get_icon (MODEST_HEADER_ICON_ATTACH,
100                                                                        SMALL_ICON_SIZE);
101                 return attachments_pixbuf;
102         case TNY_HEADER_FLAG_HIGH_PRIORITY:
103                 if (G_UNLIKELY(!high_pixbuf))
104                         high_pixbuf = modest_platform_get_icon (MODEST_HEADER_ICON_HIGH,
105                                                                 SMALL_ICON_SIZE);
106                 return high_pixbuf;
107         case TNY_HEADER_FLAG_LOW_PRIORITY:
108                 if (G_UNLIKELY(!low_pixbuf))
109                         low_pixbuf = modest_platform_get_icon (MODEST_HEADER_ICON_LOW,
110                                                                SMALL_ICON_SIZE);
111                 return low_pixbuf;
112         case TNY_HEADER_FLAG_NORMAL_PRIORITY:
113                 return NULL;
114         default:
115                 if (G_UNLIKELY(!unread_pixbuf))
116                         unread_pixbuf = modest_platform_get_icon (MODEST_HEADER_ICON_UNREAD,
117                                                                   SMALL_ICON_SIZE);
118                 return unread_pixbuf;
119         }
120 }
121
122 static void
123 set_common_flags (GtkCellRenderer *renderer, TnyHeaderFlags flags)
124 {
125         g_object_set (G_OBJECT(renderer),
126                       "weight", (flags & TNY_HEADER_FLAG_SEEN) ? PANGO_WEIGHT_NORMAL: PANGO_WEIGHT_ULTRABOLD,
127                       "strikethrough",  (flags & TNY_HEADER_FLAG_DELETED) ?  TRUE:FALSE,
128                       NULL);    
129 }
130
131 static void
132 set_cell_text (GtkCellRenderer *renderer,
133                const gchar *text,
134                TnyHeaderFlags flags)
135 {
136         gboolean strikethrough;
137         gboolean bold_is_active_color;
138         GdkColor *color = NULL;
139         PangoWeight weight;
140         gchar *newtext = NULL;
141
142         /* We have to limit the size of the text. Otherwise Pango
143            could cause freezes trying to render too large texts. This
144            prevents DoS attacks with specially malformed emails */
145         if (g_utf8_validate(text, -1, NULL)) {
146                 if (g_utf8_strlen (text, -1) > MODEST_HEADER_VIEW_MAX_TEXT_LENGTH) {
147                         /* UTF-8 bytes are 4 bytes length in the worst case */
148                         newtext = g_malloc0 (MODEST_HEADER_VIEW_MAX_TEXT_LENGTH * 4);
149                         g_utf8_strncpy (newtext, text, MODEST_HEADER_VIEW_MAX_TEXT_LENGTH);
150                         text = newtext;
151                 }
152         } else {
153                 if (strlen (text) > MODEST_HEADER_VIEW_MAX_TEXT_LENGTH) {
154                         newtext = g_malloc0 (MODEST_HEADER_VIEW_MAX_TEXT_LENGTH);
155                         strncpy (newtext, text, MODEST_HEADER_VIEW_MAX_TEXT_LENGTH);
156                         text = newtext;
157                 }
158         }
159
160         bold_is_active_color = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (renderer), BOLD_IS_ACTIVE_COLOR));
161         if (bold_is_active_color) {
162                 color = g_object_get_data (G_OBJECT (renderer), ACTIVE_COLOR);
163         }
164
165 #ifdef MODEST_TOOLKIT_HILDON2
166         weight =  PANGO_WEIGHT_NORMAL;
167 #else
168         weight =  (bold_is_active_color || (flags & TNY_HEADER_FLAG_SEEN)) ? PANGO_WEIGHT_NORMAL: PANGO_WEIGHT_ULTRABOLD;
169 #endif
170         strikethrough = (flags & TNY_HEADER_FLAG_DELETED) ?  TRUE:FALSE;
171         g_object_freeze_notify (G_OBJECT (renderer));
172         g_object_set (G_OBJECT (renderer), 
173                       "text", text, 
174                       "weight", weight,
175                       "strikethrough", (flags &TNY_HEADER_FLAG_DELETED) ? TRUE : FALSE,
176                       NULL);
177         if (bold_is_active_color && color) {
178                 if (flags & TNY_HEADER_FLAG_SEEN) {
179                         g_object_set (G_OBJECT (renderer),
180                                       "foreground-set", FALSE,
181                                       NULL);
182                 } else {
183                         g_object_set (G_OBJECT (renderer),
184                                       "foreground-gdk", color,
185                                       "foreground-set", TRUE,
186                                       NULL);
187                 }
188         }
189
190         if (newtext)
191                 g_free (newtext);
192
193         g_object_thaw_notify (G_OBJECT (renderer));
194 }
195
196 void
197 _modest_header_view_attach_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
198                                       GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data)
199 {
200         TnyHeaderFlags flags;
201
202         gtk_tree_model_get (tree_model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
203                             &flags, -1);
204
205         if (flags & TNY_HEADER_FLAG_ATTACHMENTS)
206                 g_object_set (G_OBJECT (renderer), "pixbuf",
207                               get_pixbuf_for_flag (TNY_HEADER_FLAG_ATTACHMENTS),
208                               NULL);
209 }
210
211 void
212 _modest_header_view_header_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
213                   GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer user_data)
214 {
215         TnyHeaderFlags flags;
216         
217         gtk_tree_model_get (tree_model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
218                             &flags, -1);
219         set_common_flags (renderer, flags);
220 }
221
222
223 void
224 _modest_header_view_date_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
225                                      GtkTreeModel *tree_model,  GtkTreeIter *iter,
226                                      gpointer user_data)
227 {
228         TnyHeaderFlags flags;
229         guint date, date_col;
230         gboolean received = GPOINTER_TO_INT(user_data);
231
232         if (received)
233                 date_col = TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN;
234         else
235                 date_col = TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN;
236
237         gtk_tree_model_get (tree_model, iter,
238                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
239                             date_col, &date,
240                             -1);
241
242 #if GTK_CHECK_VERSION (2, 12, 0)
243         ModestHeaderView *header_view;
244         header_view = MODEST_HEADER_VIEW (gtk_tree_view_column_get_tree_view (column));
245         set_cell_text (renderer,
246                        _modest_header_view_get_display_date (header_view, date),
247                        flags);
248 #else
249         set_cell_text (renderer, modest_text_utils_get_display_date (date),
250                        flags);
251 #endif
252 }
253
254 void
255 _modest_header_view_sender_receiver_cell_data  (GtkTreeViewColumn *column,  
256                                                 GtkCellRenderer *renderer,
257                                                 GtkTreeModel *tree_model,  
258                                                 GtkTreeIter *iter,  
259                                                 gboolean is_sender)
260 {
261         TnyHeaderFlags flags;
262         gchar *address;
263         gint sender_receiver_col;
264
265         if (is_sender)
266                 sender_receiver_col = TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN;
267         else
268                 sender_receiver_col = TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN;
269
270         gtk_tree_model_get (tree_model, iter,
271                             sender_receiver_col,  &address,
272                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
273                             -1);
274
275         modest_text_utils_get_display_address (address); /* string is changed in-place */
276
277         set_cell_text (renderer, (address && address[0] != '\0')?address:_("mail_va_no_to"),
278                        flags);
279         g_free (address);
280 }
281 /*
282  * this for both incoming and outgoing mail, depending on the the user_data
283  * parameter. in the incoming case, show 'From' and 'Received date', in the
284  * outgoing case, show 'To' and 'Sent date'
285  */
286 void
287 _modest_header_view_compact_header_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
288                                                GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer user_data)
289 {
290         TnyHeaderFlags flags = 0;
291         gchar *recipients = NULL, *addresses;
292         gchar *subject = NULL;
293         time_t date;
294         GtkCellRenderer *recipient_cell, *date_or_status_cell, *subject_cell,
295                 *attach_cell, *priority_cell,
296                 *recipient_box, *subject_box = NULL;
297         TnyHeader *msg_header = NULL;
298         TnyHeaderFlags prio = 0;
299
300 #ifdef MAEMO_CHANGES
301 #ifdef HAVE_GTK_TREE_VIEW_COLUMN_GET_CELL_DATA_HINT
302         GtkTreeCellDataHint hint;
303 #endif
304 #endif
305
306         g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (column));
307         g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
308         g_return_if_fail (GTK_IS_TREE_MODEL (tree_model));
309
310 #ifdef MAEMO_CHANGES
311 #ifdef HAVE_GTK_TREE_VIEW_COLUMN_GET_CELL_DATA_HINT
312         hint = gtk_tree_view_column_get_cell_data_hint (GTK_TREE_VIEW_COLUMN (column));
313
314         if (hint != GTK_TREE_CELL_DATA_HINT_ALL)
315                 return;
316 #endif
317 #endif
318
319         recipient_box = GTK_CELL_RENDERER (g_object_get_data (G_OBJECT (renderer), "recpt-box-renderer"));
320         subject_box = GTK_CELL_RENDERER (g_object_get_data (G_OBJECT (renderer), "subject-box-renderer"));
321         priority_cell = GTK_CELL_RENDERER (g_object_get_data (G_OBJECT (subject_box), "priority-renderer"));
322         subject_cell = GTK_CELL_RENDERER (g_object_get_data (G_OBJECT (subject_box), "subject-renderer"));
323         attach_cell = GTK_CELL_RENDERER (g_object_get_data (G_OBJECT (recipient_box), "attach-renderer"));
324         recipient_cell = GTK_CELL_RENDERER (g_object_get_data (G_OBJECT (recipient_box), "recipient-renderer"));
325         date_or_status_cell = GTK_CELL_RENDERER (g_object_get_data (G_OBJECT (recipient_box), "date-renderer"));
326
327         ModestHeaderViewCompactHeaderMode header_mode = GPOINTER_TO_INT (user_data); 
328
329         if (header_mode == MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN)
330                 gtk_tree_model_get (tree_model, iter,
331                                     TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
332                                     TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,  &recipients,
333                                     TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &subject,
334                                     TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN, &date,
335                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &msg_header,
336                                     -1);
337         else
338                 gtk_tree_model_get (tree_model, iter,
339                                     TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
340                                     TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,  &recipients,
341                                     TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &subject,
342                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &date,
343                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &msg_header,
344                                     -1);        
345         /* flags */
346         /* FIXME: we might gain something by doing all the g_object_set's at once */
347         if (flags & TNY_HEADER_FLAG_ATTACHMENTS)
348                 g_object_set (G_OBJECT (attach_cell), "pixbuf",
349                               get_pixbuf_for_flag (TNY_HEADER_FLAG_ATTACHMENTS),
350                               NULL);
351         else
352                 g_object_set (G_OBJECT (attach_cell), "pixbuf",
353                               NULL, NULL);
354
355         if (msg_header)
356                 prio = tny_header_get_priority (msg_header);
357         g_object_set (G_OBJECT (priority_cell), "pixbuf",
358                       get_pixbuf_for_flag (prio), 
359                       NULL);
360
361         set_cell_text (subject_cell, (subject && subject[0] != 0)?subject:_("mail_va_no_subject"), 
362                        flags);
363         g_free (subject);
364
365         /* Show the list of senders/recipients */
366         addresses = modest_text_utils_get_display_addresses ((const gchar *) recipients);
367         set_cell_text (recipient_cell, (addresses) ? addresses : _("mail_va_no_to"), flags);
368         g_free (addresses);
369         g_free (recipients);
370
371         /* Show status (outbox folder) or sent date */
372         if (header_mode == MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX) {
373                 ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
374                 const gchar *status_str = "";
375                 if (msg_header != NULL) {
376                         status = modest_tny_all_send_queues_get_msg_status (msg_header);
377                         if (status == MODEST_TNY_SEND_QUEUE_SUSPENDED) {
378                                 tny_header_set_flag (msg_header, TNY_HEADER_FLAG_SUSPENDED);
379                         }
380                 }
381
382                 status_str = get_status_string (status);
383                 set_cell_text (date_or_status_cell, status_str, flags);
384         } else {
385 #if GTK_CHECK_VERSION (2, 12, 0)
386                 ModestHeaderView *header_view;
387                 header_view = MODEST_HEADER_VIEW (gtk_tree_view_column_get_tree_view (column));
388                 set_cell_text (date_or_status_cell, 
389                                date ? _modest_header_view_get_display_date (header_view, date) : "",
390                                flags);
391 #else
392                 set_cell_text (date_or_status_cell, 
393                                date ? modest_text_utils_get_display_date (date) : "",
394                                flags);
395 #endif
396         }
397         if (msg_header != NULL)
398                 g_object_unref (msg_header);
399 }
400
401
402 void
403 _modest_header_view_size_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
404                                      GtkTreeModel *tree_model,  GtkTreeIter *iter,
405                                      gpointer user_data)
406 {
407         TnyHeaderFlags flags;
408         guint size;
409         gchar *size_str;
410         
411         gtk_tree_model_get (tree_model, iter,
412                            TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
413                            TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN, &size,
414                             -1);
415         
416         size_str = modest_text_utils_get_display_size (size);
417         
418         set_cell_text (renderer, size_str, flags);
419
420         g_free (size_str);
421  }
422
423 void
424 _modest_header_view_status_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
425                                        GtkTreeModel *tree_model,  GtkTreeIter *iter,
426                                        gpointer user_data)
427 {
428         TnyHeaderFlags flags;
429         //guint status;
430         gchar *status_str;
431         
432         gtk_tree_model_get (tree_model, iter,
433                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
434                             -1);
435
436        if (flags & TNY_HEADER_FLAG_SUSPENDED)
437                status_str = g_strdup(_("mcen_li_outbox_suspended"));
438        else            
439                status_str = g_strdup(_("mcen_li_outbox_waiting"));
440        
441         set_cell_text (renderer, status_str, flags);
442
443         g_free (status_str);
444  }
445