* New upstream release.
[debian/mimetic.git] / examples / engine.cxx
1 #include <algorithm>
2 #include <iostream>
3 #include <string>
4 #include <cctype>
5 #include <sys/types.h>
6 #include <regex.h>
7 #ifdef HAS_PCRE
8 #include <pcreposix.h>
9 #endif
10
11 #include "engine.h"
12
13 using namespace std;
14 using namespace mimetic;
15
16 engine::engine(const command_line& cl)
17 : m_cl(cl), m_pcre(false), 
18  m_match_mode(match_type_none)
19 {
20     // set options
21     if(cl.is_set(p_match_shell) || cl.is_set(p_match_regex))
22         m_match_mode = 
23             cl.is_set(p_match_shell) ? 
24             match_type_shell : 
25             match_type_regex;
26
27     m_match_mode |= 
28         cl.is_set(p_case_insensitive) ? match_flag_case_insensitive : 0;
29
30     m_match_mode |= 
31         cl.is_set(p_perl_regex) ? match_flag_perl_mode: 0;
32
33     if((m_match_mode & match_type_mask) == match_type_none)
34         m_match_mode |= match_type_regex; // default
35 }
36
37 int engine::posix_regex_match(const string& text, const string& pattern, int match_mode)
38 {
39     int r;
40     regex_t rex;
41     r = regcomp(&rex, pattern.c_str(), 
42         ( match_mode & match_flag_case_insensitive ? REG_ICASE: 0));
43     if(r != 0)
44     {
45         char buf[256];
46         regerror(r, &rex, buf, 255);
47         die(buf);
48     }
49     r = regexec(&rex, text.c_str(), 0, 0, 0);
50     regfree(&rex);
51     return r == 0;
52 }
53
54 int engine::perl_regex_match(const string& text, const string& pattern, int match_mode )
55 {
56 #ifdef HAS_PCRE
57 #else
58     die("uuh?");
59 #endif
60     return 0;
61 }
62
63 int engine::pattern_match(const string& text, const string& pattern, int match_mode)
64 {
65     switch(match_mode & match_type_mask)
66     {
67     case match_type_none:
68         die("match_type_none");
69     case match_type_exact:
70         return exact_match(text, pattern, match_mode);
71     case match_type_regex:
72         return regex_match(text, pattern, match_mode);
73     case match_type_shell:
74         return shell_match(text, pattern, match_mode);
75     default:
76         die("uuh?");
77     }
78     return 0;
79 }
80
81 int engine::shell_match(const string& text, const string& pattern, int match_mode)
82 {
83     die("not impl");
84     return 0;
85 }
86
87 int engine::regex_match(const string& text, const string& pattern, int match_mode)
88 {
89     if(m_pcre)
90         return engine::perl_regex_match(text, pattern, match_mode);
91     else
92         return engine::posix_regex_match(text, pattern, match_mode);
93 }
94
95 void engine::action_attach(MimeEntity& me, parts_hierarchy* ph, const string& fqn)
96 {
97     bool isMultipart = me.header().contentType().isMultipart();
98     bool isTopLevel = !ph->size();
99
100     /*
101         1) me is multipart:
102             add the attach to me as the last part
103         2) me is not multipart
104             create a multipart/mixed with me and the attach childs
105             and put it in the same level/position of me
106         3) me is not multipart and is the top level entity
107             same as 2) but move all me fields to the new top-level
108     */
109     Attachment* pA = new Attachment(fqn);
110     if(!pA->isValid())
111         die("attach error");
112     if(isMultipart)
113     {
114         DBG( "isMultipart");
115         me.body().parts().push_back(pA);
116     } else {
117         MimeEntity *mm;
118         mm = new MultipartMixed;
119         mm->body().parts().push_back(&me);
120         mm->body().parts().push_back(pA);
121         if(!isTopLevel)
122         {
123             DBG( "!isTopLevel");
124             MimeEntity *parent = *ph->begin();
125             replace(parent->body().parts().begin(), 
126                 parent->body().parts().end(), 
127                 &me, mm);
128         } else {
129             DBG( "isTopLevel");
130             // add cp fields here
131             Header::iterator bit, eit, pos;
132             bit = me.header().begin(), me.header().end();
133             string name; // field name
134             pos = mm->header().begin(); // insert before others
135             for(; bit != eit; ++bit)
136             {
137                 name = bit->name();    
138                 transform(name.begin(), name.end(), 
139                     name.begin(), ::tolower);
140                 if(name.find("content-") == 0 || name == "mime-version")
141                     continue;
142                 mm->header().insert(pos, *bit);
143             }
144         }
145     }
146 }
147
148 void engine::action(MimeEntity& me, parts_hierarchy* ph)
149 {
150     MimeEntity* parent = (ph->size() ? *ph->begin() : &me);
151     if(m_cl.is_set(p_add_header)) 
152     {
153         static const char* key = "add-header";
154         command_line::iterator bit, eit;
155         bit = m_cl.begin(key), eit = m_cl.end(key);
156         for(; bit != eit; ++bit)
157         {
158             Field f(bit->second);
159             parent->header().push_back(f);
160         }
161     } 
162     if(m_cl.is_set(p_add_part_header)) {
163         static const char* key = "add-part-header";
164         command_line::iterator bit, eit;
165         bit = m_cl.begin(key), eit = m_cl.end(key);
166         for(; bit != eit; ++bit)
167         {
168             Field f(bit->second);
169             me.header().push_back(f);
170         }
171     } 
172     if(m_cl.is_set(p_attach)) 
173     {
174         static const char* key = "attach";
175         command_line::iterator bit, eit;
176         bit = m_cl.begin(key), eit = m_cl.end(key);
177         for(; bit != eit; ++bit)
178             action_attach(me, ph, bit->second);
179     }
180
181     if(m_cl.is_set(p_print_msg))
182         cout << *parent;
183     else if(m_cl.is_set(p_print_part))
184         cout << me;
185 }
186
187
188 int engine::exact_match(const string& text, const string& pattern, int match_mode)
189 {
190     if(match_mode & match_flag_case_insensitive)
191     {
192         istring is(text);
193         return is == pattern;
194     } else
195         return text == pattern;
196 }
197
198
199 /*
200  * expr: pat1 [=|~] pat2
201  * pat1 is the pattern that represents the field name
202  * pat2 is the pattern that represents the field value
203  */
204 int engine::pattern_field_match(const MimeEntity& me, const string& expr, 
205     int match_mode)
206 {
207     int has_value = 0; // left part of the expr
208     char prev = 0; // previous char
209     string field_pat, value_pat;
210     char op;
211     for(size_t i = 0; i < expr.length(); ++i)
212     {
213         if( (expr[i] == '=' || expr[i] == '~') && prev != '\\')
214         {
215             has_value++; // right part
216             op = expr[i];
217             continue;
218         }
219         if(!has_value)
220             field_pat.append(1, expr[i]);
221         else
222             value_pat.append(1, expr[i]);
223         prev = expr[i];
224     }
225     field_pat = remove_external_blanks(field_pat);
226     value_pat = remove_external_blanks(value_pat);
227     // first try to find a field that match the field_pat pattern
228     const Header& h = me.header();
229     Header::const_iterator bit = h.begin(), eit = h.end();
230     for( ; bit != eit; ++bit)
231     {
232         if(pattern_match(bit->name(), field_pat, match_mode))
233         { // we've found a matching field, let's check the value
234             if(!has_value)
235                 return 1;
236             else 
237                 if(pattern_match(bit->value(), value_pat, match_mode))
238                     return 1;
239         }
240     }
241     return 0;
242 }
243
244 string engine::remove_external_blanks(const string& str) const
245 {
246     // a dirty way to trim ext.blanks
247     string s = str;
248     for(int i = s.length() - 1; i >= 0; --i)
249         if(s[i] == ' ')
250             s.erase(i, 1);
251         else
252             break;
253     while(s.length() && s[0] == ' ')
254         s.erase(0, 1);
255     return s;
256 }
257
258 int engine::fixed_field_match(const MimeEntity& me, const string& name, const string& value, int match_mode)
259 {
260     if(!me.header().hasField(name))
261         return 0;
262     if(value.length() == 0) 
263         return 1;  // it exists
264     const string& field_value = me.header().field(name).value();
265     return pattern_match(field_value, value, match_mode) ;
266 }
267
268 int engine::has_binary_attach(const MimeEntity& me, const command_line_switch& cls)
269 {
270     const Header& h = me.header();
271     const ContentType& ct = h.contentType();
272     if(ct.type() == "text" || ct.type() == "multipart" || ct.type() == "message")
273         return 0;
274     const ContentTransferEncoding& cte = h.contentTransferEncoding();
275     if(cte.mechanism() == "base64")
276         return 1;
277     return 1;
278 }
279
280 int engine::field_match(const MimeEntity& me, const command_line_switch& cls)
281 {
282     const string& name = cls.first, value = cls.second;
283     if(name == "field")
284         return pattern_field_match(me, value, m_match_mode);
285     else if (name == "ifield")
286         return pattern_field_match(me, value, 
287             m_match_mode | match_flag_case_insensitive);
288     else
289         return fixed_field_match(me, name, value,
290             m_match_mode);
291 }
292
293 int engine::has_field(const MimeEntity& me, const command_line_switch& cls)
294 {
295     return me.header().hasField(cls.second);
296 }
297
298 int engine::match_filename(const string& filename, const string& pattern)
299 {
300     // convert shell pattern string to regex
301     string re_pattern;
302     char c;
303     for(size_t i = 0; i < pattern.length(); ++i)
304     {
305         c = pattern[i];
306         switch(c)
307         {
308         case '?':
309             re_pattern.append(".");
310             break;
311         case '*':
312             re_pattern.append(".*");
313             break;
314         case '[':
315         case '.':
316         case '=':
317         case '<':
318         case '>':
319         case '+':
320         case '_':
321         case '\\':
322         case '-':
323         case ']':
324             re_pattern.append(1, '\\');
325             re_pattern.append(1, c);
326             break;
327         default:
328             re_pattern.append(1, c);
329         }
330     }
331     return regex_match(filename, re_pattern, 0);
332 }
333
334 int engine::attach_filename(const MimeEntity& me,const command_line_switch& cls)
335 {
336     typedef list<string> filename_list;
337     const Header& h = me.header();
338     const ContentType& ct = h.contentType();
339     const ContentDisposition& cd = h.contentDisposition();
340     string pattern = cls.second;
341     filename_list names;
342     // content-type params
343     names.push_back(ct.param("name"));
344     names.push_back(ct.param("filename")); // should not exists
345     // content-disposition params
346     names.push_back(cd.param("name"));
347     names.push_back(cd.param("filename")); // should not exists
348     filename_list::const_iterator bit = names.begin(), eit = names.end();
349     for( ; bit != eit; ++bit)
350         if(match_filename(*bit, pattern))
351             return 1;
352     return 0;
353 }
354
355 MimeEntity* engine::match(MimeEntity& me, int level, parts_hierarchy* ph)
356 {
357     int matched = 1, child_match = 0, free = 0;
358
359     if(ph == 0)
360     {
361         ph = new parts_hierarchy;
362         free++;
363     }
364     if(m_cl.is_set(p_recursive))
365     {
366         MimeEntityList& parts = me.body().parts();
367         if( parts.size() )
368         {
369             ++level;
370             MimeEntityList::iterator mbit, meit;
371             mbit = parts.begin(), meit = parts.end();
372             ph->insert(ph->begin(), &me);
373             for( ; mbit != meit; ++mbit)
374                 child_match += (match(**mbit, level, ph ) ? 1 : 0);
375             ph->erase(ph->begin());
376         }
377     }
378     static char *std_fields[] = {
379         "from", "sender", "to", "sujbect", "cc", "bcc",
380         "user-agent", "date", "content-type", 
381         "content-transfer-encoding", "content-disposition",
382         "content-description", 
383         0
384     };
385     command_line::const_iterator bit = m_cl.begin(), eit = m_cl.end();
386     for(; bit != eit; ++bit)
387     {
388         const string& name = bit->first, value = bit->second;
389         if(name == "attach-filename") {
390             if(!attach_filename(me, *bit)) 
391             {
392                 matched = 0;
393                 break;
394             }
395         } else if(name == "has-field") {
396             if(!has_field(me, *bit)) 
397             {
398                 matched = 0;
399                 break;
400             }
401         } else if(name == "has-binary-attach") {
402             if(!has_binary_attach(me, *bit))
403             {
404                 matched = 0;
405                 break;
406             }
407         } else if(name == "field") {
408             if(!field_match(me, *bit))
409             {
410                 matched = 0;
411                 break;
412             }
413         } else if(name == "ifield") {
414             if(!field_match(me, *bit))
415             {
416                 matched = 0;
417                 break;
418             }
419         } else {
420             int break_loop = 0;
421             char **std_name = std_fields;
422             for( int i = 0 ; std_name[i] ; ++i) 
423             {
424                 if(name == std_name[i])
425                     if(!field_match(me, *bit))
426                     {
427                         matched = 0;
428                         break_loop++;
429                         break;
430                     }
431             }
432             if(break_loop)
433                 break;
434         }
435     }
436     if(matched)
437         action(me, ph);
438     if(free)
439         delete ph;
440     return ( matched || child_match ? &me : 0);
441 }