d5729d67d0a6ad912c04a3998803b4650f2793d5
[claws.git] / src / plugins / perl / perl_plugin.c
1 /* Perl plugin -- Perl Support for Claws Mail
2  *
3  * Copyright (C) 2004-2007 Holger Berndt
4  *
5  * Sylpheed and Claws-Mail are GTK+ based, lightweight, and fast e-mail clients
6  * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail Team
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program. If not, see <http://www.gnu.org/licenses/>.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #include "claws-features.h"
25 #endif
26
27 #include "common/version.h"
28 #include "common/defs.h"
29 #include "common/utils.h"
30 #include "common/claws.h"
31 #include "common/prefs.h"
32 #include "procmsg.h"
33 #include "procheader.h"
34 #include "folder.h"
35 #include "account.h"
36 #include "compose.h"
37 #include "addrindex.h"
38 #include "addritem.h"
39 #include "addr_compl.h"
40 #include "statusbar.h"
41 #include "alertpanel.h"
42 #include "common/hooks.h"
43 #include "prefs_common.h"
44 #include "prefs_gtk.h"
45 #include "common/log.h"
46 #include "common/plugin.h"
47 #include "common/tags.h"
48
49 #include <EXTERN.h>
50 #include <perl.h>
51 #include <XSUB.h>
52
53 #ifdef _
54 # undef _
55 #endif
56
57 #include <glib.h>
58 #include <glib/gi18n.h>
59
60 #include <string.h>
61 #include <sys/types.h>
62 #include <sys/stat.h>
63 #include <sys/wait.h>
64 #include <unistd.h>
65
66 #include "perl_plugin.h"
67 #include "perl_gtk.h"
68
69
70 /* XSRETURN_UV was introduced in Perl 5.8.1,
71    this fixes things for 5.8.0. */
72 #ifndef XSRETURN_UV
73 #  ifndef XST_mUV
74 #    define XST_mUV(i,v)  (ST(i) = sv_2mortal(newSVuv(v))  )
75 #  endif /* XST_mUV */
76 #  define XSRETURN_UV(v) STMT_START { XST_mUV(0,v);  XSRETURN(1); } STMT_END
77 #endif /* XSRETURN_UV */
78
79 /* set this to "1" to recompile the Perl script for every mail,
80    even if it hasn't changed */
81 #define DO_CLEAN "0"
82
83 /* distinguish between automatic and manual filtering */
84 #define AUTO_FILTER 0
85 #define MANU_FILTER 1
86
87 /* embedded Perl stuff */
88 static PerlInterpreter *my_perl = NULL;
89 EXTERN_C void xs_init(pTHX);
90 EXTERN_C void boot_DynaLoader (pTHX_ CV* cv);
91
92 /* plugin stuff */
93 static guint             filtering_hook_id;
94 static guint             manual_filtering_hook_id;
95 static MailFilteringData *mail_filtering_data  = NULL;
96 static MsgInfo           *msginfo              = NULL;
97 static gboolean          stop_filtering        = FALSE;
98 static gboolean          manual_filtering      = FALSE;
99 static gboolean          wrote_filter_log_head = FALSE;
100 static gint              filter_log_verbosity;
101 static FILE              *message_file         = NULL;
102 static gchar             *attribute_key        = NULL;
103
104 /* configuration */
105 static PerlPluginConfig config;
106
107 static PrefParam param[] = {
108   {"filter_log_verbosity", "2", &config.filter_log_verbosity,
109    P_INT, NULL, NULL, NULL},
110   {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
111 };
112
113
114 /* Utility functions */
115
116 /* fire and forget */
117 gint execute_detached(gchar **cmdline)
118 {
119   pid_t pid;
120   
121   if((pid = fork()) < 0) { /* fork error */
122     perror("fork");
123     return 0;
124   }
125   else if(pid > 0) {       /* parent */
126     waitpid(pid, NULL, 0);
127     return 1;
128   }
129   else {                   /* child */
130     if((pid = fork()) < 0) { /* fork error */
131       perror("fork");
132       return 0;
133     }
134     else if(pid > 0) {     /* child */
135       /* make grand child an orphan */
136       _exit(0);
137     }
138     else {                 /* grand child */
139       execvp(cmdline[0], cmdline);
140       perror("execvp");
141       _exit(1);
142     }
143   }
144   return 0;
145 }
146
147
148 /* filter logfile */
149 #define LOG_MANUAL 1
150 #define LOG_ACTION 2
151 #define LOG_MATCH  3
152
153 static void filter_log_write(gint type, gchar *text) {
154   if(filter_log_verbosity >= type) {
155     if(!wrote_filter_log_head) {
156       log_message(LOG_PROTOCOL, "From: %s || Subject: %s || Message-ID: %s\n",
157         msginfo->from    ? msginfo->from    : "<no From header>",
158         msginfo->subject ? msginfo->subject : "<no Subject header>",
159         msginfo->msgid   ? msginfo->msgid   : "<no message id>");
160       wrote_filter_log_head = TRUE;
161     }
162     switch(type) {
163     case LOG_MANUAL:
164       log_message(LOG_PROTOCOL, "    MANUAL: %s\n", text?text:"<no text specified>");
165       break;
166     case LOG_ACTION:
167       log_message(LOG_PROTOCOL, "    ACTION: %s\n", text?text:"<no text specified>");
168       break;
169     case LOG_MATCH:
170       log_message(LOG_PROTOCOL, "    MATCH:  %s\n", text?text:"<no text specified>");
171       break;
172     default:
173       g_warning("Perl Plugin: Wrong use of filter_log_write");
174       break;
175     }
176   }
177 }
178
179 /* Addressbook interface */
180
181 static PerlPluginTimedSList *email_slist = NULL;
182 static GHashTable *attribute_hash        = NULL;
183
184 /* addressbook email collector callback */
185 static gint add_to_email_slist(ItemPerson *person, const gchar *bookname)
186 {
187   PerlPluginEmailEntry *ee;
188   GList *nodeM;
189
190   /* Process each E-Mail address */
191   nodeM = person->listEMail;
192   while(nodeM) {
193     ItemEMail *email = nodeM->data;
194     ee = g_new0(PerlPluginEmailEntry,1);
195     g_return_val_if_fail(ee != NULL, -1);
196
197     if(email->address != NULL) ee->address  = g_strdup(email->address);
198     else                       ee->address  = NULL;
199     if(bookname != NULL)       ee->bookname = g_strdup(bookname);
200     else                       ee->bookname = NULL;
201
202     email_slist->g_slist = g_slist_prepend(email_slist->g_slist,ee);
203     nodeM = g_list_next(nodeM);
204   }
205   return 0;
206 }
207
208 /* free a GSList of PerlPluginEmailEntry's. */
209 static void free_PerlPluginEmailEntry_slist(GSList *slist)
210 {
211   GSList *walk;
212
213   if(slist == NULL)
214     return;
215
216   walk = slist;
217   for(; walk != NULL; walk = g_slist_next(walk)) {
218     PerlPluginEmailEntry *ee = (PerlPluginEmailEntry *) walk->data;
219     if(ee != NULL) {
220       if(ee->address  != NULL) g_free(ee->address);
221       if(ee->bookname != NULL) g_free(ee->bookname);
222       g_free(ee);
223       ee = NULL;
224     }
225   }
226   g_slist_free(slist);
227
228   debug_print("PerlPluginEmailEntry slist freed\n");
229 }
230
231 /* free email_slist */
232 static void free_email_slist(void)
233 {
234   if(email_slist == NULL)
235     return;
236
237   free_PerlPluginEmailEntry_slist(email_slist->g_slist);
238   email_slist->g_slist = NULL;
239
240   g_free(email_slist);
241   email_slist = NULL;
242
243   debug_print("email_slist freed\n");
244 }
245
246 /* check if tl->g_slist exists and is recent enough */
247 static gboolean update_PerlPluginTimedSList(PerlPluginTimedSList *tl)
248 {
249   gboolean retVal;
250   gchar *indexfile;
251   struct stat filestat;
252
253   if(tl->g_slist == NULL)
254     return TRUE;
255
256   indexfile = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ADDRESSBOOK_INDEX_FILE, NULL);
257   if((stat(indexfile,&filestat) == 0) && filestat.st_mtime <= tl->mtime)
258      retVal = FALSE;
259   else
260     retVal = TRUE;
261
262   g_free(indexfile);
263   return retVal;
264 }
265
266 /* (re)initialize email slist */
267 static void init_email_slist(void)
268 {
269   gchar *indexfile;
270   struct stat filestat;
271
272   if(email_slist->g_slist != NULL) {
273     free_PerlPluginEmailEntry_slist(email_slist->g_slist);
274     email_slist->g_slist = NULL;
275   }
276
277   addrindex_load_person_attribute(NULL,add_to_email_slist);
278
279   indexfile = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ADDRESSBOOK_INDEX_FILE, NULL);
280   if(stat(indexfile,&filestat) == 0)
281     email_slist->mtime = filestat.st_mtime;
282   g_free(indexfile);
283   debug_print("Initialisation of email slist completed\n");
284 }
285
286 /* check if given address is in given addressbook */
287 static gboolean addr_in_addressbook(gchar *addr, gchar *bookname)
288 {
289   /* If no book is given, check the address completion list
290    * (there may be other addresses that are not in the address book,
291    * added by other plugins). */
292   if(bookname == NULL) {
293     gboolean found;
294     start_address_completion(NULL);
295     found = (complete_matches_found(addr) > 0);
296     end_address_completion();
297     return found;
298   }
299   else {
300     GSList *walk;
301
302     /* check if email_list exists */
303     if(email_slist == NULL) {
304       email_slist = g_new0(PerlPluginTimedSList,1);
305       email_slist->g_slist = NULL;
306       debug_print("email_slist created\n");
307     }
308
309     if(update_PerlPluginTimedSList(email_slist))
310       init_email_slist();
311
312     walk = email_slist->g_slist;
313     for(; walk != NULL; walk = g_slist_next(walk)) {
314       PerlPluginEmailEntry *ee = (PerlPluginEmailEntry *) walk->data;
315       gchar *a = g_utf8_casefold(ee->address, -1);
316       gchar *b = g_utf8_casefold(addr, -1);
317       if((!g_utf8_collate(a,b)) &&
318          ((bookname == NULL) || (!strcmp(ee->bookname,bookname)))) {
319         g_free(a);
320         g_free(b);
321         return TRUE;
322       }
323       g_free(a);
324       g_free(b);
325     }
326   }
327
328   return FALSE;
329 }
330
331 /* attribute hash collector callback */
332 static gint add_to_attribute_hash(ItemPerson *person, const gchar *bookname)
333 {
334   PerlPluginTimedSList *tl;
335   PerlPluginAttributeEntry *ae;
336   GList *nodeA;
337   GList *nodeM;
338
339   nodeA = person->listAttrib;
340   /* Process each User Attribute */
341   while(nodeA) {
342     UserAttribute *attrib = nodeA->data;
343     if(attrib->name && !strcmp(attrib->name,attribute_key) ) {
344       /* Process each E-Mail address */
345       nodeM = person->listEMail;
346       while(nodeM) {
347   ItemEMail *email = nodeM->data;
348
349   ae = g_new0(PerlPluginAttributeEntry,1);
350   g_return_val_if_fail(ae != NULL, -1);
351
352   if(email->address != NULL) ae->address  = g_strdup(email->address);
353   else                       ae->address  = NULL;
354   if(attrib->value  != NULL) ae->value    = g_strdup(attrib->value);
355   else                       ae->value    = NULL;
356   if(bookname != NULL)       ae->bookname = g_strdup(bookname);
357   else                       ae->bookname = NULL;
358
359   tl = (PerlPluginTimedSList *) g_hash_table_lookup(attribute_hash,attribute_key);
360   tl->g_slist = g_slist_prepend(tl->g_slist,ae);
361
362   nodeM = g_list_next(nodeM);
363       }
364     }
365     nodeA = g_list_next(nodeA);
366   }
367   
368   return 0;
369 }
370
371 /* free a key of the attribute hash */
372 static gboolean free_attribute_hash_key(gpointer key, gpointer value, gpointer user_data)
373 {
374   GSList *walk;
375   PerlPluginTimedSList *tl;
376
377   debug_print("Freeing key `%s' from attribute_hash\n",key?(char*)key:"");
378
379   tl = (PerlPluginTimedSList *) value;
380
381   if(tl != NULL) {
382     if(tl->g_slist != NULL) {
383       walk = tl->g_slist;
384       for(; walk != NULL; walk = g_slist_next(walk)) {
385   PerlPluginAttributeEntry *ae = (PerlPluginAttributeEntry *) walk->data;
386   if(ae != NULL) {
387     if(ae->address  != NULL) g_free(ae->address);
388     if(ae->value    != NULL) g_free(ae->value);
389     if(ae->bookname != NULL) g_free(ae->bookname);
390     g_free(ae);
391     ae = NULL;
392   }
393       }
394       g_slist_free(tl->g_slist);
395       tl->g_slist = NULL;
396     }
397     g_free(tl);
398     tl = NULL;
399   }
400
401   if(key != NULL) {
402     g_free(key);
403     key = NULL;
404   }
405
406   return TRUE;
407 }
408
409 /* free whole attribute hash */
410 static void free_attribute_hash(void)
411 {
412   if(attribute_hash == NULL)
413     return;
414
415   g_hash_table_foreach_remove(attribute_hash,free_attribute_hash_key,NULL);
416   g_hash_table_destroy(attribute_hash);
417   attribute_hash = NULL;
418
419   debug_print("attribute_hash freed\n");
420 }
421
422 /* Free the key if it exists. Insert the new key. */
423 static void insert_attribute_hash(gchar *attr)
424 {
425   PerlPluginTimedSList *tl;
426   gchar *indexfile;
427   struct stat filestat;
428
429   /* Check if key exists. Free it if it does. */
430   if((tl = g_hash_table_lookup(attribute_hash,attr)) != NULL) {
431     gpointer origkey;
432     gpointer value;
433     g_hash_table_lookup_extended(attribute_hash,attr,&origkey,&value);
434     g_hash_table_remove(attribute_hash,origkey);
435     free_attribute_hash_key(origkey,value,NULL);
436     debug_print("Existing key `%s' freed.\n",attr);
437   }
438
439   tl = g_new0(PerlPluginTimedSList,1);
440   tl->g_slist = NULL;
441
442   attribute_key = g_strdup(attr);
443   g_hash_table_insert(attribute_hash,attribute_key,tl);  
444
445   addrindex_load_person_attribute(attribute_key,add_to_attribute_hash);
446
447   indexfile = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ADDRESSBOOK_INDEX_FILE, NULL);
448   if(stat(indexfile,&filestat) == 0)
449     tl->mtime = filestat.st_mtime;
450   g_free(indexfile);
451
452   debug_print("added key `%s' to attribute_hash\n",attribute_key?attribute_key:"");
453 }
454
455 /* check if an update of the attribute hash entry is necessary */
456 static gboolean update_attribute_hash(const gchar *attr)
457 {
458   PerlPluginTimedSList *tl;
459
460   /* check if key attr exists in the attribute hash */
461   if((tl = (PerlPluginTimedSList*) g_hash_table_lookup(attribute_hash,attr)) == NULL)
462     return TRUE;
463
464   /* check if entry is recent enough */
465   return update_PerlPluginTimedSList(tl);
466 }
467
468 /* given an email address, return attribute value of specific book */
469 static gchar* get_attribute_value(gchar *email, gchar *attr, gchar *bookname)
470 {
471   GSList *walk;
472   PerlPluginTimedSList *tl;
473
474   /* check if attribute hash exists */
475   if(attribute_hash == NULL) {
476     attribute_hash = g_hash_table_new(g_str_hash,g_str_equal);
477     debug_print("attribute_hash created\n");
478   }
479
480   if(update_attribute_hash(attr)) {
481     debug_print("Initialisation of attribute hash entry `%s' is necessary\n",attr);
482     insert_attribute_hash(attr);
483   }
484   
485   if((tl = (PerlPluginTimedSList*) g_hash_table_lookup(attribute_hash,attr)) == NULL)
486     return NULL;  
487
488   walk = tl->g_slist;
489   for(; walk != NULL; walk = g_slist_next(walk)) {
490     PerlPluginAttributeEntry *ae = (PerlPluginAttributeEntry *) walk->data;
491     gchar *a, *b;
492     a = g_utf8_strdown(ae->address, -1);
493     b = g_utf8_strdown(email, -1);
494     if(!g_utf8_collate(a, b)) {
495       if((bookname == NULL) ||
496    ((ae->bookname != NULL) && !strcmp(bookname,ae->bookname))) {
497         g_free(a); g_free(b);
498   return ae->value;
499       }
500     }
501     g_free(a); g_free(b);
502   }
503   return NULL;
504 }
505
506 /* free up all memory allocated with lists */
507 static void free_all_lists(void)
508 {
509   /* email list */
510   free_email_slist();
511
512   /* attribute hash */
513   free_attribute_hash();
514 }
515
516
517
518 /* ClawsMail::C module */
519
520 /* Initialization */
521
522 /* ClawsMail::C::filter_init(int) */
523 static XS(XS_ClawsMail_filter_init)
524 {
525   int flag;
526   /* flags:
527    *
528    *    msginfo
529    *          1 size
530    *          2 date
531    *          3 from
532    *          4 to
533    *          5 cc
534    *          6 newsgroups
535    *          7 subject
536    *          8 msgid
537    *          9 inreplyto
538    *         10 xref
539    *         11 xface
540    *         12 dispositionnotificationto
541    *         13 returnreceiptto
542    *         14 references
543    *         15 score
544    *         16 not used anymore
545    *         17 plaintext_file
546    *         18 not used anymore
547    *         19 hidden
548    *         20 message file path
549    *         21 partial_recv
550    *         22 total_size
551    *         23 account_server
552    *         24 account_login
553    *         25 planned_download
554    *
555    *    general
556    *        100 manual
557    */
558   char *charp;
559   gchar buf[BUFFSIZE];
560   GSList *walk;
561   int ii;
562
563   dXSARGS;
564   if(items != 1) {
565     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::init");
566     XSRETURN_UNDEF;
567   }
568   flag = SvIV(ST(0));
569   switch(flag) {
570
571     /* msginfo */
572   case  1:
573     msginfo->size       ? XSRETURN_UV(msginfo->size)       : XSRETURN_UNDEF;
574   case  2:
575     msginfo->date       ? XSRETURN_PV(msginfo->date)       : XSRETURN_UNDEF;
576   case  3:
577     msginfo->from       ? XSRETURN_PV(msginfo->from)       : XSRETURN_UNDEF;
578   case  4:
579     msginfo->to         ? XSRETURN_PV(msginfo->to)         : XSRETURN_UNDEF;
580   case  5:
581     msginfo->cc         ? XSRETURN_PV(msginfo->cc)         : XSRETURN_UNDEF;
582   case  6:
583     msginfo->newsgroups ? XSRETURN_PV(msginfo->newsgroups) : XSRETURN_UNDEF;
584   case  7:
585     msginfo->subject    ? XSRETURN_PV(msginfo->subject)    : XSRETURN_UNDEF;
586   case  8:
587     msginfo->msgid      ? XSRETURN_PV(msginfo->msgid)      : XSRETURN_UNDEF;
588   case  9:
589     msginfo->inreplyto  ? XSRETURN_PV(msginfo->inreplyto)  : XSRETURN_UNDEF;
590   case 10:
591     msginfo->xref       ? XSRETURN_PV(msginfo->xref)       : XSRETURN_UNDEF;
592   case 11:
593     (msginfo->extradata && msginfo->extradata->xface) ?
594       XSRETURN_PV(msginfo->extradata->xface)               : XSRETURN_UNDEF;
595   case 12:
596     (msginfo->extradata && msginfo->extradata->dispositionnotificationto) ?
597       XSRETURN_PV(msginfo->extradata->dispositionnotificationto) : XSRETURN_UNDEF;
598   case 13:
599     (msginfo->extradata && msginfo->extradata->returnreceiptto) ?
600       XSRETURN_PV(msginfo->extradata->returnreceiptto)     : XSRETURN_UNDEF;
601   case 14:
602     EXTEND(SP, g_slist_length(msginfo->references));
603     ii = 0;
604     for(walk = msginfo->references; walk != NULL; walk = g_slist_next(walk))
605       XST_mPV(ii++,walk->data ? (gchar*) walk->data: "");
606     ii ? XSRETURN(ii) : XSRETURN_UNDEF;
607   case 15:
608     msginfo->score      ? XSRETURN_IV(msginfo->score)      : XSRETURN_UNDEF;
609   case 17:
610     msginfo->plaintext_file ?
611       XSRETURN_PV(msginfo->plaintext_file)                 : XSRETURN_UNDEF;
612   case 19:
613     msginfo->hidden     ? XSRETURN_IV(msginfo->hidden)     : XSRETURN_UNDEF;
614   case 20:
615     if((charp = procmsg_get_message_file_path(msginfo)) != NULL) {
616       strncpy2(buf,charp,sizeof(buf));
617       g_free(charp);
618       XSRETURN_PV(buf);
619     }
620     else
621       XSRETURN_UNDEF;
622   case 21:
623     (msginfo->extradata && msginfo->extradata->partial_recv) ?
624       XSRETURN_PV(msginfo->extradata->partial_recv)        : XSRETURN_UNDEF;
625   case 22:
626     msginfo->total_size ? XSRETURN_IV(msginfo->total_size) : XSRETURN_UNDEF;
627   case 23:
628     (msginfo->extradata && msginfo->extradata->account_server) ?
629       XSRETURN_PV(msginfo->extradata->account_server)      : XSRETURN_UNDEF;
630   case 24:
631     (msginfo->extradata && msginfo->extradata->account_login) ?
632       XSRETURN_PV(msginfo->extradata->account_login)       : XSRETURN_UNDEF;
633   case 25:
634     msginfo->planned_download ?
635       XSRETURN_IV(msginfo->planned_download)               : XSRETURN_UNDEF;
636
637     /* general */
638   case 100:
639     if(manual_filtering)
640       XSRETURN_YES;
641     else
642       XSRETURN_NO;
643   default:
644     g_warning("Perl Plugin: Wrong argument to ClawsMail::C::init");
645     XSRETURN_UNDEF;    
646   }
647 }
648
649 /* ClawsMail::C::open_mail_file */
650 static XS(XS_ClawsMail_open_mail_file)
651 {
652   char *file;
653   gchar buf[BUFFSIZE];
654
655   dXSARGS;
656   if(items != 0) {
657     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::open_mail_file");
658     XSRETURN_UNDEF;
659   }
660   file = procmsg_get_message_file_path(msginfo);
661   if(!file)
662     XSRETURN_UNDEF;
663   strncpy2(buf,file,sizeof(buf));
664   g_free(file);
665   if((message_file = fopen(buf, "rb")) == NULL) {
666     FILE_OP_ERROR(buf, "fopen");
667     g_warning("Perl Plugin: File open error in ClawsMail::C::open_mail_file");
668     XSRETURN_UNDEF;
669   }
670 }
671
672 /* ClawsMail::C::close_mail_file */
673 static XS(XS_ClawsMail_close_mail_file)
674 {
675   dXSARGS;
676   if(items != 0) {
677     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::close_mail_file");
678     XSRETURN_UNDEF;
679   }
680   if(message_file != NULL)
681     fclose(message_file);
682   XSRETURN_YES;
683 }
684
685 /* ClawsMail::C::get_next_header */
686 static XS(XS_ClawsMail_get_next_header)
687 {
688   gchar buf[BUFFSIZE];
689   Header *header;
690
691   dXSARGS;
692   if(items != 0) {
693     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::get_next_header");
694     XSRETURN_EMPTY;
695   }
696   if(message_file == NULL) {
697     g_warning("Perl Plugin: Message file not open. Use ClawsMail::C::open_message_file first.");
698     XSRETURN_EMPTY;
699   }
700   if(procheader_get_one_field(buf, sizeof(buf), message_file, NULL) != -1) {
701     header = procheader_parse_header(buf);
702     EXTEND(SP, 2);
703     if(header) {
704       XST_mPV(0,header->name);
705       XST_mPV(1,header->body);
706       procheader_header_free(header);
707     }
708     else {
709       XST_mPV(0,"");
710       XST_mPV(1,"");
711     }
712     XSRETURN(2);
713   }
714   else
715     XSRETURN_EMPTY;
716 }
717
718 /* ClawsMail::C::get_next_body_line */
719 static XS(XS_ClawsMail_get_next_body_line)
720 {
721   gchar buf[BUFFSIZE];
722
723   dXSARGS;
724   if(items != 0) {
725     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::get_next_body_line");
726     XSRETURN_UNDEF;
727   }
728   if(message_file == NULL) {
729     g_warning("Perl Plugin: Message file not open. Use ClawsMail::C::open_message_file first.");
730     XSRETURN_UNDEF;
731   }
732   if(fgets(buf, sizeof(buf), message_file) != NULL)
733     XSRETURN_PV(buf);
734   else
735     XSRETURN_UNDEF;
736 }
737
738
739 /* Filter matchers */
740
741 /* ClawsMail::C::check_flag(int) */
742 static XS(XS_ClawsMail_check_flag)
743 {
744   int flag;
745   /* flags:  1 marked
746    *         2 unread
747    *         3 deleted
748    *       4 new
749    *       5 replied
750    *       6 forwarded
751    *       7 locked
752    *         8 ignore thread
753    */
754
755   dXSARGS;
756   if(items != 1) {
757     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::check_flag");
758     XSRETURN_UNDEF;
759   }
760   flag = SvIV(ST(0));
761
762   switch(flag) {
763   case 1:
764     if(MSG_IS_MARKED(msginfo->flags)) {
765       filter_log_write(LOG_MATCH,"marked");
766       XSRETURN_YES;
767     }
768     else
769       XSRETURN_NO;
770   case 2:
771     if(MSG_IS_UNREAD(msginfo->flags)) {
772       filter_log_write(LOG_MATCH,"unread");
773       XSRETURN_YES;
774     }
775     else
776       XSRETURN_NO;
777   case 3:
778     if(MSG_IS_DELETED(msginfo->flags)) {
779       filter_log_write(LOG_MATCH,"deleted");
780       XSRETURN_YES;
781     }
782     else
783       XSRETURN_NO;
784   case 4:
785     if(MSG_IS_NEW(msginfo->flags)) {
786       filter_log_write(LOG_MATCH,"new");
787       XSRETURN_YES;
788     }
789     else
790       XSRETURN_NO;
791   case 5:
792     if(MSG_IS_REPLIED(msginfo->flags)) {
793       filter_log_write(LOG_MATCH,"replied");
794       XSRETURN_YES;
795     }
796     else
797       XSRETURN_NO;
798   case 6:
799     if(MSG_IS_FORWARDED(msginfo->flags)) {
800       filter_log_write(LOG_MATCH,"forwarded");
801       XSRETURN_YES;
802     }
803     else
804       XSRETURN_NO;
805   case 7:
806     if(MSG_IS_LOCKED(msginfo->flags)) {
807       filter_log_write(LOG_MATCH,"locked");
808       XSRETURN_YES;
809     }
810     else
811       XSRETURN_NO;
812   case 8:
813     if(MSG_IS_IGNORE_THREAD(msginfo->flags)) {
814       filter_log_write(LOG_MATCH,"ignore_thread");
815       XSRETURN_YES;
816     }
817     else
818       XSRETURN_NO;
819   default:
820     g_warning("Perl Plugin: Unknown argument to ClawsMail::C::check_flag");
821     XSRETURN_UNDEF;
822   }
823 }
824
825 /* ClawsMail::C::colorlabel(int) */
826 static XS(XS_ClawsMail_colorlabel)
827 {
828   int color;
829
830   dXSARGS;
831   if(items != 1) {
832     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::colorlabel");
833     XSRETURN_UNDEF;
834   }
835   color = SvIV(ST(0));
836
837   if((MSG_GET_COLORLABEL_VALUE(msginfo->flags) == (guint32)color)) {
838     filter_log_write(LOG_MATCH,"colorlabel");
839     XSRETURN_YES;
840   }
841   else
842     XSRETURN_NO;
843 }
844
845 /* ClawsMail::C::age_greater(int) */
846 static XS(XS_ClawsMail_age_greater)
847 {
848   int age;
849   time_t t;
850
851   dXSARGS;
852   if(items != 1) {
853     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::age_greater");
854     XSRETURN_UNDEF;
855   }
856   age = SvIV(ST(0));
857   t = time(NULL);
858   if(((t - msginfo->date_t) / 86400) >= age) {
859     filter_log_write(LOG_MATCH,"age_greater");
860     XSRETURN_YES;
861   }
862   else
863     XSRETURN_NO;
864 }
865
866 /* ClawsMail::C::age_lower(int) */
867 static XS(XS_ClawsMail_age_lower)
868 {
869   int age;
870   time_t t;
871
872   dXSARGS;
873   if(items != 1) {
874     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::age_lower");
875     XSRETURN_UNDEF;
876   }
877   age = SvIV(ST(0));
878   t = time(NULL);
879   if(((t - msginfo->date_t) / 86400) <= age) {
880     filter_log_write(LOG_MATCH,"age_lower");
881     XSRETURN_YES;
882   }
883   else
884     XSRETURN_NO;
885 }
886
887 /* ClawsMail::C::tagged() */
888 static XS(XS_ClawsMail_tagged)
889 {
890   dXSARGS;
891   if(items != 0) {
892     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::tagged");
893     XSRETURN_UNDEF;
894   }
895
896   return msginfo->tags ? XSRETURN_YES : XSRETURN_NO;
897 }
898
899 /* ClawsMail::C::get_tags() */
900 static XS(XS_ClawsMail_get_tags)
901 {
902   guint iTag;
903   guint num_tags;
904   GSList *walk;
905
906   dXSARGS;
907   if(items != 0) {
908     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::get_tags");
909     XSRETURN_UNDEF;
910   }
911
912   num_tags = g_slist_length(msginfo->tags);
913
914   EXTEND(SP, num_tags);
915   iTag = 0;
916   for(walk = msginfo->tags; walk != NULL; walk = g_slist_next(walk)) {
917     const char *tag_str;
918     tag_str = tags_get_tag(GPOINTER_TO_INT(walk->data));
919     XST_mPV(iTag++, tag_str ? tag_str: "");
920   }
921
922   XSRETURN(num_tags);
923 }
924
925
926
927 /* ClawsMail::C::set_tag(char*) */
928 static XS(XS_ClawsMail_set_tag)
929 {
930   gchar *tag_str;
931   gint tag_id;
932
933   dXSARGS;
934   if(items != 1) {
935     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::set_tag");
936     XSRETURN_UNDEF;
937   }
938
939   tag_str = SvPV_nolen(ST(0));
940   tag_id = tags_get_id_for_str(tag_str);
941   if(tag_id == -1) {
942     g_warning("Perl Plugin: set_tag requested setting of a non-existing tag");
943     XSRETURN_UNDEF;
944   }
945
946   procmsg_msginfo_update_tags(msginfo, TRUE, tag_id);
947
948   XSRETURN_YES;
949 }
950
951 /* ClawsMail::C::unset_tag(char*) */
952 static XS(XS_ClawsMail_unset_tag)
953 {
954   gchar *tag_str;
955   gint tag_id;
956
957   dXSARGS;
958   if(items != 1) {
959     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::unset_tag");
960     XSRETURN_UNDEF;
961   }
962
963   tag_str = SvPV_nolen(ST(0));
964   tag_id = tags_get_id_for_str(tag_str);
965   if(tag_id == -1) {
966     g_warning("Perl Plugin: unset_tag requested setting of a non-existing tag");
967     XSRETURN_UNDEF;
968   }
969
970   procmsg_msginfo_update_tags(msginfo, FALSE, tag_id);
971
972   XSRETURN_YES;
973 }
974
975 /* ClawsMail::C::clear_tags() */
976 static XS(XS_ClawsMail_clear_tags)
977 {
978   dXSARGS;
979   if(items != 0) {
980     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::clear_tags");
981     XSRETURN_UNDEF;
982   }
983
984   procmsg_msginfo_clear_tags(msginfo);
985   XSRETURN_YES;
986 }
987
988
989 /* ClawsMail::C::make_sure_tag_exists(char*) */
990 static XS(XS_ClawsMail_make_sure_tag_exists)
991 {
992   gchar *tag_str;
993
994   dXSARGS;
995   if(items != 1) {
996     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::make_sure_tag_exists");
997     XSRETURN_UNDEF;
998   }
999
1000   tag_str = SvPV_nolen(ST(0));
1001
1002   if(IS_NOT_RESERVED_TAG(tag_str) == FALSE) {
1003     g_warning("Perl Plugin: Trying to create a tag with a reserved name: %s", tag_str);
1004     XSRETURN_UNDEF;
1005   }
1006
1007   tags_add_tag(tag_str);
1008
1009   XSRETURN_YES;
1010 }
1011
1012
1013
1014 /* ClawsMail::C::make_sure_folder_exists(char*) */
1015 static XS(XS_ClawsMail_make_sure_folder_exists)
1016 {
1017   gchar *identifier;
1018   FolderItem *item;
1019
1020   dXSARGS;
1021   if(items != 1) {
1022     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::make_sure_folder_exists");
1023     XSRETURN_UNDEF;
1024   }
1025
1026   identifier = SvPV_nolen(ST(0));
1027   item = folder_get_item_from_identifier(identifier);
1028   if(item)
1029     XSRETURN_YES;
1030   else
1031     XSRETURN_NO;
1032 }
1033
1034
1035 /* ClawsMail::C::addr_in_addressbook(char* [, char*]) */
1036 static XS(XS_ClawsMail_addr_in_addressbook)
1037 {
1038   gchar *addr;
1039   gchar *bookname;
1040   gboolean found;
1041
1042   dXSARGS;
1043   if(items != 1 && items != 2) {
1044     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::addr_in_addressbook");
1045     XSRETURN_UNDEF;
1046   }
1047
1048   addr = SvPV_nolen(ST(0));
1049
1050   if(items == 1) {
1051     found = addr_in_addressbook(addr,NULL);
1052   }
1053   else {
1054     bookname = SvPV_nolen(ST(1));
1055     found = addr_in_addressbook(addr,bookname);
1056   }
1057
1058   if(found) {
1059     filter_log_write(LOG_MATCH,"addr_in_addressbook");
1060     XSRETURN_YES;
1061   }
1062   else
1063     XSRETURN_NO;
1064 }
1065
1066
1067 /* Filter actions */
1068
1069 /* ClawsMail::C::set_flag(int) */
1070 static XS(XS_ClawsMail_set_flag)
1071 {
1072   int flag;
1073   /* flags:  1 mark
1074    *         2 mark as unread
1075    *         7 lock
1076    */
1077
1078   dXSARGS;
1079   if(items != 1) {
1080     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::set_flag");
1081     XSRETURN_UNDEF;
1082   }
1083   flag = SvIV(ST(0));
1084
1085   switch(flag) {
1086   case 1:
1087     MSG_SET_PERM_FLAGS(msginfo->flags, MSG_MARKED);
1088     procmsg_msginfo_set_flags(msginfo, MSG_MARKED,0);
1089     filter_log_write(LOG_ACTION,"mark");
1090     XSRETURN_YES;
1091   case 2:
1092     MSG_SET_PERM_FLAGS(msginfo->flags, MSG_UNREAD);
1093     procmsg_msginfo_set_flags(msginfo, MSG_UNREAD,0);
1094     filter_log_write(LOG_ACTION,"mark_as_unread");
1095     XSRETURN_YES;
1096   case 7:
1097     MSG_SET_PERM_FLAGS(msginfo->flags, MSG_LOCKED);
1098     procmsg_msginfo_set_flags(msginfo, MSG_LOCKED,0);
1099     filter_log_write(LOG_ACTION,"lock");
1100     XSRETURN_YES;
1101   default:
1102     g_warning("Perl Plugin: Unknown argument to ClawsMail::C::set_flag");
1103     XSRETURN_UNDEF;
1104   }
1105 }
1106
1107 /* ClawsMail::C::unset_flag(int) */
1108 static XS(XS_ClawsMail_unset_flag)
1109 {
1110   int flag;
1111   /*
1112    * flags:  1 unmark
1113    *         2 mark as read
1114    *         7 unlock
1115    */
1116
1117   dXSARGS;
1118   if(items != 1) {
1119     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::unset_flag");
1120     XSRETURN_UNDEF;
1121   }
1122   flag = SvIV(ST(0));
1123
1124   switch(flag) {
1125   case 1:
1126     MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_MARKED);
1127     procmsg_msginfo_unset_flags(msginfo, MSG_MARKED,0);
1128     filter_log_write(LOG_ACTION,"unmark");
1129     XSRETURN_YES;
1130   case 2:
1131     MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_UNREAD | MSG_NEW);
1132     procmsg_msginfo_unset_flags(msginfo, MSG_UNREAD | MSG_NEW,0);
1133     filter_log_write(LOG_ACTION,"mark_as_read");
1134     XSRETURN_YES;
1135   case 7:
1136     MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_LOCKED);
1137     procmsg_msginfo_unset_flags(msginfo, MSG_LOCKED,0);
1138     filter_log_write(LOG_ACTION,"unlock");
1139     XSRETURN_YES;
1140   default:
1141     g_warning("Perl Plugin: Unknown argument to ClawsMail::C::unset_flag");
1142     XSRETURN_UNDEF;
1143   }
1144 }
1145
1146 /* ClawsMail::C::move(char*) */
1147 static XS(XS_ClawsMail_move)
1148 {
1149   gchar *targetfolder;
1150   gchar *logtext;
1151   FolderItem *dest_folder;
1152
1153   dXSARGS;
1154   if(items != 1) {
1155     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::move");
1156     XSRETURN_UNDEF;
1157   }
1158
1159   targetfolder = SvPV_nolen(ST(0));
1160   dest_folder = folder_find_item_from_identifier(targetfolder);
1161
1162   if (!dest_folder) {
1163     g_warning("Perl Plugin: move: folder not found '%s'",
1164       targetfolder ? targetfolder :"");
1165     XSRETURN_UNDEF;
1166   }
1167   if (folder_item_move_msg(dest_folder, msginfo) == -1) {
1168     g_warning("Perl Plugin: move:  could not move message");
1169     XSRETURN_UNDEF;
1170   }
1171   stop_filtering = TRUE;
1172   logtext = g_strconcat("move to ", targetfolder, NULL);
1173   filter_log_write(LOG_ACTION, logtext);
1174   g_free(logtext);
1175   XSRETURN_YES;
1176 }
1177
1178 /* ClawsMail::C::copy(char*) */
1179 static XS(XS_ClawsMail_copy)
1180 {
1181   char *targetfolder;
1182   gchar *logtext;
1183   FolderItem *dest_folder;
1184
1185   dXSARGS;
1186   if(items != 1) {
1187     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::copy");
1188     XSRETURN_UNDEF;
1189   }
1190   targetfolder = SvPV_nolen(ST(0));
1191   dest_folder = folder_find_item_from_identifier(targetfolder);
1192
1193   if (!dest_folder) {
1194     g_warning("Perl Plugin: copy: folder not found '%s'",
1195       targetfolder ? targetfolder :"");
1196     XSRETURN_UNDEF;
1197   }
1198   if (folder_item_copy_msg(dest_folder, msginfo) == -1) {
1199     g_warning("Perl Plugin: copy: could not copy message");
1200     XSRETURN_UNDEF;
1201   }
1202   logtext = g_strconcat("copy to ", targetfolder, NULL);
1203   filter_log_write(LOG_ACTION, logtext);
1204   g_free(logtext);
1205   XSRETURN_YES;
1206 }
1207
1208 /* ClawsMail::C::delete */
1209 static XS(XS_ClawsMail_delete)
1210 {
1211   dXSARGS;
1212   if(items != 0) {
1213     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::delete");
1214     XSRETURN_UNDEF;
1215   }
1216   folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
1217   stop_filtering = TRUE;
1218   filter_log_write(LOG_ACTION, "delete");
1219   XSRETURN_YES;
1220 }
1221
1222 /* ClawsMail::C::hide */
1223 static XS(XS_ClawsMail_hide)
1224 {
1225   dXSARGS;
1226   if(items != 0) {
1227     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::hide");
1228     XSRETURN_UNDEF;
1229   }
1230   msginfo->hidden = TRUE;
1231   filter_log_write(LOG_ACTION, "hide");
1232   XSRETURN_YES;
1233 }
1234
1235
1236 /* ClawsMail::C::color(int) */
1237 static XS(XS_ClawsMail_color)
1238 {
1239   int color;
1240   gchar *logtext;
1241
1242   dXSARGS;
1243   if(items != 1) {
1244     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::color");
1245     XSRETURN_UNDEF;
1246   }
1247   color = SvIV(ST(0));
1248   procmsg_msginfo_unset_flags(msginfo, MSG_CLABEL_FLAG_MASK, 0); 
1249   procmsg_msginfo_set_flags(msginfo, MSG_COLORLABEL_TO_FLAGS(color), 0);
1250   MSG_SET_COLORLABEL_VALUE(msginfo->flags,color);
1251
1252   logtext = g_strdup_printf("color: %d", color);
1253   filter_log_write(LOG_ACTION, logtext);
1254   g_free(logtext);
1255
1256   XSRETURN_YES;
1257 }
1258
1259 /* ClawsMail::C::change_score(int) */
1260 static XS(XS_ClawsMail_change_score)
1261 {
1262   int score;
1263   gchar *logtext;
1264
1265   dXSARGS;
1266   if(items != 1) {
1267     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::change_score");
1268     XSRETURN_UNDEF;
1269   }
1270   score = SvIV(ST(0));
1271   msginfo->score += score;
1272
1273   logtext = g_strdup_printf("change score: %+d", score);
1274   filter_log_write(LOG_ACTION, logtext);
1275   g_free(logtext);
1276
1277   XSRETURN_IV(msginfo->score);
1278 }
1279
1280 /* ClawsMail::C::set_score(int) */
1281 static XS(XS_ClawsMail_set_score)
1282 {
1283   int score;
1284   gchar *logtext;
1285
1286   dXSARGS;
1287   if(items != 1) {
1288     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::set_score");
1289     XSRETURN_UNDEF;
1290   }
1291   score = SvIV(ST(0));
1292   msginfo->score = score;
1293
1294   logtext = g_strdup_printf("set score: %d", score);
1295   filter_log_write(LOG_ACTION, logtext);
1296   g_free(logtext);
1297
1298   XSRETURN_IV(msginfo->score);
1299 }
1300
1301 /* ClawsMail::C::forward(int,int,char*) */
1302 static XS(XS_ClawsMail_forward)
1303 {
1304   int flag;
1305   /* flags:  1 forward
1306    *         2 forward as attachment
1307    */
1308   int account_id,val;
1309   char *dest;
1310   gchar *logtext;
1311   PrefsAccount *account;
1312   Compose *compose;
1313
1314   dXSARGS;
1315   if(items != 3) {
1316     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::forward");
1317     XSRETURN_UNDEF;
1318   }
1319
1320   flag = SvIV(ST(0));
1321   account_id = SvIV(ST(1));
1322   dest = SvPV_nolen(ST(2));
1323
1324   account = account_find_from_id(account_id);
1325   compose = compose_forward(account, msginfo,
1326           flag == 1 ? FALSE : TRUE,
1327           NULL, TRUE, TRUE);
1328   compose_entry_append(compose, dest,
1329            compose->account->protocol == A_NNTP ?
1330            COMPOSE_NEWSGROUPS : COMPOSE_TO, PREF_NONE);
1331
1332   val = compose_send(compose);
1333
1334   if(val == 0) {
1335
1336     logtext = g_strdup_printf("forward%s to %s",
1337             flag==2 ? " as attachment" : "",
1338             dest    ? dest : "<unknown destination>");
1339     filter_log_write(LOG_ACTION, logtext);
1340     g_free(logtext);
1341
1342     XSRETURN_YES;
1343   }
1344   else
1345     XSRETURN_UNDEF;
1346 }
1347
1348 /* ClawsMail::C::redirect(int,char*) */
1349 static XS(XS_ClawsMail_redirect)
1350 {
1351   int account_id,val;
1352   char *dest;
1353   gchar *logtext;
1354   PrefsAccount *account;
1355   Compose *compose;
1356
1357   dXSARGS;
1358   if(items != 2) {
1359     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::redirect");
1360     XSRETURN_UNDEF;
1361   }
1362
1363   account_id = SvIV(ST(0));
1364   dest = SvPV_nolen(ST(1));
1365
1366   account = account_find_from_id(account_id);
1367   compose = compose_redirect(account, msginfo, TRUE);
1368   
1369   if (compose->account->protocol == A_NNTP)
1370     XSRETURN_UNDEF;
1371   else
1372     compose_entry_append(compose, dest, COMPOSE_TO, PREF_NONE);
1373
1374   val = compose_send(compose);
1375   
1376   if(val == 0) {
1377     
1378     logtext = g_strdup_printf("redirect to %s",
1379             dest ? dest : "<unknown destination>");
1380     filter_log_write(LOG_ACTION, logtext);
1381     g_free(logtext);
1382
1383     XSRETURN_YES;
1384   }
1385   else
1386     XSRETURN_UNDEF;
1387 }
1388
1389
1390 /* Utilities */
1391
1392 /* ClawsMail::C::move_to_trash */
1393 static XS(XS_ClawsMail_move_to_trash)
1394 {
1395   FolderItem *dest_folder;
1396   
1397   dXSARGS;
1398   if(items != 0) {
1399     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::move_to_trash");
1400     XSRETURN_UNDEF;
1401   }
1402   dest_folder = folder_get_default_trash();
1403   if (!dest_folder) {
1404     g_warning("Perl Plugin: move_to_trash: Trash folder not found");
1405     XSRETURN_UNDEF;
1406   }
1407   if (folder_item_move_msg(dest_folder, msginfo) == -1) {
1408     g_warning("Perl Plugin: move_to_trash: could not move message to trash");
1409     XSRETURN_UNDEF;
1410   }
1411   stop_filtering = TRUE;
1412   filter_log_write(LOG_ACTION, "move_to_trash");
1413   XSRETURN_YES;
1414 }
1415
1416 /* ClawsMail::C::abort */
1417 static XS(XS_ClawsMail_abort)
1418 {
1419   FolderItem *inbox_folder;
1420
1421   dXSARGS;
1422   if(items != 0) {
1423     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::abort");
1424     XSRETURN_UNDEF;
1425   }
1426   if(!manual_filtering) {
1427     inbox_folder = folder_get_default_inbox();
1428     if (!inbox_folder) {
1429       g_warning("Perl Plugin: abort: Inbox folder not found");
1430       XSRETURN_UNDEF;
1431     }
1432     if (folder_item_move_msg(inbox_folder, msginfo) == -1) {
1433       g_warning("Perl Plugin: abort: Could not move message to default inbox");
1434       XSRETURN_UNDEF;
1435     }
1436     filter_log_write(LOG_ACTION, "abort -- message moved to default inbox");
1437   }
1438   else
1439     filter_log_write(LOG_ACTION, "abort");
1440
1441   stop_filtering = TRUE;
1442   XSRETURN_YES;
1443 }
1444
1445 /* ClawsMail::C::get_attribute_value(char*,char*[,char*]) */
1446 static XS(XS_ClawsMail_get_attribute_value)
1447 {
1448   char *addr;
1449   char *attr;
1450   char *attribute_value;
1451   char *bookname;
1452
1453   dXSARGS;
1454   if(items != 2 && items != 3) {
1455     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::get_attribute_value");
1456     XSRETURN_UNDEF;
1457   }
1458   addr = SvPV_nolen(ST(0));
1459   attr = SvPV_nolen(ST(1));
1460
1461   if(items == 2)
1462     attribute_value = get_attribute_value(addr,attr,NULL);
1463   else {
1464     bookname = SvPV_nolen(ST(2));
1465     attribute_value = get_attribute_value(addr,attr,bookname);
1466   }
1467
1468   if(attribute_value)
1469     XSRETURN_PV(attribute_value);
1470   XSRETURN_PV("");
1471 }
1472
1473 /* ClawsMail::C::filter_log(char*,char*) */
1474 static XS(XS_ClawsMail_filter_log)
1475 {
1476   char *text;
1477   char *type;
1478   
1479   dXSARGS;
1480   if(items != 2) {
1481     g_warning("Perl Plugin: Wrong number of arguments to ClawsMail::C::filter_log");
1482     XSRETURN_UNDEF;
1483   }
1484   type = SvPV_nolen(ST(0));
1485   text = SvPV_nolen(ST(1));
1486   if(!strcmp(type, "LOG_ACTION"))
1487     filter_log_write(LOG_ACTION, text);
1488   else if(!strcmp(type, "LOG_MANUAL"))
1489     filter_log_write(LOG_MANUAL, text);
1490   else if(!strcmp(type, "LOG_MATCH"))
1491     filter_log_write(LOG_MATCH, text);
1492   else {
1493     g_warning("Perl Plugin: ClawsMail::C::filter_log -- wrong first argument");
1494     XSRETURN_UNDEF;
1495   }  
1496   XSRETURN_YES;
1497 }
1498
1499 /* ClawsMail::C::filter_log_verbosity(int) */
1500 static XS(XS_ClawsMail_filter_log_verbosity)
1501 {
1502   int retval;
1503
1504   dXSARGS;
1505   if(items != 1 && items != 0) {
1506     g_warning("Perl Plugin: Wrong number of arguments to "
1507     "ClawsMail::C::filter_log_verbosity");
1508     XSRETURN_UNDEF;
1509   }
1510   retval = filter_log_verbosity;
1511
1512   if(items == 1)
1513     filter_log_verbosity = SvIV(ST(0));
1514
1515   XSRETURN_IV(retval);
1516 }
1517
1518 /* register extensions */ 
1519 EXTERN_C void xs_init(pTHX)
1520 {
1521   char *file = __FILE__;
1522   dXSUB_SYS;
1523   newXS("DynaLoader::boot_DynaLoader",    boot_DynaLoader,               file);
1524   newXS("ClawsMail::C::filter_init",  XS_ClawsMail_filter_init,  "ClawsMail::C");
1525   newXS("ClawsMail::C::check_flag",   XS_ClawsMail_check_flag,   "ClawsMail::C");
1526   newXS("ClawsMail::C::age_greater",  XS_ClawsMail_age_greater,  "ClawsMail::C");
1527   newXS("ClawsMail::C::age_lower",    XS_ClawsMail_age_lower,    "ClawsMail::C");
1528   newXS("ClawsMail::C::tagged",       XS_ClawsMail_tagged,       "ClawsMail::C");
1529   newXS("ClawsMail::C::set_flag",     XS_ClawsMail_set_flag,     "ClawsMail::C");
1530   newXS("ClawsMail::C::unset_flag",   XS_ClawsMail_unset_flag,   "ClawsMail::C");
1531   newXS("ClawsMail::C::delete",       XS_ClawsMail_delete,       "ClawsMail::C");
1532   newXS("ClawsMail::C::move",         XS_ClawsMail_move,         "ClawsMail::C");
1533   newXS("ClawsMail::C::copy",         XS_ClawsMail_copy,         "ClawsMail::C");
1534   newXS("ClawsMail::C::color",        XS_ClawsMail_color,        "ClawsMail::C");
1535   newXS("ClawsMail::C::colorlabel",   XS_ClawsMail_colorlabel,   "ClawsMail::C");
1536   newXS("ClawsMail::C::change_score", XS_ClawsMail_change_score, "ClawsMail::C");
1537   newXS("ClawsMail::C::set_score",    XS_ClawsMail_set_score,    "ClawsMail::C");
1538   newXS("ClawsMail::C::hide",         XS_ClawsMail_hide,         "ClawsMail::C");
1539   newXS("ClawsMail::C::forward",      XS_ClawsMail_forward,      "ClawsMail::C");
1540   newXS("ClawsMail::C::redirect",     XS_ClawsMail_redirect,     "ClawsMail::C");
1541   newXS("ClawsMail::C::set_tag",      XS_ClawsMail_set_tag,      "ClawsMail::C");
1542   newXS("ClawsMail::C::unset_tag",    XS_ClawsMail_unset_tag,    "ClawsMail::C");
1543   newXS("ClawsMail::C::clear_tags",   XS_ClawsMail_clear_tags,   "ClawsMail::C");
1544   newXS("ClawsMail::C::make_sure_folder_exists",
1545   XS_ClawsMail_make_sure_folder_exists,"ClawsMail::C");
1546   newXS("ClawsMail::C::make_sure_tag_exists", XS_ClawsMail_make_sure_tag_exists,"ClawsMail::C");
1547   newXS("ClawsMail::C::get_tags", XS_ClawsMail_get_tags,"ClawsMail::C");
1548   newXS("ClawsMail::C::addr_in_addressbook",
1549   XS_ClawsMail_addr_in_addressbook,"ClawsMail::C");
1550   newXS("ClawsMail::C::open_mail_file",
1551   XS_ClawsMail_open_mail_file,"ClawsMail::C");
1552   newXS("ClawsMail::C::close_mail_file",
1553   XS_ClawsMail_close_mail_file,"ClawsMail::C");
1554   newXS("ClawsMail::C::get_next_header",
1555   XS_ClawsMail_get_next_header,"ClawsMail::C");
1556   newXS("ClawsMail::C::get_next_body_line",
1557   XS_ClawsMail_get_next_body_line,"ClawsMail::C");
1558   newXS("ClawsMail::C::move_to_trash",XS_ClawsMail_move_to_trash,"ClawsMail::C");
1559   newXS("ClawsMail::C::abort",        XS_ClawsMail_abort,        "ClawsMail::C");
1560   newXS("ClawsMail::C::get_attribute_value",
1561   XS_ClawsMail_get_attribute_value,"ClawsMail::C");
1562   newXS("ClawsMail::C::filter_log",   XS_ClawsMail_filter_log,   "ClawsMail::C");
1563   newXS("ClawsMail::C::filter_log_verbosity",
1564   XS_ClawsMail_filter_log_verbosity, "ClawsMail::C");
1565 }
1566
1567 /*
1568  * The workhorse.
1569  * Returns: 0 on success
1570  *          1 error in scriptfile or invocation of external
1571  *            editor              -> retry
1572  *          2 error in scriptfile -> abort
1573  * (Yes, I know..)
1574  */
1575 static int perl_load_file(void)
1576 {
1577   gchar *args[] = {"", DO_CLEAN, NULL};
1578   gchar *noargs[] = { NULL };
1579   gchar *perlfilter;
1580   gchar **cmdline;
1581   gchar buf[1024];
1582   gchar *pp;
1583   STRLEN n_a;
1584
1585   call_argv("ClawsMail::Filter::Matcher::filter_init_",
1586       G_DISCARD | G_EVAL | G_NOARGS,noargs);
1587   /* check $@ */
1588   if(SvTRUE(ERRSV)) {
1589     debug_print("%s", SvPV(ERRSV,n_a));
1590     return 1; 
1591   }
1592   perlfilter = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PERLFILTER, NULL);
1593   args[0] = perlfilter;
1594   call_argv("ClawsMail::Persistent::eval_file",
1595       G_DISCARD | G_EVAL, args);
1596   g_free(perlfilter);
1597   if(SvTRUE(ERRSV)) {
1598     AlertValue val;
1599     gchar *message;
1600
1601     if(strstr(SvPV(ERRSV,n_a),"intended"))
1602       return 0;
1603
1604     debug_print("%s", SvPV(ERRSV,n_a));
1605     message = g_strdup_printf("Error processing Perl script file: "
1606             "(line numbers may not be valid)\n%s",
1607             SvPV(ERRSV,n_a));
1608     val = alertpanel("Perl Plugin error",message,"Retry","Abort","Edit");
1609     g_free(message);
1610
1611     if(val == G_ALERTOTHER) {
1612       /* Open PERLFILTER in an external editor */
1613       perlfilter = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PERLFILTER, NULL);
1614       if (prefs_common_get_ext_editor_cmd() &&
1615     (pp = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
1616     *(pp + 1) == 's' && !strchr(pp + 2, '%')) {
1617   g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), perlfilter);
1618       }
1619       else {
1620   if (prefs_common_get_ext_editor_cmd())
1621     g_warning("Perl Plugin: External editor command-line is invalid: `%s'",
1622         prefs_common_get_ext_editor_cmd());
1623   g_snprintf(buf, sizeof(buf), "emacs %s", perlfilter);
1624       }
1625       g_free(perlfilter);
1626       cmdline = strsplit_with_quote(buf, " ", 1024);
1627       execute_detached(cmdline);
1628       g_strfreev(cmdline);
1629       return 1;
1630     }
1631     else if(val == G_ALERTDEFAULT)
1632       return 1;
1633     else
1634       return 2;
1635   }
1636
1637   return 0;
1638 }
1639
1640
1641 /* let there be magic */
1642 static int perl_init(void)
1643 {
1644   int exitstatus;
1645   char *initialize[] = { "", "-w", "-e", "1;"};
1646   /* The `persistent' module is taken from the Perl documentation
1647      and has only slightly been modified. */
1648   const char perl_persistent[] = {
1649 "package ClawsMail::Persistent;\n"
1650 "\n"
1651 "use strict;\n"
1652 "our %Cache;\n"
1653 "use Symbol qw(delete_package);\n"
1654 "\n"
1655 "sub valid_package_name {\n"
1656 "    my($string) = @_;\n"
1657 "    $string =~ s/([^A-Za-z0-9\\/])/sprintf(\"_%2x\",unpack(\"C\",$1))/eg;\n"
1658 "    # second pass only for words starting with a digit\n"
1659 "    $string =~ s|/(\\d)|sprintf(\"/_%2x\",unpack(\"C\",$1))|eg;\n"
1660 "    \n"
1661 "    # Dress it up as a real package name\n"
1662 "    $string =~ s|/|::|g;\n"
1663 "    return \"ClawsMail\" . $string;\n"
1664 "}\n"
1665 "\n"
1666 "sub eval_file {\n"
1667 "    my($file, $delete) = @_;\n"
1668 "    my $package = valid_package_name($file);\n"
1669 "    my $mtime = -M $file;\n"
1670 "    if(!(defined $Cache{$package}{mtime} &&\n"
1671 "   $Cache{$package}{mtime} <= $mtime)) {\n"
1672 "      delete_package($package) if defined $Cache{$package}{mtime};\n"
1673 "  local *FH;\n"
1674 "  open FH, $file or die \"Failed to open '$file': $!\";\n"
1675 "  local($/) = undef;\n"
1676 "  my $sub = <FH>;\n"
1677 "  close FH;\n"
1678 "  #wrap the code into a subroutine inside our unique package\n"
1679 "  my $eval = qq{package $package;\n"
1680 "          use ClawsMail::Filter::Matcher;\n"
1681 "          use ClawsMail::Filter::Action;\n"
1682 "          use ClawsMail::Utils;\n"
1683 "          sub handler { $sub; }};\n"
1684 "  {\n"
1685 "      # hide our variables within this block\n"
1686 "      my($file,$mtime,$package,$sub);\n"
1687 "      eval $eval;\n"
1688 "  }\n"
1689 "  die $@ if $@;\n"
1690 "  #cache it unless we're cleaning out each time\n"
1691 "  $Cache{$package}{mtime} = $mtime unless $delete;\n"
1692 "    }\n"
1693 "    eval {$package->handler;};\n"
1694 "    die $@ if $@;\n"
1695 "    delete_package($package) if $delete;\n"
1696 "}\n"
1697   };
1698   const char perl_filter_matcher[] = {
1699 "BEGIN {$INC{'ClawsMail/Filter/Matcher.pm'} = 1;}\n"
1700 "package ClawsMail::Filter::Matcher;\n"
1701 "use locale;\n"
1702 "use base qw(Exporter);\n"
1703 "use strict;\n"
1704 "our @EXPORT =   (qw(header body filepath manual),\n"
1705 "     qw(filter_log_verbosity filter_log),\n"
1706 "     qw(all marked unread deleted new replied),\n"
1707 "     qw(forwarded locked colorlabel match matchcase),\n"
1708 "     qw(regexp regexpcase test),\n"
1709 "     qw(to cc subject from to_or_cc newsgroups inreplyto),\n"
1710 "     qw(references body_part headers_part message),\n"
1711 "     qw(size_greater size_smaller size_equal),\n"
1712 "     qw(score_greater score_lower score_equal),\n"
1713 "     qw(age_greater age_lower partial tagged $permanent));\n"
1714 "# Global Variables\n"
1715 "our(%header,$body,%msginfo,$mail_done,$manual);\n"
1716 "our %colors = ('none'     =>  0,'orange'   =>  1,'red'  =>  2,\n"
1717 "            'pink'     =>  3,'sky blue' =>  4,'blue' =>  5,\n"
1718 "             'green'    =>  6,'brown'    =>  7);\n"
1719 "# For convenience\n"
1720 "sub lc2_ {\n"
1721 "    my $arg = shift;\n"
1722 "    if(defined $arg) {\n"
1723 "        return lc $arg;\n"
1724 "    }\n"
1725 "    else {\n"
1726 "        return \"\";\n"
1727 "    }\n"
1728 "}\n"
1729 "sub to           { return \"to\";            }\n"
1730 "sub cc           { return \"cc\";            }\n"
1731 "sub from         { return \"from\";          }\n"
1732 "sub subject      { return \"subject\";       }\n"
1733 "sub to_or_cc     { return \"to_or_cc\";      }\n"
1734 "sub newsgroups   { return \"newsgroups\";    }\n"
1735 "sub inreplyto    { return \"in-reply-to\";   }\n"
1736 "sub references   { return \"references\";    }\n"
1737 "sub body_part    { return \"body_part\";     }\n"
1738 "sub headers_part { return \"headers_part\";  }\n"
1739 "sub message      { return \"message\";       }\n"
1740 "# access the mail directly\n"
1741 "sub header {\n"
1742 "    my $key = shift;\n"
1743 "    if(not defined $key) {\n"
1744 "  init_();\n"
1745 "  return keys %header;\n"
1746 "    }\n"
1747 "    $key = lc2_ $key; $key =~ s/:$//;\n"
1748 "    init_() unless exists $header{$key};\n"
1749 "    if(exists $header{$key}) {\n"
1750 "  wantarray ? return @{$header{$key}} : return $header{$key}->[-1];\n"
1751 "    }\n"
1752 "    return undef;\n"
1753 "}\n"
1754 "sub body {init_();return $body;}\n"
1755 "sub filepath {return $msginfo{\"filepath\"};}\n"
1756 "sub manual {\n"
1757 "    ClawsMail::C::filter_log(\"LOG_MATCH\",\"manual\") if $manual;\n"
1758 "    return $manual;\n"
1759 "}\n"
1760 "sub filter_log {\n"
1761 "    my $arg1 = shift;\n"
1762 "    my $arg2 = shift;\n"
1763 "    return ClawsMail::C::filter_log($arg1,$arg2)\n"
1764 "  if defined($arg2);\n"
1765 "    return ClawsMail::C::filter_log(\"LOG_MANUAL\",$arg1);\n"
1766 "}\n"
1767 "sub filter_log_verbosity {\n"
1768 "    $_ = shift;\n"
1769 "    return ClawsMail::C::filter_log_verbosity($_)\n"
1770 "  if defined($_);\n"
1771 "    return ClawsMail::C::filter_log_verbosity();\n"
1772 "}\n"
1773 "# Public Matcher Tests\n"
1774 "sub all { ClawsMail::C::filter_log(\"LOG_MATCH\",\"all\");return 1; }\n"
1775 "sub marked        { return ClawsMail::C::check_flag(1);}\n"
1776 "sub unread        { return ClawsMail::C::check_flag(2);}\n"
1777 "sub deleted       { return ClawsMail::C::check_flag(3);}\n"
1778 "sub new           { return ClawsMail::C::check_flag(4);}\n"
1779 "sub replied       { return ClawsMail::C::check_flag(5);}\n"
1780 "sub forwarded     { return ClawsMail::C::check_flag(6);}\n"
1781 "sub locked        { return ClawsMail::C::check_flag(7);}\n"
1782 "sub ignore_thread { return ClawsMail::C::check_flag(8);}\n"
1783 "sub age_greater  {return ClawsMail::C::age_greater(@_);}\n"
1784 "sub age_lower    {return ClawsMail::C::age_lower(@_);  }\n"
1785 "sub tagged       {return ClawsMail::C::tagged(@_);  }\n"
1786 "sub score_equal {\n"
1787 "    my $my_score = shift;\n"
1788 "    return 0 unless (defined($msginfo{\"score\"}) and defined($my_score));\n"
1789 "    if($my_score == $msginfo{\"score\"}) {\n"
1790 "  ClawsMail::C::filter_log(\"LOG_MATCH\",\"score_equal\");\n"
1791 "  return 1;\n"
1792 "    }else{return 0;}\n"
1793 "}\n"
1794 "sub score_greater {\n"
1795 "    my $my_score = shift;\n"
1796 "    return 0 unless (defined($msginfo{\"score\"}) and defined($my_score));\n"
1797 "    if($msginfo{\"score\"} > $my_score) {\n"
1798 "  ClawsMail::C::filter_log(\"LOG_MATCH\",\"score_greater\");\n"
1799 "  return 1;\n"
1800 "    }else{return 0;}\n"
1801 "}\n"
1802 "sub score_lower {\n"
1803 "    my $my_score = shift;\n"
1804 "    return 0 unless (defined($msginfo{\"score\"}) and defined($my_score));\n"
1805 "    if($msginfo{\"score\"} < $my_score) {\n"
1806 "  ClawsMail::C::filter_log(\"LOG_MATCH\",\"score_lower\");\n"
1807 "  return 1;\n"
1808 "    }else{return 0;}\n"
1809 "}\n"
1810 "sub colorlabel {\n"
1811 "    my $color = shift;\n"
1812 "    $color = lc2_ $color;\n"
1813 "    $color = $colors{$color} if exists $colors{$color};\n"
1814 "    $color = 0 if $color =~ m/\\D/;\n"
1815 "    return ClawsMail::C::colorlabel($color);\n"
1816 "}\n"
1817 "sub size_greater {\n"
1818 "    my $my_size = shift;\n"
1819 "    return 0 unless (defined($msginfo{\"size\"}) and defined($my_size));\n"
1820 "    if($msginfo{\"size\"} > $my_size) {\n"
1821 "  ClawsMail::C::filter_log(\"LOG_MATCH\",\"size_greater\");\n"
1822 "  return 1;\n"
1823 "    }else{return 0;}\n"
1824 "}\n"
1825 "sub size_smaller {\n"
1826 "    my $my_size = shift;\n"
1827 "    return 0 unless (defined($msginfo{\"size\"}) and defined($my_size));\n"
1828 "    if($msginfo{\"size\"} < $my_size) {\n"
1829 "  ClawsMail::C::filter_log(\"LOG_MATCH\",\"size_smaller\");\n"
1830 "  return 1;\n"
1831 "    }else{return 0;}\n"
1832 "}\n"
1833 "sub size_equal {\n"
1834 "    my $my_size = shift;\n"
1835 "    return 0 unless (defined($msginfo{\"size\"}) and defined($my_size));\n"
1836 "    if($msginfo{\"size\"} == $my_size) {\n"
1837 "  ClawsMail::C::filter_log(\"LOG_MATCH\",\"size_equal\");\n"
1838 "  return 1;\n"
1839 "    }else{return 0;}\n"
1840 "}\n"
1841 "sub partial {\n"
1842 "    return 0 unless defined($msginfo{\"total_size\"})\n"
1843 "  and defined($msginfo{\"size\"});\n"
1844 "    if($msginfo{\"total_size\"} != 0\n"
1845 "       && $msginfo{\"size\"} != $msginfo{\"total_size\"}) {\n"
1846 "  ClawsMail::C::filter_log(\"LOG_MATCH\",\"partial\");\n"
1847 "  return 1;\n"
1848 "    }else{return 0;}\n"
1849 "}\n"
1850 "sub test {\n"
1851 "   $_ = shift; my $command = \"\"; my $hl=\"\"; my $re=\"\"; my $retval;\n"
1852 "   my $cmdline = $_;\n"
1853 "   s/\\\"/\"/g; #fool stupid emacs perl mode\";\n"
1854 "   s/([^%]*)//; $command .= $1;\n"
1855 "   while($_) {\n"
1856 "       if   (/^%%/){s/^%%([^%]*)//;$command .= \"\\\\%\".$1; next;}\n"
1857 "       elsif(/^%s/){s/^%s([^%]*)//;$hl=header(\"subject\");$re=$1;}\n"
1858 "       elsif(/^%f/){s/^%f([^%]*)//;$hl=header(\"from\");$re=$1;}\n"
1859 "       elsif(/^%t/){s/^%t([^%]*)//;$hl=header(\"to\");$re=$1;}\n"
1860 "       elsif(/^%c/){s/^%c([^%]*)//;$hl=header(\"cc\");$re=$1;}\n"
1861 "       elsif(/^%d/){s/^%d([^%]*)//;$hl=header(\"date\");$re=$1;}\n"
1862 "       elsif(/^%i/){s/^%i([^%]*)//;$hl=header(\"message-id\");$re=$1;}\n"
1863 "       elsif(/^%n/){s/^%n([^%]*)//;$hl=header(\"newsgroups\");$re=$1;}\n"
1864 "       elsif(/^%r/){s/^%r([^%]*)//;$hl=header(\"references\");$re=$1;}\n"
1865 "       elsif(/^%F/){s/^%F([^%]*)//;$hl=filepath();$re=$1;}\n"
1866 "       else        {s/^(%[^%]*)//; $command .= $1;}\n"
1867 "       $command .= \"\\Q$hl\\E\" if defined $hl;$hl=\"\";\n"
1868 "       $command .= $re;$re=\"\";\n"
1869 "   }\n"
1870 "   $retval = !(system($command)>>8);\n"
1871 "   ClawsMail::C::filter_log(\"LOG_MATCH\",\"test: $cmdline\")\n"
1872 "       if $retval;\n"
1873 "   return $retval;\n"
1874 "}\n"
1875 "sub matchcase {\n"
1876 "    my $retval;\n"
1877 "    $retval = match_(@_,\"i\");\n"
1878 "    ClawsMail::C::filter_log(\"LOG_MATCH\",\"matchcase: $_[0], $_[1]\")\n"
1879 "  if $retval;\n"
1880 "    return $retval;\n"
1881 "}\n"
1882 "sub match {\n"
1883 "    my $retval;\n"
1884 "    $retval = match_(@_);\n"
1885 "    ClawsMail::C::filter_log(\"LOG_MATCH\",\"match: $_[0], $_[1]\")\n"
1886 "  if $retval;\n"
1887 "    return $retval;\n"
1888 "}\n"
1889 "sub regexpcase {\n"
1890 "    my $retval;\n"
1891 "    $retval = match_(@_,\"ri\");\n"
1892 "    ClawsMail::C::filter_log(\"LOG_MATCH\",\"regexpcase: $_[0], $_[1]\")\n"
1893 "  if $retval;\n"
1894 "    return $retval;\n"
1895 "}\n"
1896 "sub regexp {\n"
1897 "    my $retval;\n"
1898 "    $retval = match_(@_,\"r\");\n"
1899 "    ClawsMail::C::filter_log(\"LOG_MATCH\",\"regexp: $_[0], $_[1]\")\n"
1900 "  if $retval;\n"
1901 "    return $retval;\n"
1902 "}\n"
1903 "# Internals\n"
1904 "sub add_header_entries_ {\n"
1905 "    my($key,@values) = @_; $key = lc2_ $key; $key =~ s/:$//;\n"
1906 "    $header{$key} = [] unless exists $header{$key};\n"
1907 "    push @{$header{$key}},@values;\n"
1908 "}\n"
1909 "# read whole mail\n"
1910 "sub init_ {\n"
1911 "    return 0 if $mail_done;\n"
1912 "    ClawsMail::C::open_mail_file();\n"
1913 "    read_headers_();\n"
1914 "    read_body_();\n"
1915 "    ClawsMail::C::close_mail_file();\n"
1916 "    $mail_done = 1;\n"
1917 "}\n"
1918 "sub filter_init_ {\n"
1919 "    %header = (); %msginfo = (); undef $body; $mail_done = 0;\n"
1920 "    $manual                        = ClawsMail::C::filter_init(100);\n"
1921 "    $msginfo{\"size\"}               = ClawsMail::C::filter_init( 1) ;\n"
1922 "    add_header_entries_(\"date\",      ClawsMail::C::filter_init( 2));\n"
1923 "    add_header_entries_(\"from\",      ClawsMail::C::filter_init( 3));\n"
1924 "    add_header_entries_(\"to\",        ClawsMail::C::filter_init( 4));\n"
1925 "    add_header_entries_(\"cc\",        ClawsMail::C::filter_init( 5));\n"
1926 "    add_header_entries_(\"newsgroups\",ClawsMail::C::filter_init( 6));\n"
1927 "    add_header_entries_(\"subject\",   ClawsMail::C::filter_init( 7));\n"
1928 "    add_header_entries_(\"msgid\",     ClawsMail::C::filter_init( 8));\n"
1929 "    add_header_entries_(\"inreplyto\", ClawsMail::C::filter_init( 9));\n"
1930 "    add_header_entries_(\"xref\",      ClawsMail::C::filter_init(10));\n"
1931 "    add_header_entries_(\"xface\",     ClawsMail::C::filter_init(11));\n"
1932 "    add_header_entries_(\"dispositionnotificationto\",\n"
1933 "                   ClawsMail::C::filter_init(12));\n"
1934 "    add_header_entries_(\"returnreceiptto\",\n"
1935 "                   ClawsMail::C::filter_init(13));\n"
1936 "    add_header_entries_(\"references\",ClawsMail::C::filter_init(14));\n"
1937 "    $msginfo{\"score\"}              = ClawsMail::C::filter_init(15);\n"
1938 "    $msginfo{\"plaintext_file\"}     = ClawsMail::C::filter_init(17);\n"
1939 "    $msginfo{\"hidden\"}             = ClawsMail::C::filter_init(19);\n"
1940 "    $msginfo{\"filepath\"}           = ClawsMail::C::filter_init(20);\n"
1941 "    $msginfo{\"partial_recv\"}       = ClawsMail::C::filter_init(21);\n"
1942 "    $msginfo{\"total_size\"}         = ClawsMail::C::filter_init(22);\n"
1943 "    $msginfo{\"account_server\"}     = ClawsMail::C::filter_init(23);\n"
1944 "    $msginfo{\"account_login\"}      = ClawsMail::C::filter_init(24);\n"
1945 "    $msginfo{\"planned_download\"}   = ClawsMail::C::filter_init(25);\n"
1946 "} \n"
1947 "sub read_headers_ {\n"
1948 "    my($key,$value);\n"
1949 "    %header = ();\n"
1950 "    while(($key,$value) = ClawsMail::C::get_next_header()) {\n"
1951 "  next unless $key =~ /:$/;\n"
1952 "  add_header_entries_($key,$value);\n"
1953 "    }\n"
1954 "}\n"
1955 "sub read_body_ {\n"
1956 "    my $line;\n"
1957 "    while(defined($line = ClawsMail::C::get_next_body_line())) {\n"
1958 "  $body .= $line;\n"
1959 "    }    \n"
1960 "}\n"
1961 "sub match_ {\n"
1962 "  my ($where,$what,$modi) = @_; $modi ||= \"\";\n"
1963 "  my $nocase=\"\"; $nocase = \"1\" if (index($modi,\"i\") != -1);\n"
1964 "  my $regexp=\"\"; $regexp = \"1\" if (index($modi,\"r\") != -1);\n"
1965 "  if($where eq \"to_or_cc\") {\n"
1966 "    if(not $regexp) { \n"
1967 "      return ((index(header(\"to\"),$what) != -1) or\n"
1968 "        (index(header(\"cc\"),$what) != -1)) unless $nocase;\n"
1969 "      return ((index(lc2_(header(\"to\")),lc2_($what)) != -1) or\n"
1970 "        (index(lc2_(header(\"cc\")),lc2_($what)) != -1))\n"
1971 "    } else {\n"
1972 "      return ((header(\"to\") =~ m/$what/) or\n"
1973 "        (header(\"cc\") =~ m/$what/)) unless $nocase;\n"
1974 "      return ((header(\"to\") =~ m/$what/i) or\n"
1975 "        (header(\"cc\") =~ m/$what/i));\n"
1976 "    }\n"
1977 "  } elsif($where eq \"body_part\") {\n"
1978 "    my $mybody = body(); $mybody =~ s/\\s+/ /g;\n"
1979 "    if(not $regexp) {\n"
1980 "      return (index($mybody,$what) != -1) unless $nocase;\n"
1981 "      return (index(lc2_($mybody),lc2_($what)) != -1);\n"
1982 "    } else {\n"
1983 "      return ($body =~ m/$what/) unless $nocase;\n"
1984 "      return ($body =~ m/$what/i);\n"
1985 "    }\n"
1986 "  } elsif($where eq \"headers_part\") {\n"
1987 "    my $myheader = header_as_string_();\n"
1988 "    if(not $regexp) {\n"
1989 "      $myheader =~ s/\\s+/ /g;\n"
1990 "      return (index($myheader,$what) != -1) unless $nocase;\n"
1991 "      return (index(lc2_($myheader),lc2_($what)) != -1);\n"
1992 "    } else {\n"
1993 "      return ($myheader =~ m/$what/) unless $nocase;\n"
1994 "      return ($myheader =~ m/$what/i);\n"
1995 "    }\n"
1996 "  } elsif($where eq \"message\") {\n"
1997 "    my $message = header_as_string_();\n"
1998 "    $message .= \"\\n\".body();\n"
1999 "    if(not $regexp) {\n"
2000 "      $message =~ s/\\s+/ /g;\n"
2001 "      return (index($message,$what) != -1) unless $nocase;\n"
2002 "      return (index(lc2_($message),lc2_($what)) != -1);\n"
2003 "    } else {\n"
2004 "      return ($message =~ m/$what/) unless $nocase;\n"
2005 "      return ($message =~ m/$what/i);\n"
2006 "    }\n"
2007 "  } elsif($where eq \"tag\") {\n"
2008 "    my $found = 0;\n"
2009 "    sub ClawsMail::Utils::get_tags;"
2010 "    foreach my $tag (ClawsMail::Utils::get_tags) {\n"
2011 "      if(not $regexp) {\n"
2012 "        if($nocase) {\n"
2013 "          $found = (index(lc2_($tag),lc2_($what)) != -1);\n"
2014 "        } else {\n"
2015 "          $found = (index($tag,$what) != -1);\n"
2016 "        }\n"
2017 "      } else {\n"
2018 "        if ($nocase) {\n"
2019 "          $found = ($tag =~ m/$what/i);\n"
2020 "        } else {\n"
2021 "          $found = ($tag =~ m/$what/);\n"
2022 "        }\n"
2023 "      }\n"
2024 "      last if $found;\n"
2025 "    }\n"
2026 "    return $found;"
2027 "  } else {\n"
2028 "    $where = lc2_ $where;\n"
2029 "    my $myheader = header(lc2_ $where); $myheader ||= \"\";\n"
2030 "    return 0 unless $myheader;\n"
2031 "    if(not $regexp) {    \n"
2032 "      return (index(header($where),$what) != -1) unless $nocase;\n"
2033 "      return (index(lc2_(header($where)),lc2_($what)) != -1);\n"
2034 "    } else {\n"
2035 "      return (header($where) =~ m/$what/) unless $nocase;\n"
2036 "      return (header($where) =~ m/$what/i);\n"
2037 "    } \n"
2038 "  }\n"
2039 "}\n"
2040 "sub header_as_string_ {\n"
2041 "    my $headerstring=\"\";\n"
2042 "    my @headerkeys = header(); my(@fields,$field);\n"
2043 "    foreach $field (@headerkeys) {\n"
2044 "  @fields = header($field);\n"
2045 "  foreach (@fields) {\n"
2046 "      $headerstring .= $field.\": \".$_.\"\\n\";\n"
2047 "  }\n"
2048 "    }\n"
2049 "    return $headerstring;\n"
2050 "}\n"
2051 "our $permanent = \"\";\n"
2052 "1;\n"
2053   };
2054   const char perl_filter_action[] = {
2055 "BEGIN {$INC{'ClawsMail/Filter/Action.pm'} = 1;}\n"
2056 "package ClawsMail::Filter::Action;\n"
2057 "use base qw(Exporter);\n"
2058 "our @EXPORT = (qw(mark unmark dele mark_as_unread mark_as_read),\n"
2059 "         qw(lock unlock move copy color execute),\n"
2060 "         qw(hide set_score change_score stop exit),\n"
2061 "         qw(forward forward_as_attachment redirect),\n"
2062 "        qw(set_tag unset_tag clear_tags),\n"
2063 "         );\n"
2064 "our %colors = ('none'     =>  0,'orange' =>  1,\n"
2065 "             'red'      =>  2,'pink'   =>  3,\n"
2066 "             'sky blue' =>  4,'blue'   =>  5,\n"
2067 "             'green'    =>  6,'brown'  =>  7);\n"
2068 "sub mark           { ClawsMail::C::set_flag  (1);}\n"
2069 "sub unmark         { ClawsMail::C::unset_flag(1);}\n"
2070 "sub mark_as_unread { ClawsMail::C::set_flag  (2);}\n"
2071 "sub mark_as_read   { ClawsMail::C::unset_flag(2);}\n"
2072 "sub lock           { ClawsMail::C::set_flag  (7);}\n"
2073 "sub unlock         { ClawsMail::C::unset_flag(7);}\n"
2074 "sub copy           { ClawsMail::C::copy     (@_);}\n"
2075 "sub forward        { ClawsMail::C::forward(1,@_);}\n"
2076 "sub forward_as_attachment {ClawsMail::C::forward(2,@_);}\n"
2077 "sub redirect       { ClawsMail::C::redirect(@_); }\n"
2078 "sub hide           { ClawsMail::C::hide();       }\n"
2079 "sub exit {\n"
2080 "    ClawsMail::C::filter_log(\"LOG_ACTION\",\"exit\");\n"
2081 "    stop(1);\n"
2082 "}\n"
2083 "sub stop {\n"
2084 "    my $nolog = shift;\n"
2085 "    ClawsMail::C::filter_log(\"LOG_ACTION\", \"stop\")\n"
2086 "  unless defined($nolog);\n"
2087 "    die 'intended';\n"
2088 "}\n"
2089 "sub set_score {\n"
2090 "    $ClawsMail::Filter::Matcher::msginfo{\"score\"} =\n"
2091 "  ClawsMail::C::set_score(@_);\n"
2092 "}\n"
2093 "sub change_score {\n"
2094 "    $ClawsMail::Filter::Matcher::msginfo{\"score\"} =\n"
2095 "  ClawsMail::C::change_score(@_);\n"
2096 "}\n"
2097 "sub execute {\n"
2098 "    my $flv; my $cmd = shift; return 0 unless defined($cmd);\n"
2099 "    $flv = ClawsMail::C::filter_log_verbosity(0);\n"
2100 "    ClawsMail::Filter::Matcher::test($cmd);\n"
2101 "    ClawsMail::C::filter_log_verbosity($flv);\n"
2102 "    ClawsMail::C::filter_log(\"LOG_ACTION\", \"execute: $cmd\");\n"
2103 "    1;\n"
2104 "}\n"
2105 "sub move { ClawsMail::C::move(@_); stop(1);}\n"
2106 "sub dele { ClawsMail::C::delete(); stop(1);}\n"
2107 "sub color {\n"
2108 "    ($color) = @_;$color = lc2_ $color;\n"
2109 "    $color = $colors{$color} if exists $colors{$color};\n"
2110 "    $color = 0 if $color =~ m/\\D/;\n"
2111 "    ClawsMail::C::color($color);\n"
2112 "}\n"
2113 "sub set_tag { ClawsMail::C::set_tag(@_);}\n"
2114 "sub unset_tag { ClawsMail::C::unset_tag(@_);}\n"
2115 "sub clear_tags { ClawsMail::C::clear_tags(@_);}\n"
2116 "1;\n"
2117   };
2118   const char perl_utils[] = {
2119 "BEGIN {$INC{'ClawsMail/Utils.pm'} = 1;}\n"
2120 "package ClawsMail::Utils;\n"
2121 "use base qw(Exporter);\n"
2122 "our @EXPORT = (\n"
2123 "             qw(SA_is_spam extract_addresses move_to_trash abort),\n"
2124 "             qw(addr_in_addressbook from_in_addressbook),\n"
2125 "             qw(get_attribute_value make_sure_folder_exists),\n"
2126 "            qw(make_sure_tag_exists get_tags),\n"
2127 "             );\n"
2128 "# Spam\n"
2129 "sub SA_is_spam {\n"
2130 "    my $retval;\n"
2131 "    $retval = not ClawsMail::Filter::Matcher::test('spamc -c < %F > /dev/null');\n"
2132 "    ClawsMail::C::filter_log(\"LOG_MATCH\",\"SA_is_spam\") if $retval;\n"
2133 "    return $retval;\n"
2134 "}\n"
2135 "# simple extract email addresses from a header field\n"
2136 "sub extract_addresses {\n"
2137 "  my $hf = shift; return undef unless defined($hf);\n"
2138 "  my @addr = ();\n"
2139 "  while($hf =~ m/[-.+\\w]+\\@[-.+\\w]+/) {\n"
2140 "    $hf =~ s/^.*?([-.+\\w]+\\@[-.+\\w]+)//;\n"
2141 "    push @addr,$1;\n"
2142 "  }\n"
2143 "  push @addr,\"\" unless @addr;\n"
2144 "  return @addr;\n"
2145 "}\n"
2146 "# move to trash\n"
2147 "sub move_to_trash {\n"
2148 "    ClawsMail::C::move_to_trash();\n"
2149 "    ClawsMail::Filter::Action::stop(1);\n"
2150 "}\n"
2151 "# make sure a folder with a given id exists\n"
2152 "sub make_sure_folder_exists {\n"
2153 "    ClawsMail::C::make_sure_folder_exists(@_);\n"
2154 "}\n"
2155 "sub make_sure_tag_exists {\n"
2156 "    ClawsMail::C::make_sure_tag_exists(@_);\n"
2157 "}\n"
2158 "sub get_tags {\n"
2159 "    ClawsMail::C::get_tags(@_);\n"
2160 "}\n"
2161 "# abort: stop() and do not continue with built-in filtering\n"
2162 "sub abort {\n"
2163 "    ClawsMail::C::abort();\n"
2164 "    ClawsMail::Filter::Action::stop(1);\n"
2165 "}\n"
2166 "# addressbook query\n"
2167 "sub addr_in_addressbook {\n"
2168 "    return ClawsMail::C::addr_in_addressbook(@_) if @_;\n"
2169 "    return 0;\n"
2170 "}\n"
2171 "sub from_in_addressbook {\n"
2172 "    my ($from) = extract_addresses(ClawsMail::Filter::Matcher::header(\"from\"));\n"
2173 "    return 0 unless $from;\n"
2174 "    return addr_in_addressbook($from,@_);\n"
2175 "}\n"
2176 "sub get_attribute_value {\n"
2177 "    my $email = shift; my $key = shift;\n"
2178 "    return \"\" unless ($email and $key);\n"
2179 "    return ClawsMail::C::get_attribute_value($email,$key,@_);\n"
2180 "}\n"
2181 "1;\n"
2182   };
2183
2184   if((my_perl = perl_alloc()) == NULL) {
2185     g_warning("Perl Plugin: Not enough memory to allocate Perl interpreter");
2186     return -1;
2187   }
2188   PL_perl_destruct_level = 1;
2189   perl_construct(my_perl);
2190
2191   exitstatus = perl_parse(my_perl, xs_init, 4, initialize, NULL);
2192   PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
2193   eval_pv(perl_filter_matcher,TRUE);
2194   eval_pv(perl_filter_action,TRUE);
2195   eval_pv(perl_persistent,TRUE);
2196   eval_pv(perl_utils,TRUE);
2197   return exitstatus;
2198 }
2199
2200 static gboolean my_filtering_hook(gpointer source, gpointer data)
2201 {
2202   int retry;
2203
2204   g_return_val_if_fail(source != NULL, FALSE);
2205
2206   mail_filtering_data = (MailFilteringData *) source;
2207   msginfo = mail_filtering_data->msginfo;
2208   if (!msginfo)
2209     return FALSE;
2210   stop_filtering = FALSE;
2211   wrote_filter_log_head = FALSE;
2212   filter_log_verbosity = config.filter_log_verbosity;
2213   if(GPOINTER_TO_UINT(data) == AUTO_FILTER)
2214     manual_filtering = FALSE;
2215   else if(GPOINTER_TO_UINT(data) == MANU_FILTER)
2216     manual_filtering = TRUE;
2217   else
2218     debug_print("Invalid user data ignored.\n");
2219
2220   if(!manual_filtering)
2221     statusbar_print_all("Perl Plugin: filtering message...");
2222
2223   /* Process Skript File */
2224   retry = perl_load_file();
2225   while(retry == 1) {
2226     debug_print("Error processing Perl script file. Retrying..\n");
2227     retry = perl_load_file();
2228   }
2229   if(retry == 2) {
2230     debug_print("Error processing Perl script file. Aborting..\n");
2231     stop_filtering = FALSE;
2232   }
2233   return stop_filtering;
2234 }
2235
2236 static void perl_plugin_save_config(void)
2237 {
2238   PrefFile *pfile;
2239   gchar *rcpath;
2240
2241   debug_print("Saving Perl Plugin Configuration\n");
2242
2243   rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
2244   pfile = prefs_write_open(rcpath);
2245   g_free(rcpath);
2246   if (!pfile || (prefs_set_block_label(pfile, "PerlPlugin") < 0))
2247     return;
2248   
2249   if (prefs_write_param(param, pfile->fp) < 0) {
2250     g_warning("Perl Plugin: Failed to write Perl Plugin configuration to file");
2251     prefs_file_close_revert(pfile);
2252     return;
2253   }
2254         if (fprintf(pfile->fp, "\n") < 0) {
2255     FILE_OP_ERROR(rcpath, "fprintf");
2256     prefs_file_close_revert(pfile);
2257   } else
2258           prefs_file_close(pfile);
2259 }
2260
2261 gint plugin_init(gchar **error)
2262 {
2263   int argc;
2264   char **argv;
2265   char **env;
2266   int status = 0;
2267   FILE *fp;
2268   gchar *perlfilter;
2269   gchar *rcpath;
2270
2271   /* version check */
2272   if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,4,6),
2273         VERSION_NUMERIC, "Perl", error))
2274     return -1;
2275
2276   /* register hook for automatic and manual filtering */
2277   filtering_hook_id = hooks_register_hook(MAIL_FILTERING_HOOKLIST,
2278             my_filtering_hook,
2279             GUINT_TO_POINTER(AUTO_FILTER));
2280   if(filtering_hook_id == (guint) -1) {
2281     *error = g_strdup("Failed to register mail filtering hook");
2282     return -1;
2283   }
2284   manual_filtering_hook_id = hooks_register_hook(MAIL_MANUAL_FILTERING_HOOKLIST,
2285              my_filtering_hook,
2286              GUINT_TO_POINTER(MANU_FILTER));
2287   if(manual_filtering_hook_id == (guint) -1) {
2288     hooks_unregister_hook(MAIL_FILTERING_HOOKLIST, filtering_hook_id);
2289     *error = g_strdup("Failed to register manual mail filtering hook");
2290     return -1;
2291   }
2292
2293   rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
2294   prefs_read_config(param, "PerlPlugin", rcpath, NULL);
2295   g_free(rcpath);
2296
2297   /* make sure we have at least an empty scriptfile */
2298   perlfilter = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PERLFILTER, NULL);
2299   if((fp = fopen(perlfilter, "a")) == NULL) {
2300     *error = g_strdup("Failed to create blank scriptfile");
2301     g_free(perlfilter);
2302     hooks_unregister_hook(MAIL_FILTERING_HOOKLIST,
2303         filtering_hook_id);
2304     hooks_unregister_hook(MAIL_MANUAL_FILTERING_HOOKLIST,
2305         manual_filtering_hook_id);
2306     return -1;
2307   }
2308   /* chmod for security */
2309   if (change_file_mode_rw(fp, perlfilter) < 0) {
2310     FILE_OP_ERROR(perlfilter, "chmod");
2311     g_warning("Perl Plugin: Can't change file mode");
2312   }
2313   fclose(fp);
2314   g_free(perlfilter);
2315
2316   argc = 1;
2317   argv = g_new0(char*, 1);
2318   argv[0] = NULL;
2319   env = g_new0(char*, 1);
2320   env[0] = NULL;
2321
2322
2323   /* Initialize Perl Interpreter */
2324   PERL_SYS_INIT3(&argc, &argv, &env);
2325   g_free(argv);
2326   g_free(env);
2327   if(my_perl == NULL)
2328     status = perl_init();
2329   if(status) {
2330     *error = g_strdup("Failed to load Perl Interpreter\n");
2331     hooks_unregister_hook(MAIL_FILTERING_HOOKLIST,
2332         filtering_hook_id);
2333     hooks_unregister_hook(MAIL_MANUAL_FILTERING_HOOKLIST,
2334         manual_filtering_hook_id);
2335     return -1;
2336   }
2337
2338   perl_gtk_init();
2339   debug_print("Perl Plugin loaded\n");
2340   return 0;
2341 }
2342
2343 gboolean plugin_done(void)
2344 {
2345   hooks_unregister_hook(MAIL_FILTERING_HOOKLIST,
2346       filtering_hook_id);
2347   hooks_unregister_hook(MAIL_MANUAL_FILTERING_HOOKLIST,
2348       manual_filtering_hook_id);
2349   
2350   free_all_lists();
2351
2352   if(my_perl != NULL) {
2353     PL_perl_destruct_level = 1;
2354     perl_destruct(my_perl);
2355     perl_free(my_perl);
2356   }
2357   PERL_SYS_TERM();
2358
2359   perl_plugin_save_config();
2360
2361   perl_gtk_done();
2362   debug_print("Perl Plugin unloaded\n");
2363   return TRUE;
2364 }
2365
2366 const gchar *plugin_name(void)
2367 {
2368   return "Perl";
2369 }
2370
2371 const gchar *plugin_desc(void)
2372 {
2373   return "This plugin provides a Perl scripting "
2374     "interface for mail filters.\nFeedback "
2375     "to <berndth@gmx.de> is welcome.";
2376 }
2377
2378 const gchar *plugin_type(void)
2379 {
2380   return "GTK2";
2381 }
2382
2383 const gchar *plugin_licence(void)
2384 {
2385   return "GPL3+";
2386 }
2387
2388 const gchar *plugin_version(void)
2389 {
2390   return VERSION;
2391 }
2392
2393 struct PluginFeature *plugin_provides(void)
2394 {
2395   static struct PluginFeature features[] =
2396     { {PLUGIN_FILTERING, N_("Perl integration")},
2397       {PLUGIN_NOTHING, NULL}};
2398   return features;
2399 }