* New upstream release.
[debian/mimetic.git] / examples / exbin.cxx
1 /***************************************************************************
2     copyright            : (C) 2002-2005 by Stefano Barbato
3     email                : stefano@codesink.org
4
5     $Id: exbin.cxx,v 1.4 2005/03/07 16:50:59 tat Exp $
6  ***************************************************************************/
7
8 /***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 /** \example exbin.cc
17  *  extract a Part based on command line parameters
18  *  more info on:
19  *      exbin -h
20  */ 
21 #include <iostream>
22 #include <sstream>
23 #include <iterator>
24 #include <fstream>
25 #include <cassert>
26 #include <mimetic/mimetic.h>
27
28 using namespace std;
29 using namespace mimetic;
30
31 unsigned int g_decoded = 0, g_messages = 0, g_files = 0, g_parts = 0;
32
33 void usage()
34 {
35     cout << "exbin [params] [in_file...]" << endl;
36     cout << "\t-t type[/subtype] matches Content-Type" << endl;
37     cout << "\t-q                totaly quiet; exit code = num of decoded entities" << endl;
38     exit(-1);
39 }
40
41
42 struct MatchParamRq
43 {
44     MatchParamRq()
45     : m_quiet(0), m_set(0)
46     {
47     }
48     bool operator()(const MimeEntity* pMe) const
49     {
50         const Header& h = pMe->header();
51         // if not set consider a part to be an attach if its
52         // MIME type is not text/*
53         if(!m_set)
54             return h.contentType().type() != "text" && 
55                 h.contentType().type() != "message";
56         // check for content type match
57         if(m_type.length() && m_type != h.contentType().type())
58             return false;
59         if(m_subtype.length() && m_subtype != h.contentType().subtype())
60             return false;
61         return true;
62     }
63     void type(const string& s)     { m_type = s; ++m_set; }
64     const istring& type() const    { return m_type; }
65     void subtype(const string& s)    { m_subtype = s; ++m_set; }
66     const istring& subtype() const    { return m_subtype; }
67     void quiet(bool b)        { m_quiet = b; }
68     bool quiet() const        { return m_quiet; }
69 private:
70     istring m_type, m_subtype;
71     bool m_quiet, m_set;
72 } mpr;
73
74 void die(bool b, const string& msg)
75 {
76     if(b)
77     {
78         cerr << "Error: " << msg << endl << endl;
79         usage();
80     }
81 }
82
83 string get_filename(const MimeEntity& me)
84 {
85     if(me.hasField(ContentDisposition::label))
86     {
87         const ContentDisposition& cd = me.header().contentDisposition();
88         if(cd.param("filename").length())
89             return cd.param("filename");
90     } else if (me.hasField(ContentType::label)) {
91         const ContentType& ct = me.header().contentType();
92         return string("unnamed_" + ct.type() + "." + ct.subtype());
93     } 
94     return "unknown_attachment";
95 }
96
97 static bool is_known_mechanism(const string& mechanism)
98 {
99     istring m = mechanism;
100
101     if(m == "base64" || m == "quoted-printable" || m == "binary" || 
102        m == "7bit" || m == "8bit")
103         return true;
104
105     return false;
106 }
107
108 void decode_bin(const MimeEntity& me)
109 {
110     const ContentTransferEncoding& cte = me.header().contentTransferEncoding();
111     const ContentDisposition& cd = me.header().contentDisposition();
112
113     if(is_known_mechanism(cte.mechanism()))
114     {
115         string filename = get_filename(me);
116         if(File::exists(filename))
117         {
118             int t;
119             for(t = 0; File::exists(utils::int2str(t)+"-"+filename);t++)
120                 ;
121             filename = utils::int2str(t) + "-" + filename;    
122         }
123         const ContentType& ct = me.header().contentType();
124         if(!mpr.quiet())
125             cout << "\tdecoding " << filename
126                 << " (" << ct.type() << "/" << ct.subtype() << ")"
127                 << endl;
128         ofstream of(filename.c_str());    
129         if(!of.is_open())
130         {
131             cerr << "ERR: unable to write to " << filename << endl;
132             return;
133         }
134         ostreambuf_iterator<char> oi(of);
135         istring enc_algo = cte.mechanism();
136         if(enc_algo == "base64")
137         {
138             Base64::Decoder b64;
139             g_decoded++;
140             decode(me.body().begin(), me.body().end(), b64 ,oi);
141         } else if (enc_algo == "quoted-printable") {
142             QP::Decoder qp;
143             g_decoded++;
144             decode(me.body().begin(), me.body().end(), qp, oi);
145         } else if (enc_algo == "8bit" || enc_algo == "7bit" || 
146                 enc_algo == "binary") {
147             copy(me.body().begin(), me.body().end(), oi);
148         } else {
149             cerr << "ERR: unknown encoding algorithm "
150                 << enc_algo << endl;
151         }
152     }
153 }
154
155 void parsePart(const MimeEntity& me, string& fqn)
156 {
157     g_parts++;
158     if(mpr(&me))
159     {
160         if(!mpr.quiet() && fqn.length() > 0)
161         {
162             cout << fqn << endl;
163             fqn.clear();
164         }
165         decode_bin(me);
166     }
167     MimeEntityList::const_iterator mbit, meit;
168     mbit = me.body().parts().begin(), meit = me.body().parts().end();
169     for(; mbit != meit; ++mbit)
170         parsePart(**mbit, fqn);
171 }
172
173 inline int isnl(char c)
174 {
175     return c == '\n' || c == '\r';
176 }
177
178 template<typename Iterator>
179 void parseMboxFile(Iterator bit, Iterator eit, string& fqn)
180 {
181     char prev;
182     Iterator it = bit;
183     for(; bit != eit; )
184     {
185         for(;;)
186         {
187             it = utils::find_bm(it, eit, "From ");
188             //it = find_n(it, eit, "From ");
189             prev = *(it-1); // previous char (must be a newline)
190             if(it == eit || isnl(prev) )
191                 break;
192             else
193                 ++it; // From in the middle of a line
194         } 
195         g_messages++;
196         MimeEntity me(bit, it);
197         parsePart(me, fqn);
198         if(it == eit)
199             return;
200         bit = it;
201         ++it; // skip the current From 
202     }
203 }
204
205 template<typename Iterator>
206 void parse(Iterator bit, Iterator eit, string& fqn)
207 {
208     string sep = "From ";
209     Iterator it = utils::find_bm(bit, bit + sep.length(), sep);
210     if(it == bit)
211     {
212         parseMboxFile(bit, eit, fqn);
213     } else {
214         g_messages++;
215         MimeEntity me(bit, eit);
216         parsePart(me, fqn);
217     }
218 }
219
220
221 int main(int argc, char** argv)
222 {
223     std::ios_base::sync_with_stdio(false);
224
225     // handle command line parameters
226     int p = 1;
227     while(p < argc)
228     {
229         string param = argv[p];
230         if(param == "-h")
231             usage();
232         else if (param == "-q") 
233             mpr.quiet(true);
234         else if (param == "-t") {
235             die( ++p == argc, param + " requires an argument");
236             ContentType ct(argv[p]);
237             die(mpr.type().length() != 0, "just one -t allowed");
238             mpr.type(ct.type());
239             mpr.subtype(ct.subtype());
240         } else {
241             // filename list starts here
242             // first filename: argv[p]
243             break;
244         }
245         ++p;
246     }
247
248     string fqn;
249     if(argc == p)
250     { // read from stdin
251         fqn = "stdin";
252         string buf;
253         enum { page_sz = 4096 };
254         char page[page_sz];
255         int count;
256         while((count = cin.rdbuf()->sgetn(page, page_sz)) > 0)
257             buf.append(page);
258         parse(buf.begin(), buf.end(), fqn);
259         g_files++;
260     } else {
261         for(int fc = p; fc < argc; ++fc)
262         {
263             fqn = argv[fc];
264             File in(fqn);
265             if(!in)
266             {
267                 cerr << "ERR: unable to open file " << argv[fc]
268                      << endl;
269                 continue;
270             }
271             g_files++;
272             parse(in.begin(), in.end(), fqn);
273         }
274     }
275     if(!mpr.quiet())
276         cout << g_files << " file(s) analyzed, " <<
277             g_messages << " message(s) and " <<
278             g_parts << " entitie(s) parsed, " <<
279             g_decoded << " attachment(s) extracted." << endl;
280     return g_decoded;
281 }
282