Perform SMTP dot-stuffing: Closes #7173

Ensures dot-stuffing occurs one way or another before transmitting
SMTP data.  Also clears up native/CRLF linefeed issues with
RFC822.Message.
This commit is contained in:
Jim Nelson 2013-06-27 15:27:09 -07:00
parent 668ced74eb
commit a11838e40b
4 changed files with 69 additions and 12 deletions

View file

@ -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);

View file

@ -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 {

View file

@ -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 (<CR><LF><dot><CR><LF>). 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);

View file

@ -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");