fix CID 1596595: Resource leaks, and CID 1596594: (CHECKED_RETURN)
[claws.git] / src / plugins / python / messageinfotype.c
1 /* Python plugin for Claws Mail
2  * Copyright (C) 2009-2014 Holger Berndt
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 3 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, see <http://www.gnu.org/licenses/>.
16  */
17
18 #ifdef HAVE_CONFIG_H
19 #  include "config.h"
20 #include "claws-features.h"
21 #endif
22
23 #include "messageinfotype.h"
24 #include "foldertype.h"
25
26 #include "common/tags.h"
27 #include "common/defs.h"
28 #include "mainwindow.h"
29 #include "summaryview.h"
30 #include "procheader.h"
31
32 #include <glib/gi18n.h>
33
34 #include <structmember.h>
35
36 #include <string.h>
37
38 typedef struct {
39     PyObject_HEAD
40     MsgInfo *msginfo;
41 } clawsmail_MessageInfoObject;
42
43
44 static void MessageInfo_dealloc(clawsmail_MessageInfoObject* self)
45 {
46   Py_TYPE(self)->tp_free((PyObject*)self);
47 }
48
49 static int MessageInfo_init(clawsmail_MessageInfoObject *self, PyObject *args, PyObject *kwds)
50 {
51   return 0;
52 }
53
54 static PyObject* MessageInfo_str(clawsmail_MessageInfoObject *self)
55 {
56   if(self->msginfo) {
57     gchar *From;
58     gchar *Subject;
59     From = self->msginfo->from ? self->msginfo->from : "";
60     Subject = self->msginfo->subject ? self->msginfo->subject : "";
61     return PyUnicode_FromFormat("MessageInfo: %s / %s", From, Subject);
62   }
63   Py_RETURN_NONE;
64 }
65
66 static PyObject *py_boolean_return_value(gboolean val)
67 {
68   if(val) {
69     Py_INCREF(Py_True);
70     return Py_True;
71   }
72   else {
73     Py_INCREF(Py_False);
74     return Py_False;
75   }
76 }
77
78 static PyObject *is_new(PyObject *self, PyObject *args)
79 {
80   return py_boolean_return_value(MSG_IS_NEW(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
81 }
82
83 static PyObject *is_unread(PyObject *self, PyObject *args)
84 {
85   return py_boolean_return_value(MSG_IS_UNREAD(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
86 }
87
88 static PyObject *is_marked(PyObject *self, PyObject *args)
89 {
90   return py_boolean_return_value(MSG_IS_MARKED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
91 }
92
93 static PyObject *is_replied(PyObject *self, PyObject *args)
94 {
95   return py_boolean_return_value(MSG_IS_REPLIED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
96 }
97
98 static PyObject *is_locked(PyObject *self, PyObject *args)
99 {
100   return py_boolean_return_value(MSG_IS_LOCKED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
101 }
102
103 static PyObject *is_forwarded(PyObject *self, PyObject *args)
104 {
105   return py_boolean_return_value(MSG_IS_FORWARDED(((clawsmail_MessageInfoObject*)self)->msginfo->flags));
106 }
107
108 static PyObject* get_tags(PyObject *self, PyObject *args)
109 {
110   GSList *tags_list;
111   Py_ssize_t num_tags;
112   PyObject *tags_tuple;
113
114   tags_list = ((clawsmail_MessageInfoObject*)self)->msginfo->tags;
115   num_tags = g_slist_length(tags_list);
116
117   tags_tuple = PyTuple_New(num_tags);
118   if(tags_tuple != NULL) {
119     Py_ssize_t iTag;
120     PyObject *tag_object;
121     GSList *walk;
122
123     iTag = 0;
124     for(walk = tags_list; walk; walk = walk->next) {
125       tag_object = Py_BuildValue("s", tags_get_tag(GPOINTER_TO_INT(walk->data)));
126       if(tag_object == NULL) {
127         Py_DECREF(tags_tuple);
128         return NULL;
129       }
130       PyTuple_SET_ITEM(tags_tuple, iTag++, tag_object);
131     }
132   }
133
134   return tags_tuple;
135 }
136
137
138 static PyObject* add_or_remove_tag(PyObject *self, PyObject *args, gboolean add)
139 {
140   int retval;
141   const char *tag_str;
142   gint tag_id;
143   MsgInfo *msginfo;
144   MainWindow *mainwin;
145
146   retval = PyArg_ParseTuple(args, "s", &tag_str);
147   if(!retval)
148     return NULL;
149
150   tag_id = tags_get_id_for_str(tag_str);
151   if(tag_id == -1) {
152     PyErr_SetString(PyExc_ValueError, "Tag does not exist");
153     return NULL;
154   }
155
156   msginfo = ((clawsmail_MessageInfoObject*)self)->msginfo;
157
158   if(!add) {
159     /* raise KeyError if tag is not set */
160     if(!g_slist_find(msginfo->tags, GINT_TO_POINTER(tag_id))) {
161       PyErr_SetString(PyExc_KeyError, "Tag is not set on this message");
162       return NULL;
163     }
164   }
165
166   procmsg_msginfo_update_tags(msginfo, add, tag_id);
167
168   /* update display */
169   mainwin = mainwindow_get_mainwindow();
170   if(mainwin)
171     summary_redisplay_msg(mainwin->summaryview);
172
173   Py_RETURN_NONE;
174 }
175
176
177
178 static PyObject* add_tag(PyObject *self, PyObject *args)
179 {
180   return add_or_remove_tag(self, args, TRUE);
181 }
182
183
184 static PyObject* remove_tag(PyObject *self, PyObject *args)
185 {
186   return add_or_remove_tag(self, args, FALSE);
187 }
188
189 static PyObject* get_header(PyObject *self, PyObject *args)
190 {
191   int retval;
192   char *header_str;
193   char *header_str_dup;
194   MsgInfo *msginfo;
195   gchar *header_content = NULL;
196
197   retval = PyArg_ParseTuple(args, "s", &header_str);
198   if(!retval)
199     return NULL;
200
201   msginfo = ((clawsmail_MessageInfoObject*)self)->msginfo;
202
203   header_str_dup = g_strdup(header_str);
204   retval = procheader_get_header_from_msginfo(msginfo, &header_content, header_str);
205   g_free(header_str_dup);
206   if(retval == 0) {
207     PyObject *header_content_object;
208     gchar *content_start;
209
210     /* the string is now Header: Value. Strip the Header: part */
211     content_start = strstr(header_content, ":");
212     if(content_start == NULL)
213       content_start = header_content;
214     else
215       content_start++;
216     /* strip leading spaces */
217     while(*content_start == ' ')
218       content_start++;
219     header_content_object = Py_BuildValue("s", content_start);
220     g_free(header_content);
221     return header_content_object;
222   }
223   else {
224     g_free(header_content);
225     Py_RETURN_NONE;
226   }
227 }
228
229 static PyObject* get_From(clawsmail_MessageInfoObject *self, void *closure)
230 {
231   if(self->msginfo && self->msginfo->from)
232     return PyUnicode_FromString(self->msginfo->from);
233   Py_RETURN_NONE;
234 }
235
236 static PyObject* get_To(clawsmail_MessageInfoObject *self, void *closure)
237 {
238   if(self->msginfo && self->msginfo->to)
239     return PyUnicode_FromString(self->msginfo->to);
240   Py_RETURN_NONE;
241 }
242
243 static PyObject* get_Cc(clawsmail_MessageInfoObject *self, void *closure)
244 {
245   if(self->msginfo && self->msginfo->cc)
246     return PyUnicode_FromString(self->msginfo->cc);
247   Py_RETURN_NONE;
248 }
249
250 static PyObject* get_Subject(clawsmail_MessageInfoObject *self, void *closure)
251 {
252   if(self->msginfo && self->msginfo->subject)
253     return PyUnicode_FromString(self->msginfo->subject);
254   Py_RETURN_NONE;
255 }
256
257 static PyObject* get_MessageID(clawsmail_MessageInfoObject *self, void *closure)
258 {
259   if(self->msginfo && self->msginfo->msgid)
260     return PyUnicode_FromString(self->msginfo->msgid);
261   Py_RETURN_NONE;
262 }
263
264 static PyObject* get_FilePath(clawsmail_MessageInfoObject *self, void *closure)
265 {
266   if(self->msginfo) {
267     gchar *filepath;
268     filepath = procmsg_get_message_file_path(self->msginfo);
269     if(filepath) {
270       PyObject *retval;
271       retval = PyUnicode_FromString(filepath);
272       g_free(filepath);
273       return retval;
274     }
275   }
276   Py_RETURN_NONE;
277 }
278
279 static PyObject* get_flag(clawsmail_MessageInfoObject *self, void *closure)
280 {
281   if(self->msginfo) {
282     int flag = GPOINTER_TO_INT(closure);
283     return py_boolean_return_value(((self->msginfo->flags.perm_flags & flag) != 0));
284   }
285   Py_RETURN_NONE;
286 }
287
288 static int set_flag(clawsmail_MessageInfoObject *self, PyObject *value, void *closure)
289 {
290   int flag = GPOINTER_TO_INT(closure);
291
292   if(value == NULL) {
293     PyErr_SetString(PyExc_TypeError, "Cannot delete flag attribute");
294     return -1;
295   }
296
297   if(!self->msginfo) {
298     PyErr_SetString(PyExc_RuntimeError, "MessageInfo object broken");
299     return -1;
300   }
301
302   if(PyObject_IsTrue(value))
303     procmsg_msginfo_set_flags(self->msginfo, flag, 0);
304   else
305     procmsg_msginfo_unset_flags(self->msginfo, flag, 0);
306
307   return 0;
308 }
309
310
311 static PyObject* get_Folder(clawsmail_MessageInfoObject *self, void *closure)
312 {
313   if(self->msginfo && self->msginfo->folder) {
314     return clawsmail_folder_new(self->msginfo->folder);
315   }
316   Py_RETURN_NONE;
317 }
318
319
320 static PyMethodDef MessageInfo_methods[] = {
321   {"is_new",  is_new, METH_NOARGS,
322    "is_new() - checks if the message is new\n"
323    "\n"
324    "Returns True if the new flag of the message is set."
325    "\n\nThis function is deprecated in favor of the 'new' attribute."},
326
327   {"is_unread",  is_unread, METH_NOARGS,
328    "is_unread() - checks if the message is unread\n"
329    "\n"
330    "Returns True if the unread flag of the message is set."
331    "\n\nThis function is deprecated in favor of the 'unread' attribute."},
332
333   {"is_marked",  is_marked, METH_NOARGS,
334    "is_marked() - checks if the message is marked\n"
335    "\n"
336    "Returns True if the marked flag of the message is set."
337    "\n\nThis function is deprecated in favor of the 'marked' attribute."},
338
339   {"is_replied",  is_replied, METH_NOARGS,
340    "is_replied() - checks if the message has been replied to\n"
341    "\n"
342    "Returns True if the replied flag of the message is set."
343    "\n\nThis function is deprecated in favor of the 'replied' attribute."},
344
345   {"is_locked",  is_locked, METH_NOARGS,
346    "is_locked() - checks if the message has been locked\n"
347    "\n"
348    "Returns True if the locked flag of the message is set."
349    "\n\nThis function is deprecated in favor of the 'locked' attribute."},
350
351   {"is_forwarded",  is_forwarded, METH_NOARGS,
352    "is_forwarded() - checks if the message has been forwarded\n"
353    "\n"
354    "Returns True if the forwarded flag of the message is set."
355    "\n\nThis function is deprecated in favor of the 'forwarded' attribute."},
356
357   {"get_tags",  get_tags, METH_NOARGS,
358    "get_tags() - get message tags\n"
359    "\n"
360    "Returns a tuple of tags that apply to this message."},
361
362   {"add_tag",  add_tag, METH_VARARGS,
363    "add_tag(tag) - add a tag to this message\n"
364    "\n"
365    "Add a tag to this message. If the tag is already set, nothing is done.\n"
366    "If the tag does not exist, a ValueError exception is raised."},
367
368   {"remove_tag",  remove_tag, METH_VARARGS,
369    "remove_tag(tag) - remove a tag from this message\n"
370    "\n"
371    "Remove a tag from this message. If the tag is not set, a KeyError exception is raised.\n"
372    "If the tag does not exist, a ValueError exception is raised."},
373
374    {"get_header",  get_header, METH_VARARGS,
375     "get_header(name) - get a message header with a given name\n"
376     "\n"
377     "Get a message header content with a given name. If the header does not exist,\n"
378     "the value 'None' is returned. If multiple headers with the same name exist,\n"
379     "the first one is returned."},
380
381   {NULL}
382 };
383
384 static PyGetSetDef MessageInfo_getset[] = {
385     { "From", (getter)get_From, (setter)NULL,
386       "From - the From header of the message", NULL},
387
388     { "To", (getter)get_To, (setter)NULL,
389       "To - the To header of the message", NULL },
390
391     { "Cc", (getter)get_Cc, (setter)NULL,
392       "Cc - the Cc header of the message", NULL },
393
394     {"Subject", (getter)get_Subject, (setter)NULL,
395      "Subject - the subject header of the message", NULL},
396
397     {"MessageID", (getter)get_MessageID, (setter)NULL,
398      "MessageID - the Message-ID header of the message", NULL},
399
400     {"FilePath", (getter)get_FilePath, (setter)NULL,
401      "FilePath - path and filename of the message", NULL},
402
403     {"unread", (getter)get_flag, (setter)set_flag,
404      "unread - Unread-flag of the message", GINT_TO_POINTER(MSG_UNREAD)},
405
406     {"locked", (getter)get_flag, (setter)set_flag,
407      "locked - Locked-flag of the message", GINT_TO_POINTER(MSG_LOCKED)},
408
409     {"marked", (getter)get_flag, (setter)set_flag,
410      "marked - Marked-flag of the message", GINT_TO_POINTER(MSG_MARKED)},
411
412     {"new", (getter)get_flag, (setter)NULL,
413      "new - new-flag of the message", GINT_TO_POINTER(MSG_NEW)},
414
415     {"replied", (getter)get_flag, (setter)NULL,
416      "replied - Replied-flag of the message", GINT_TO_POINTER(MSG_REPLIED)},
417
418     {"forwarded", (getter)get_flag, (setter)NULL,
419      "forwarded - Forwarded-flag of the message", GINT_TO_POINTER(MSG_FORWARDED)},
420
421     {"Folder", (getter)get_Folder, (setter)NULL,
422      "Folder - Folder in which the message is contained", NULL},
423
424     {NULL}
425 };
426
427
428 static PyTypeObject clawsmail_MessageInfoType = {
429     PyVarObject_HEAD_INIT(NULL, 0)
430     "clawsmail.MessageInfo",   /* tp_name*/
431     sizeof(clawsmail_MessageInfoObject), /* tp_basicsize*/
432     0,                         /* tp_itemsize*/
433     (destructor)MessageInfo_dealloc, /* tp_dealloc*/
434     0,                         /* tp_print*/
435     0,                         /* tp_getattr*/
436     0,                         /* tp_setattr*/
437     0,                         /* tp_compare*/
438     0,                         /* tp_repr*/
439     0,                         /* tp_as_number*/
440     0,                         /* tp_as_sequence*/
441     0,                         /* tp_as_mapping*/
442     0,                         /* tp_hash */
443     0,                         /* tp_call*/
444     (reprfunc)MessageInfo_str, /* tp_str*/
445     0,                         /* tp_getattro*/
446     0,                         /* tp_setattro*/
447     0,                         /* tp_as_buffer*/
448     Py_TPFLAGS_DEFAULT,        /* tp_flags*/
449     "A MessageInfo represents" /* tp_doc */
450     " a single message.\n\n"
451     "Do not construct objects of this type yourself.",
452     0,                         /* tp_traverse */
453     0,                         /* tp_clear */
454     0,                         /* tp_richcompare */
455     0,                         /* tp_weaklistoffset */
456     0,                         /* tp_iter */
457     0,                         /* tp_iternext */
458     MessageInfo_methods,       /* tp_methods */
459     0,                         /* tp_members */
460     MessageInfo_getset,        /* tp_getset */
461     0,                         /* tp_base */
462     0,                         /* tp_dict */
463     0,                         /* tp_descr_get */
464     0,                         /* tp_descr_set */
465     0,                         /* tp_dictoffset */
466     (initproc)MessageInfo_init,/* tp_init */
467     0,                         /* tp_alloc */
468     0,                         /* tp_new */
469     0,                         /* tp_free */
470         0,                         /* tp_is_gc */
471         0,                         /* tp_bases */
472         0,                         /* tp_mro */
473         0,                         /* tp_cache */
474         0,                         /* tp_subclasses */
475         0,                         /* tp_weaklist */
476         0,                         /* tp_del */
477 #if ((PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6) || \
478      (PY_MAJOR_VERSION == 3))
479     0,                         /* tp_version_tag */
480 #endif
481 #if (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 4)
482     0,                         /* tp_finalize */
483 #endif
484 };
485
486 gboolean cmpy_add_messageinfo(PyObject *module)
487 {
488   clawsmail_MessageInfoType.tp_new = PyType_GenericNew;
489   if(PyType_Ready(&clawsmail_MessageInfoType) < 0)
490     return FALSE;
491
492   Py_INCREF(&clawsmail_MessageInfoType);
493   return (PyModule_AddObject(module, "MessageInfo", (PyObject*)&clawsmail_MessageInfoType) == 0);
494 }
495
496 PyObject* clawsmail_messageinfo_new(MsgInfo *msginfo)
497 {
498   clawsmail_MessageInfoObject *ff;
499
500   if(!msginfo)
501     return NULL;
502
503   ff = (clawsmail_MessageInfoObject*) PyObject_CallObject((PyObject*) &clawsmail_MessageInfoType, NULL);
504   if(!ff)
505     return NULL;
506
507   ff->msginfo = msginfo;
508   return (PyObject*)ff;
509 }
510
511 PyTypeObject* clawsmail_messageinfo_get_type_object()
512 {
513   return &clawsmail_MessageInfoType;
514 }
515
516 MsgInfo* clawsmail_messageinfo_get_msginfo(PyObject *self)
517 {
518   return ((clawsmail_MessageInfoObject*)self)->msginfo;
519 }