diff --git a/src/engine/imap-db/outbox/smtp-outbox-folder.vala b/src/engine/imap-db/outbox/smtp-outbox-folder.vala index b498a693..e4ce0314 100644 --- a/src/engine/imap-db/outbox/smtp-outbox-folder.vala +++ b/src/engine/imap-db/outbox/smtp-outbox-folder.vala @@ -226,10 +226,11 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractLocalFolder, Geary.FolderSu int email_count = 0; OutboxRow? row = null; yield db.exec_transaction_async(Db.TransactionType.WR, (cx) => { + // save in database ready for SMTP, but without dot-stuffing Db.Statement stmt = cx.prepare( "INSERT INTO SmtpOutboxTable (message, ordering)" + "VALUES (?, (SELECT COALESCE(MAX(ordering), 0) + 1 FROM SmtpOutboxTable))"); - stmt.bind_string(0, rfc822.get_body_rfc822_buffer().to_string()); + stmt.bind_string_buffer(0, rfc822.get_body_rfc822_buffer_smtp(false)); int64 id = stmt.exec_insert(cancellable); diff --git a/src/engine/rfc822/rfc822-message.vala b/src/engine/rfc822/rfc822-message.vala index 060bc840..c0536a0d 100644 --- a/src/engine/rfc822/rfc822-message.vala +++ b/src/engine/rfc822/rfc822-message.vala @@ -400,8 +400,23 @@ public class Geary.RFC822.Message : BaseObject { return (addrs.size > 0) ? addrs : null; } - public Memory.Buffer get_body_rfc822_buffer() { - return new Geary.Memory.StringBuffer(message.to_string()); + /** + * Returns the {@link Message} as a {@link Memory.Buffer} suitable for in-memory use (i.e. + * with native linefeed characters). + */ + public Memory.Buffer get_body_rfc822_buffer_native() throws RFC822Error { + return message_to_memory_buffer(false, false); + } + + /** + * Returns the {@link Message} as a {@link Memory.Buffer} suitable for transmission or + * storage (i.e. using protocol-specific linefeeds). + * + * The buffer can also be dot-stuffed if required. See + * [[http://tools.ietf.org/html/rfc2821#section-4.5.2]] + */ + public Memory.Buffer get_body_rfc822_buffer_smtp(bool dotstuffed) throws RFC822Error { + return message_to_memory_buffer(true, dotstuffed); } public Memory.Buffer get_first_mime_part_of_content_type(string content_type, bool to_html = false) @@ -610,7 +625,21 @@ public class Geary.RFC822.Message : BaseObject { messages.add(new Geary.RFC822.Message.from_gmime_message(sub_message)); } } - + + private Memory.Buffer message_to_memory_buffer(bool encoded, bool dotstuffed) throws RFC822Error { + ByteArray byte_array = new ByteArray(); + GMime.StreamMem stream = new GMime.StreamMem.with_byte_array(byte_array); + stream.set_owner(false); + + GMime.StreamFilter stream_filter = new GMime.StreamFilter(stream); + stream_filter.add(new GMime.FilterCRLF(encoded, dotstuffed)); + + message.write_to_stream(stream_filter); + stream_filter.flush(); + + return new Memory.ByteBuffer.from_byte_array(byte_array); + } + private Memory.Buffer mime_part_to_memory_buffer(GMime.Part part, bool to_utf8 = false, bool to_html = false) throws RFC822Error { diff --git a/src/engine/smtp/smtp-client-connection.vala b/src/engine/smtp/smtp-client-connection.vala index d5cdc1ff..3582bf85 100644 --- a/src/engine/smtp/smtp-client-connection.vala +++ b/src/engine/smtp/smtp-client-connection.vala @@ -93,14 +93,16 @@ public class Geary.Smtp.ClientConnection { /** * Sends a block of data (mail message) by first issuing the DATA command and transmitting - * the block if the appropriate response is sent. The data block should *not* have the SMTP - * data terminator (). The caller is also responsible to ensure that this - * pattern does not occur anywhere in the data, causing an early termination of the message. + * the block if the appropriate response is sent. + * + * Dot-stuffing is performed on the data if !already_dotstuffed. See + * [[http://tools.ietf.org/html/rfc2821#section-4.5.2]] * * Returns the final Response of the transaction. If the ResponseCode is not a successful * completion, the message should not be considered sent. */ - public async Response send_data_async(uint8[] data, Cancellable? cancellable = null) throws Error { + public async Response send_data_async(Memory.Buffer data, bool already_dotstuffed, + Cancellable? cancellable = null) throws Error { check_connected(); // In the case of DATA, want to receive an intermediate response code, specifically 354 @@ -108,10 +110,35 @@ public class Geary.Smtp.ClientConnection { if (!response.code.is_start_data()) return response; - Logging.debug(Logging.Flag.NETWORK, "[%s] SMTP Data: <%ldb>", to_string(), data.length); + Logging.debug(Logging.Flag.NETWORK, "[%s] SMTP Data: <%ldb>", to_string(), data.size); - yield Stream.write_all_async(douts, data, 0, -1, Priority.DEFAULT, cancellable); - douts.put_string(DataFormat.DATA_TERMINATOR); + if (!already_dotstuffed) { + // By using DataStreamNewlineType.ANY, we're assured to get each line and convert to + // a proper line terminator for SMTP + DataInputStream dins = new DataInputStream(data.get_input_stream()); + dins.set_newline_type(DataStreamNewlineType.ANY); + + // Read each line and dot-stuff if necessary + for (;;) { + size_t length; + string? line = yield dins.read_line_async(Priority.DEFAULT, cancellable, out length); + if (line == null) + break; + + // stuffing + if (!already_dotstuffed && line[0] == '.') + yield douts.write_async(".".data, Priority.DEFAULT, cancellable); + + yield douts.write_async(line.data, Priority.DEFAULT, cancellable); + yield douts.write_async(DataFormat.LINE_TERMINATOR.data, Priority.DEFAULT, cancellable); + } + } else { + // ready to go, send and commit + yield douts.write_bytes_async(data.get_bytes()); + } + + // terminate buffer and flush to server + yield douts.write_async(DataFormat.DATA_TERMINATOR.data, Priority.DEFAULT, cancellable); yield douts.flush_async(Priority.DEFAULT, cancellable); return yield recv_response_async(cancellable); diff --git a/src/engine/smtp/smtp-client-session.vala b/src/engine/smtp/smtp-client-session.vala index 57ea5322..9b661205 100644 --- a/src/engine/smtp/smtp-client-session.vala +++ b/src/engine/smtp/smtp-client-session.vala @@ -173,7 +173,7 @@ public class Geary.Smtp.ClientSession { // DATA Geary.RFC822.Message email_copy = new Geary.RFC822.Message.without_bcc(email); - response = yield cx.send_data_async(email_copy.get_body_rfc822_buffer().get_bytes().get_data(), + response = yield cx.send_data_async(email_copy.get_body_rfc822_buffer_smtp(true), true, cancellable); if (!response.code.is_success_completed()) response.throw_error("Unable to send message");