1 # include <iostream>
2 # include <sys/time.h>
3 # include <sys/wait.h>
4 
5 # include <boost/filesystem.hpp>
6 # include <glib.h>
7 # include <gio/gio.h>
8 # include <thread>
9 # include <mutex>
10 # include <condition_variable>
11 # include <chrono>
12 
13 # include <gmime/gmime.h>
14 # include "utils/gmime/gmime-compat.h"
15 
16 # include "astroid.hh"
17 # include "db.hh"
18 # include "config.hh"
19 # include "compose_message.hh"
20 # include "message_thread.hh"
21 # include "account_manager.hh"
22 # include "chunk.hh"
23 # include "crypto.hh"
24 # include "actions/action_manager.hh"
25 # include "actions/onmessage.hh"
26 # include "utils/address.hh"
27 # include "utils/ustring_utils.hh"
28 # ifndef DISABLE_PLUGINS
29   # include "plugin/manager.hh"
30 # endif
31 
32 using namespace std;
33 namespace bfs = boost::filesystem;
34 
35 namespace Astroid {
ComposeMessage()36   ComposeMessage::ComposeMessage () {
37     LOG (debug) << "cm: initialize..";
38     message = g_mime_message_new (true);
39 
40     d_message_sent.connect (
41         sigc::mem_fun (this, &ComposeMessage::message_sent_event));
42     d_message_send_status.connect (
43         sigc::mem_fun (this, &ComposeMessage::message_send_status_event));
44   }
45 
~ComposeMessage()46   ComposeMessage::~ComposeMessage () {
47     if (send_thread.joinable ()) send_thread.join ();
48     g_object_unref (message);
49 
50     LOG (debug) << "cm: deinitialized.";
51   }
52 
set_from(Account * a)53   void ComposeMessage::set_from (Account *a) {
54     account = a;
55     from = internet_address_mailbox_new (a->name.c_str(), a->email.c_str());
56     g_mime_message_add_mailbox (message, GMIME_ADDRESS_TYPE_FROM, a->name.c_str (), a->email.c_str ());
57   }
58 
set_to(ustring _to)59   void ComposeMessage::set_to (ustring _to) {
60     to = _to;
61     g_mime_object_set_header (GMIME_OBJECT(message), "To", to.c_str(), NULL);
62   }
63 
set_cc(ustring _cc)64   void ComposeMessage::set_cc (ustring _cc) {
65     cc = _cc;
66     g_mime_object_set_header (GMIME_OBJECT(message), "Cc", cc.c_str(), NULL);
67   }
68 
set_bcc(ustring _bcc)69   void ComposeMessage::set_bcc (ustring _bcc) {
70     bcc = _bcc;
71     g_mime_object_set_header (GMIME_OBJECT(message), "Bcc", bcc.c_str(), NULL);
72   }
73 
set_subject(ustring _subject)74   void ComposeMessage::set_subject (ustring _subject) {
75     subject = _subject;
76     g_mime_message_set_subject (message, subject.c_str(), NULL);
77   }
78 
set_id(ustring _id)79   void ComposeMessage::set_id (ustring _id) {
80     id = _id;
81   }
82 
set_references(ustring _refs)83   void ComposeMessage::set_references (ustring _refs) {
84     references = _refs;
85     if (references.empty ()) {
86       g_mime_header_list_remove (
87           g_mime_object_get_header_list (GMIME_OBJECT(message)),
88           "References");
89     } else {
90       g_mime_object_set_header (GMIME_OBJECT(message), "References",
91           references.c_str(), NULL);
92     }
93   }
94 
set_inreplyto(ustring _inreplyto)95   void ComposeMessage::set_inreplyto (ustring _inreplyto) {
96     inreplyto = _inreplyto;
97     if (inreplyto.empty ()) {
98       g_mime_header_list_remove (
99           g_mime_object_get_header_list (GMIME_OBJECT(message)),
100           "In-Reply-To");
101     } else {
102       ustring tmp = "<" + inreplyto + ">";
103       g_mime_object_set_header (GMIME_OBJECT(message), "In-Reply-To",
104           tmp.c_str(), NULL);
105     }
106   }
107 
build()108   void ComposeMessage::build () {
109     LOG (debug) << "cm: build..";
110 
111     std::string text_body_content(body.str());
112 
113     /* attached signatures are handled in ::finalize */
114     if (include_signature && account && !account->signature_attach) {
115       LOG (debug) << "cm: adding inline signature from: " << account->signature_file.c_str ();
116       std::ifstream s (account->signature_file.c_str ());
117       std::ostringstream sf;
118       sf << s.rdbuf ();
119       s.close ();
120       if (account->signature_separate) {
121 	text_body_content += "-- \n";
122       }
123       text_body_content += sf.str ();
124     }
125 
126     markdown_success = false;
127     markdown_error   = "";
128 
129 
130     /* create text part */
131     GMimeStream * contentStream = g_mime_stream_mem_new_with_buffer(text_body_content.c_str(), text_body_content.size());
132     GMimePart * messagePart = g_mime_part_new_with_type ("text", "plain");
133 
134     g_mime_object_set_content_type_parameter ((GMimeObject *) messagePart, "charset", astroid->config().get<string>("editor.charset").c_str());
135 
136     if (astroid->config().get<bool> ("mail.format_flowed")) {
137       g_mime_object_set_content_type_parameter ((GMimeObject *) messagePart, "format", "flowed");
138     }
139 
140     GMimeDataWrapper * contentWrapper = g_mime_data_wrapper_new_with_stream(contentStream, GMIME_CONTENT_ENCODING_DEFAULT);
141 
142     g_mime_part_set_content_encoding (messagePart, GMIME_CONTENT_ENCODING_QUOTEDPRINTABLE);
143     g_mime_part_set_content (messagePart, contentWrapper);
144 
145     g_object_unref(contentWrapper);
146     g_object_unref(contentStream);
147 
148 
149     if (markdown) {
150       std::string md_body_content(body.str());
151 
152       /* attached signatures are handled in ::finalize */
153       if (include_signature && account && !account->signature_attach && account->has_signature_markdown) {
154         LOG (debug) << "cm: adding inline signature (markdown) from: " << account->signature_file_markdown.c_str ();
155         std::ifstream s (account->signature_file_markdown.c_str ());
156         std::ostringstream sf;
157         sf << s.rdbuf ();
158         s.close ();
159         if (account->signature_separate) {
160           md_body_content += "-- \n";
161         }
162         md_body_content += sf.str ();
163       }
164 
165       GMimePart * text = messagePart;
166       GMimeMultipart * mp = g_mime_multipart_new_with_subtype ("alternative");
167 
168       /* add text part */
169       g_mime_multipart_add (mp, GMIME_OBJECT(messagePart));
170       messagePart = GMIME_PART(mp);
171 
172       /* construct HTML part */
173       GMimePart * html = g_mime_part_new_with_type ("text", "html");
174       g_mime_object_set_content_type_parameter ((GMimeObject *) html, "charset", astroid->config().get<string>("editor.charset").c_str());
175 
176       if (astroid->config().get<bool> ("mail.format_flowed")) {
177         g_mime_object_set_content_type_parameter ((GMimeObject *) html, "format", "flowed");
178       }
179 
180       GMimeStream * contentStream;
181 
182       /* pipe through markdown to html generator */
183       int pid;
184       int stdin;
185       int stdout;
186       int stderr;
187       markdown_success = true;
188       vector<string> args = Glib::shell_parse_argv (astroid->config().get<string>("editor.markdown_processor"));
189       try {
190         Glib::spawn_async_with_pipes ("",
191                           args,
192                           Glib::SPAWN_DO_NOT_REAP_CHILD |
193                           Glib::SPAWN_SEARCH_PATH,
194                           sigc::slot <void> (),
195                           &pid,
196                           &stdin,
197                           &stdout,
198                           &stderr
199                           );
200 
201         refptr<Glib::IOChannel> ch_stdin;
202         refptr<Glib::IOChannel> ch_stdout;
203         refptr<Glib::IOChannel> ch_stderr;
204         ch_stdin  = Glib::IOChannel::create_from_fd (stdin);
205         ch_stdout = Glib::IOChannel::create_from_fd (stdout);
206         ch_stderr = Glib::IOChannel::create_from_fd (stderr);
207 
208         ch_stdin->write (md_body_content);
209         ch_stdin->close ();
210 
211         ustring _html;
212         ch_stdout->read_to_end (_html);
213         ch_stdout->close ();
214 
215 
216         ustring _err;
217         ch_stderr->read_to_end (_err);
218         ch_stderr->close ();
219 
220         if (!_err.empty ()) {
221           LOG (error) << "cm: md: " << _err;
222           markdown_error   = _err;
223           markdown_success = false;
224         } else {
225 
226           LOG (debug) << "cm: md: got html: " << _html;
227 
228           contentStream = g_mime_stream_mem_new_with_buffer(_html.c_str(), _html.size());
229         }
230 
231         g_spawn_close_pid (pid);
232       } catch (Glib::SpawnError &ex) {
233         LOG (error) << "cm: md: failed to spawn markdown processor: " << ex.what ();
234 
235         markdown_success = false;
236         markdown_error   = "Failed to spawn markdown processor: " + ex.what();
237       }
238 
239       if (markdown_success) {
240         /* add output to html part */
241         GMimeDataWrapper * contentWrapper = g_mime_data_wrapper_new_with_stream(contentStream, GMIME_CONTENT_ENCODING_DEFAULT);
242 
243         g_mime_part_set_content_encoding (html, GMIME_CONTENT_ENCODING_QUOTEDPRINTABLE);
244         g_mime_part_set_content (html, contentWrapper);
245 
246         g_object_unref(contentWrapper);
247         g_object_unref(contentStream);
248 
249         /* add html part to message */
250         g_mime_multipart_add (mp, GMIME_OBJECT (html));
251         g_object_unref (text);
252       } else {
253         /* revert to only text part */
254         g_object_unref (messagePart);
255         messagePart = text;
256       }
257     }
258 
259     g_mime_message_set_mime_part(message, GMIME_OBJECT(messagePart));
260     g_object_unref(messagePart);
261   }
262 
load_message(ustring _mid,ustring fname)263   void ComposeMessage::load_message (ustring _mid, ustring fname) {
264     set_id (_mid);
265     UnprocessedMessage msg (_mid, fname);
266 
267     Account * from = astroid->accounts->get_account_for_address (msg.sender);
268     if (from == NULL) {
269       LOG (warn) << "cm: warning: unknown sending address, using default.";
270       from = &(astroid->accounts->accounts[astroid->accounts->default_account]);
271     }
272     set_from (from);
273 
274     char * cto = internet_address_list_to_string (msg.to(), NULL, false);
275     if (cto) set_to (cto);
276 
277     cto = internet_address_list_to_string (msg.cc(), NULL, false);
278     if (cto) set_cc (cto);
279 
280     cto = internet_address_list_to_string (msg.bcc(), NULL, false);
281     if (cto) set_bcc (cto);
282 
283     set_references (msg.references);
284     set_inreplyto (msg.inreplyto);
285 
286     set_subject (msg.subject);
287 
288     body << msg.plain_text (false);
289   }
290 
finalize()291   void ComposeMessage::finalize () {
292     /* make message ready to be sent */
293     LOG (debug) << "cm: finalize..";
294 
295     /* set user agent */
296     ustring ua = "";
297 
298 # ifndef DISABLE_PLUGINS
299     if (!astroid->plugin_manager->astroid_extension->get_user_agent (ua)) {
300 # endif
301 
302       ua = astroid->config ().get<string> ("mail.user_agent");
303       UstringUtils::trim (ua);
304 
305       if (ua == "default") ua = astroid->user_agent;
306 # ifndef DISABLE_PLUGINS
307     }
308 # endif
309 
310     if (!ua.empty ()) {
311       g_mime_object_set_header (GMIME_OBJECT(message), "User-Agent", ua.c_str(), NULL);
312     }
313 
314     /* add date to the message */
315     g_mime_message_set_date_now (message);
316 
317     /* Give the message an ID */
318     g_mime_message_set_message_id(message, id.c_str());
319 
320     /* inline signatures are handled in ::build */
321     if (include_signature && account && account->signature_attach) {
322       shared_ptr<ComposeMessage::Attachment> sa (
323         new ComposeMessage::Attachment (account->signature_file));
324 
325       /* this could be an .vcf, so lets not try to be too smart here */
326       add_attachment (sa);
327     }
328 
329     /* attachments  */
330     if (attachments.size() > 0)
331     {
332       GMimeMultipart * multipart = g_mime_multipart_new_with_subtype("mixed");
333       g_mime_multipart_add (multipart, (GMimeObject*) message->mime_part);
334       g_mime_message_set_mime_part (message, (GMimeObject*) multipart);
335 
336       /* not unreffing message->mime_part here since it is reused */
337 
338       for (shared_ptr<Attachment> &a : attachments)
339       {
340         if (!a->valid) {
341           LOG (error) << "cm: invalid attachment: " << a->name;
342           // in practice this cannot happen since EditMessage will
343           // not add an invalid attachment. In the case that it would
344           // be added, there would be no way for the user to delete it
345           // from the draft.
346           continue;
347         }
348 
349         if (a->is_mime_message) {
350 
351           GMimeMessagePart * mp = g_mime_message_part_new_with_message ("rfc822", (GMimeMessage*) a->message->message);
352           g_mime_multipart_add (multipart, (GMimeObject *) mp);
353 
354           g_object_unref (mp);
355 
356         } else {
357 
358           GMimeStream * file_stream;
359 
360 
361           file_stream = g_mime_stream_mem_new_with_byte_array (a->contents->gobj());
362           g_mime_stream_mem_set_owner (GMIME_STREAM_MEM (file_stream), false);
363 
364 
365           GMimeDataWrapper * data = g_mime_data_wrapper_new_with_stream (file_stream,
366               GMIME_CONTENT_ENCODING_DEFAULT);
367 
368           GMimeContentType * contentType = g_mime_content_type_parse (g_mime_parser_options_get_default (), a->content_type.c_str ());
369 
370           GMimePart * part =
371             g_mime_part_new_with_type(g_mime_content_type_get_media_type (contentType),
372             g_mime_content_type_get_media_subtype (contentType));
373           g_mime_part_set_content (part, data);
374           g_mime_part_set_filename (part, a->name.c_str());
375 
376           if (a->dispostion_inline) {
377             g_mime_object_set_disposition (GMIME_OBJECT(part), "inline");
378           } else {
379             g_mime_part_set_content_encoding (part, GMIME_CONTENT_ENCODING_BASE64);
380           }
381 
382 
383           g_mime_multipart_add (multipart, (GMimeObject*) part);
384 
385           g_object_unref (part);
386           g_object_unref (contentType);
387           g_object_unref (file_stream);
388           g_object_unref (data);
389         }
390 
391       }
392 
393       g_object_unref(multipart);
394     }
395 
396     /* encryption */
397     encryption_success = false;
398     encryption_error = "";
399     GError * err = NULL;
400 
401     if (encrypt || sign) {
402       GMimeObject * content = g_mime_message_get_mime_part (message);
403 
404       Crypto cy ("application/pgp-encrypted");
405 
406       if (encrypt) {
407 
408         GMimeMultipartEncrypted * e_content = NULL;
409         encryption_success = cy.encrypt (content, sign, account->gpgkey, from, AddressList (to) + AddressList (cc) + AddressList (bcc), &e_content, &err);
410 
411         g_mime_message_set_mime_part (message, (GMimeObject *) e_content);
412         g_object_unref (e_content);
413 
414       } else {
415         /* only sign */
416 
417         GMimeMultipartSigned * s_content = NULL;
418         encryption_success = cy.sign (content, account->gpgkey, &s_content, &err);
419 
420         g_mime_message_set_mime_part (message, (GMimeObject *) s_content);
421         g_object_unref (s_content);
422       }
423 
424       /* g_object_unref (content); */
425 
426       if (!encryption_success) {
427         encryption_error = err->message;
428         LOG (error) << "cm: failed encrypting or signing: " << encryption_error;
429       }
430     }
431   }
432 
add_attachment(shared_ptr<Attachment> a)433   void ComposeMessage::add_attachment (shared_ptr<Attachment> a) {
434     attachments.push_back (a);
435   }
436 
cancel_sending()437   bool ComposeMessage::cancel_sending () {
438     LOG (warn) << "cm: cancel sendmail pid: " << pid;
439     std::lock_guard<std::mutex> lk (send_cancel_m);
440 
441     cancel_send_during_delay = true;
442 
443     if (pid > 0) {
444 
445       int r = kill (pid, SIGKILL);
446 
447       if (r == 0) {
448         LOG (warn) << "cm: sendmail killed.";
449       } else {
450         LOG (error) << "cm: could not kill sendmail.";
451       }
452     }
453 
454     send_cancel_cv.notify_one ();
455     if (send_thread.joinable ()) {
456       send_thread.detach ();
457     }
458 
459     return true;
460   }
461 
send_threaded()462   void ComposeMessage::send_threaded ()
463   {
464     LOG (info) << "cm: sending (threaded)..";
465     cancel_send_during_delay = false;
466     send_thread = std::thread (&ComposeMessage::send, this);
467   }
468 
send()469   bool ComposeMessage::send () {
470 
471     dryrun = astroid->config().get<bool>("astroid.debug.dryrun_sending");
472 
473     message_send_status_warn = false;
474     message_send_status_msg  = "";
475 
476     unsigned int delay = astroid->config ().get<unsigned int> ("mail.send_delay");
477     std::unique_lock<std::mutex> lk (send_cancel_m);
478 
479     while (delay > 0 && !cancel_send_during_delay) {
480       LOG (debug) << "cm: sending in " << delay << " seconds..";
481       if (astroid->hint_level () < 1) {
482         /* TODO: replace C-c with the actual keybinding configured by the user */
483         message_send_status_msg = ustring::compose ("sending message in %1 seconds... Press C-c to cancel!", delay);
484       } else {
485         message_send_status_msg = ustring::compose ("sending message in %1 seconds...", delay);
486       }
487       d_message_send_status ();
488       std::chrono::seconds sec (1);
489       send_cancel_cv.wait_until (lk, std::chrono::system_clock::now () + sec, [&] { return cancel_send_during_delay; });
490       delay--;
491     }
492 
493     if (cancel_send_during_delay) {
494       LOG (error) << "cm: cancelled sending before message could be sent.";
495       message_send_status_msg = "sending message... cancelled before sending.";
496       message_send_status_warn = true;
497       d_message_send_status ();
498 
499       message_sent_result = false;
500       d_message_sent ();
501       pid = 0;
502       return false;
503     }
504 
505     lk.unlock ();
506 
507     if (astroid->hint_level () < 1) {
508       /* TODO: replace C-c with the actual keybinding configured by the user */
509       message_send_status_msg = "sending message... Press C-c to cancel!";
510     } else {
511       message_send_status_msg = "sending message...";
512     }
513     d_message_send_status ();
514 
515     int stdin;
516     int stdout;
517     int stderr;
518 
519     /* Send the message */
520     if (!dryrun) {
521       LOG (warn) << "cm: sending message from account: " << account->full_address ();
522 
523       ustring send_command = account->sendmail;
524 
525       LOG (debug) << "cm: sending message using command: " << send_command;
526 
527       vector<string> args = Glib::shell_parse_argv (send_command);
528       try {
529         Glib::spawn_async_with_pipes ("",
530                           args,
531                           Glib::SPAWN_DO_NOT_REAP_CHILD |
532                           Glib::SPAWN_SEARCH_PATH,
533                           sigc::slot <void> (),
534                           &pid,
535                           &stdin,
536                           &stdout,
537                           &stderr
538                           );
539       } catch (Glib::SpawnError &ex) {
540         LOG (error) << "cm: could not send message!";
541 
542         message_send_status_msg = "message could not be sent!";
543         message_send_status_warn = true;
544         d_message_send_status ();
545 
546         message_sent_result = false;
547         d_message_sent ();
548         pid = 0;
549         return false;
550       }
551 
552       /* connect channels */
553       refptr<Glib::IOChannel> ch_stdout;
554       refptr<Glib::IOChannel> ch_stderr;
555       ch_stdout = Glib::IOChannel::create_from_fd (stdout);
556       ch_stderr = Glib::IOChannel::create_from_fd (stderr);
557 
558       /* write message to sendmail */
559       GMimeStream * stream = g_mime_stream_pipe_new (stdin);
560       g_mime_stream_pipe_set_owner (GMIME_STREAM_PIPE(stream), true); // causes stdin to be closed when stream is closed
561       g_mime_object_write_to_stream (GMIME_OBJECT(message), g_mime_format_options_get_default (), stream);
562       g_mime_stream_flush (stream);
563       g_object_unref (stream); // closes stdin
564 
565       /* wait for sendmail to finish */
566       int status;
567       pid_t wp = waitpid (pid, &status, 0);
568 
569       if (wp == (pid_t)-1) {
570         LOG (error) << "cm: error when executing sendmail process: " << errno << ", unknown if message was sent.";
571       }
572 
573       g_spawn_close_pid (pid);
574 
575       /* these read_to_end's are necessary to wait for the pipes to be closed, hopefully
576        * ensuring that any child processed forked by the sendmail process
577        * has also finished */
578       if (ch_stdout) {
579         Glib::ustring buf;
580 
581         ch_stdout->read_to_end(buf);
582         if (*(--buf.end()) == '\n') buf.erase (--buf.end());
583 
584         if (!buf.empty ()) LOG (debug) << "sendmail: " << buf;
585         ch_stdout->close ();
586         ch_stdout.clear ();
587       }
588 
589       if (ch_stderr) {
590         Glib::ustring buf;
591 
592         ch_stderr->read_to_end(buf);
593         if (*(--buf.end()) == '\n') buf.erase (--buf.end());
594 
595         if (!buf.empty ()) LOG (warn) << "sendmail: " << buf;
596         ch_stderr->close ();
597         ch_stderr.clear ();
598       }
599 
600       ::close (stdout);
601       ::close (stderr);
602 
603       if (status == 0 && wp != (pid_t)-1)
604       {
605         LOG (warn) << "cm: message sent successfully!";
606 
607         if (account->save_sent) {
608           using bfs::path;
609           save_to = account->save_sent_to / path(id + ":2,");
610           LOG (info) << "cm: saving message to: " << save_to;
611 
612           write (save_to.c_str());
613         }
614 
615         message_send_status_msg = "message sent successfully!";
616         message_send_status_warn = false;
617         d_message_send_status ();
618 
619         pid = 0;
620         message_sent_result = true;
621         d_message_sent ();
622         return true;
623 
624       } else {
625         LOG (error) << "cm: could not send message: " << status << "!";
626 
627         message_send_status_msg = "message could not be sent!";
628         message_send_status_warn = true;
629         d_message_send_status ();
630 
631         message_sent_result = false;
632         d_message_sent ();
633         pid = 0;
634         return false;
635       }
636     } else {
637       ustring fname = "/tmp/" + id;
638       LOG (warn) << "cm: sending disabled in config, message written to: " << fname;
639       message_send_status_msg = "sending disabled, message written to: " + fname;
640       message_send_status_warn = true;
641       d_message_send_status ();
642 
643       write (fname);
644       message_sent_result = false;
645       d_message_sent ();
646       pid = 0;
647       return false;
648     }
649   }
650 
651   /* signals */
652   ComposeMessage::type_message_sent
message_sent()653     ComposeMessage::message_sent ()
654   {
655     return m_message_sent;
656   }
657 
emit_message_sent(bool res)658   void ComposeMessage::emit_message_sent (bool res) {
659     m_message_sent.emit (res);
660   }
661 
message_sent_event()662   void ComposeMessage::message_sent_event () {
663     /* add to notmuch with sent tag (on main GUI thread) */
664     if (!dryrun && message_sent_result && account->save_sent) {
665       astroid->actions->doit (refptr<Action> (
666             new AddSentMessage (save_to.c_str (), account->additional_sent_tags, inreplyto)));
667       LOG (info) << "cm: sent message added to db.";
668     }
669 
670     emit_message_sent (message_sent_result);
671   }
672 
673   ComposeMessage::type_message_send_status
message_send_status()674     ComposeMessage::message_send_status ()
675   {
676     return m_message_send_status;
677   }
678 
emit_message_send_status(bool warn,ustring msg)679   void ComposeMessage::emit_message_send_status (bool warn, ustring msg) {
680     m_message_send_status.emit (warn, msg);
681   }
682 
message_send_status_event()683   void ComposeMessage::message_send_status_event () {
684     emit_message_send_status (message_send_status_warn, message_send_status_msg);
685   }
686 
write_tmp()687   ustring ComposeMessage::write_tmp () {
688     char * temporaryFilePath;
689 
690     if (!bfs::is_directory ("/tmp")) {
691       /* this fails if /tmp does not exist, typically in a chroot */
692       LOG (warn) << "cm: /tmp is not a directory, writing tmp files to current directory.";
693       temporaryFilePath = strdup("tmp-astroid-compose-XXXXXX");
694     } else {
695       temporaryFilePath = strdup("/tmp/astroid-compose-XXXXXX");
696     }
697 
698     int fd = mkstemp(temporaryFilePath);
699     message_file = temporaryFilePath;
700     free(temporaryFilePath);
701 
702     GMimeStream * stream = g_mime_stream_fs_new(fd);
703     g_mime_object_write_to_stream (GMIME_OBJECT(message), g_mime_format_options_get_default (), stream);
704     g_mime_stream_flush (stream);
705 
706     g_object_unref(stream);
707 
708     LOG (info) << "cm: wrote tmp file: " << message_file;
709 
710     return message_file;
711   }
712 
write(ustring fname)713   void ComposeMessage::write (ustring fname) {
714     if (bfs::exists (fname.c_str ())) unlink (fname.c_str ());
715 
716     FILE * MessageFile = fopen(fname.c_str(), "w");
717 
718     GMimeStream * stream = g_mime_stream_file_new(MessageFile);
719     g_mime_object_write_to_stream (GMIME_OBJECT(message), g_mime_format_options_get_default (), stream);
720     g_mime_stream_flush (stream);
721 
722     g_object_unref(stream);
723 
724     LOG (debug) << "cm: wrote file: " << fname;
725   }
726 
write(GMimeStream * stream)727   void ComposeMessage::write (GMimeStream * stream) {
728     g_object_ref (stream);
729 
730     g_mime_object_write_to_stream (GMIME_OBJECT(message), g_mime_format_options_get_default (), stream);
731     g_mime_stream_flush (stream);
732     g_mime_stream_seek (stream, 0, GMIME_STREAM_SEEK_SET);
733 
734     g_object_unref(stream);
735 
736     LOG (debug) << "cm: wrote to stream.";
737   }
738 
Attachment()739   ComposeMessage::Attachment::Attachment () {
740   }
741 
Attachment(bfs::path p)742   ComposeMessage::Attachment::Attachment (bfs::path p) {
743     LOG (debug) << "cm: at: construct from file.";
744     fname   = p;
745 
746     std::string filename = fname.c_str ();
747 
748     GError * error = NULL;
749     GFile * file = g_file_new_for_path(filename.c_str());
750     GFileInfo * file_info = g_file_query_info(file,
751         G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
752         G_FILE_QUERY_INFO_NONE, NULL, &error);
753 
754     if (error)
755     {
756       LOG (error) << "cm: could not query file information.";
757       valid = false;
758       g_object_unref (file);
759       g_object_unref (file_info);
760       return;
761     }
762 
763     if (g_file_info_get_file_type(file_info) != G_FILE_TYPE_REGULAR)
764     {
765       LOG (error) << "cm: attached file is not a regular file.";
766       valid = false;
767       g_object_unref (file);
768       g_object_unref (file_info);
769       return;
770     }
771 
772     content_type = g_file_info_get_content_type (file_info);
773     name = fname.filename().c_str();
774     valid = true;
775 
776     if (content_type == "message/rfc822") {
777       LOG (debug) << "cm: attachment is mime message.";
778       message = refptr<Message> (new UnprocessedMessage(fname.c_str ()));
779 
780       is_mime_message = true;
781 
782       name = message->subject;
783 
784     } else {
785       /* load into byte array */
786       refptr<Gio::File> fle = Glib::wrap (file, false);
787       refptr<Gio::FileInputStream> istr = fle->read ();
788 
789       refptr<Glib::Bytes> b;
790       contents = Glib::ByteArray::create ();
791 
792       do {
793         b = istr->read_bytes (4096, refptr<Gio::Cancellable>(NULL));
794 
795         if (b) {
796           gsize s = b->get_size ();
797           if (s <= 0) break;
798           contents->append ((const guint8 *) b->get_data (s), s);
799         }
800 
801       } while (b);
802     }
803 
804     g_object_unref (file);
805     g_object_unref (file_info);
806   }
807 
Attachment(refptr<Chunk> c)808   ComposeMessage::Attachment::Attachment (refptr<Chunk> c) {
809     LOG (debug) << "cm: at: construct from chunk.";
810     name = c->get_filename ();
811     valid = true;
812 
813     /* used by edit message when deleting attachment */
814     chunk_id = c->id;
815 
816     if (c->mime_message) {
817       content_type = "message/rfc822";
818       is_mime_message = true;
819 
820       message = refptr<Message> (new UnprocessedMessage(GMIME_MESSAGE(c->mime_object)));
821 
822       g_object_ref (c->mime_object); // should be cleaned by Message : Glib::Object
823 
824       name = message->subject;
825 
826     } else {
827 
828       contents = c->contents ();
829 
830       const char * ct = g_mime_content_type_get_mime_type (c->content_type);
831       if (ct != NULL) {
832         content_type = std::string (ct);
833       } else {
834         content_type = "application/octet-stream";
835       }
836     }
837   }
838 
Attachment(refptr<Message> msg)839   ComposeMessage::Attachment::Attachment (refptr<Message> msg) {
840     LOG (debug) << "cm: at: construct from message.";
841     name = msg->subject;
842     is_mime_message = true;
843 
844     content_type = "message/rfc822";
845     message = msg;
846 
847     valid = true;
848   }
849 
~Attachment()850   ComposeMessage::Attachment::~Attachment () {
851     LOG (debug) << "cm: at: deconstruct";
852   }
853 
854 }
855 
856