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