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:
parent
668ced74eb
commit
a11838e40b
4 changed files with 69 additions and 12 deletions
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue