Merge branch 'wip/543-append-failure' into 'mainline'

Fix IMAP APPEND command timeout on large email upload

Closes #543

See merge request GNOME/geary!296
This commit is contained in:
Michael Gratton 2019-08-29 23:57:03 +00:00
commit 59e80db80d
6 changed files with 69 additions and 47 deletions

View file

@ -64,7 +64,9 @@ public class Geary.Imap.AuthenticateCommand : Command {
// Wait to either get a response or a continuation request
yield this.error_lock.wait_async(cancellable);
if (this.response_literal != null) {
yield this.response_literal.serialize_data(ser, cancellable);
yield ser.push_literal_data(
this.response_literal.value.get_uint8_array(), cancellable
);
ser.push_eol(cancellable);
yield ser.flush_stream(cancellable);
}

View file

@ -178,7 +178,45 @@ public class Geary.Imap.Command : BaseObject {
// Will get notified via continuation_requested
// when server indicated the literal can be sent.
yield this.literal_spinlock.wait_async(cancellable);
yield literal.serialize_data(ser, cancellable);
// Buffer size is dependent on timeout, since we
// need to ensure we can send a full buffer before
// the timeout is up. v.92 56k baud modems have
// theoretical max upload of 48kbit/s and GSM 2G
// 40kbit/s, but typical is usually well below
// that, so assume a low end of 1kbyte/s. Hence
// buffer size needs to be less than or equal to
// (response_timeout * 1)k, rounded down to the
// nearest power of two.
uint buf_size = 1;
while (buf_size <= this.response_timeout) {
buf_size <<= 1;
}
buf_size >>= 1;
uint8[] buf = new uint8[buf_size * 1024];
GLib.InputStream data = literal.value.get_input_stream();
try {
while (true) {
size_t read;
yield data.read_all_async(
buf, Priority.DEFAULT, cancellable, out read
);
if (read <= 0) {
break;
}
buf.length = (int) read;
yield ser.push_literal_data(buf, cancellable);
this.response_timer.start();
}
} finally {
try {
yield data.close_async();
} catch (GLib.Error err) {
// Oh well
}
}
}
}
}

View file

@ -217,7 +217,7 @@ public class Geary.Imap.ListParameter : Geary.Imap.Parameter {
return stringp;
LiteralParameter? literalp = param as LiteralParameter;
if (literalp != null && literalp.get_size() <= MAX_STRING_LITERAL_LENGTH)
if (literalp != null && literalp.value.size <= MAX_STRING_LITERAL_LENGTH)
return literalp.coerce_to_string_parameter();
throw new ImapError.TYPE_ERROR("Parameter %d not of type string or literal (is %s)", index,
@ -246,7 +246,7 @@ public class Geary.Imap.ListParameter : Geary.Imap.Parameter {
return stringp;
LiteralParameter? literalp = param as LiteralParameter;
if (literalp != null && literalp.get_size() <= MAX_STRING_LITERAL_LENGTH)
if (literalp != null && literalp.value.size <= MAX_STRING_LITERAL_LENGTH)
return literalp.coerce_to_string_parameter();
throw new ImapError.TYPE_ERROR("Parameter %d not of type string or literal (is %s)", index,
@ -390,7 +390,7 @@ public class Geary.Imap.ListParameter : Geary.Imap.Parameter {
public Memory.Buffer? get_as_nullable_buffer(int index) throws ImapError {
LiteralParameter? literalp = get_if_literal(index);
if (literalp != null)
return literalp.get_buffer();
return literalp.value;
StringParameter? stringp = get_if_string(index);
if (stringp != null)

View file

@ -1,38 +1,30 @@
/* Copyright 2016 Software Freedom Conservancy Inc.
/*
* Copyright 2016 Software Freedom Conservancy Inc.
* Copyright 2019 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* A representation of an IMAP literal parameter.
*
* Because a literal parameter can hold 8-bit data, this is not a descendent of
* {@link StringParameter}, although some times literal data is used to store 8-bit text (for
* example, UTF-8).
* Because a literal parameter can hold 8-bit data, this is not a
* descendent of {@link StringParameter}, although some times literal
* data is used to store 8-bit text (for example, UTF-8).
*
* See [[http://tools.ietf.org/html/rfc3501#section-4.3]]
*/
public class Geary.Imap.LiteralParameter : Geary.Imap.Parameter {
private Memory.Buffer buffer;
public LiteralParameter(Memory.Buffer buffer) {
this.buffer = buffer;
}
/**
* Returns the number of bytes in the literal parameter's buffer.
*/
public size_t get_size() {
return buffer.size;
}
/** The value of the literal parameter. */
public Memory.Buffer value { get; private set; }
/**
* Returns the literal paremeter's buffer.
*/
public Memory.Buffer get_buffer() {
return buffer;
public LiteralParameter(Memory.Buffer value) {
this.value = value;
}
/**
@ -45,14 +37,14 @@ public class Geary.Imap.LiteralParameter : Geary.Imap.Parameter {
* for transmitting on the wire.
*/
public StringParameter coerce_to_string_parameter() {
return new UnquotedStringParameter(buffer.get_valid_utf8());
return new UnquotedStringParameter(this.value.get_valid_utf8());
}
/**
* {@inheritDoc}
*/
public override string to_string() {
return "{literal/%lub}".printf(get_size());
return "{literal/%lub}".printf(this.value.size);
}
/**
@ -60,17 +52,8 @@ public class Geary.Imap.LiteralParameter : Geary.Imap.Parameter {
*/
public override void serialize(Serializer ser, GLib.Cancellable cancellable)
throws GLib.Error {
ser.push_unquoted_string("{%lu}".printf(get_size()), cancellable);
ser.push_unquoted_string("{%lu}".printf(this.value.size), cancellable);
ser.push_eol(cancellable);
}
/**
* Serialises the literal parameter data.
*/
public async void serialize_data(Serializer ser,
GLib.Cancellable cancellable)
throws GLib.Error {
yield ser.push_literal_data(buffer, cancellable);
}
}

View file

@ -42,7 +42,7 @@ public abstract class Geary.Imap.FetchDataDecoder : BaseObject {
// because this method is called without the help of get_as_string() (which converts
// reasonably-length literals into StringParameters), do so here manually
try {
if (literalp.get_size() <= ListParameter.MAX_STRING_LITERAL_LENGTH)
if (literalp.value.size <= ListParameter.MAX_STRING_LITERAL_LENGTH)
return decode_string(literalp.coerce_to_string_parameter());
} catch (ImapError imap_err) {
// if decode_string() throws a TYPE_ERROR, retry as a LiteralParameter, otherwise
@ -197,7 +197,7 @@ public class Geary.Imap.RFC822HeaderDecoder : Geary.Imap.FetchDataDecoder {
}
protected override MessageData decode_literal(LiteralParameter literalp) throws ImapError {
return new Geary.Imap.RFC822Header(literalp.get_buffer());
return new Geary.Imap.RFC822Header(literalp.value);
}
}
@ -207,7 +207,7 @@ public class Geary.Imap.RFC822TextDecoder : Geary.Imap.FetchDataDecoder {
}
protected override MessageData decode_literal(LiteralParameter literalp) throws ImapError {
return new Geary.Imap.RFC822Text(literalp.get_buffer());
return new Geary.Imap.RFC822Text(literalp.value);
}
protected override MessageData decode_nil(NilParameter nilp) throws ImapError {
@ -221,7 +221,6 @@ public class Geary.Imap.RFC822FullDecoder : Geary.Imap.FetchDataDecoder {
}
protected override MessageData decode_literal(LiteralParameter literalp) throws ImapError {
return new Geary.Imap.RFC822Full(literalp.get_buffer());
return new Geary.Imap.RFC822Full(literalp.value);
}
}

View file

@ -103,14 +103,14 @@ public class Geary.Imap.Serializer : BaseObject {
/**
* Writes literal data to the output stream.
*/
public async void push_literal_data(Memory.Buffer buffer,
public async void push_literal_data(uint8[] buffer,
GLib.Cancellable? cancellable = null)
throws GLib.Error {
yield this.output.splice_async(
buffer.get_input_stream(),
OutputStreamSpliceFlags.NONE,
yield this.output.write_all_async(
buffer,
Priority.DEFAULT,
cancellable
cancellable,
null
);
}