Since both submitting a command no longer requires a cancellable, and it is desirable to avoid sending a queued command that has already been cancelled beforehand, add a new `Command.should_send` Cancellable property to specify if a command should (still) be sent or not, and stop passing a cancellable to ClientSession when submitting commands. Allow call sites to pass in existing cancellable objects, and thus also add it it as a ctor property to the Command class and all subclasses. Lastly, throw a cancelled exception in `wait_until_complete` if send was cancelled so that the caller knows what happened. Remove redundant cancellable argument from `Imap.Client.command_transaction_async` and rename it to `submit_command` to make it more obvious about what it does.
120 lines
4 KiB
Vala
120 lines
4 KiB
Vala
/*
|
|
* Copyright 2018 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.
|
|
*/
|
|
|
|
/**
|
|
* The IMAP AUTHENTICATE command.
|
|
*
|
|
* See [[http://tools.ietf.org/html/rfc3501#section-6.2.2]]
|
|
*/
|
|
public class Geary.Imap.AuthenticateCommand : Command {
|
|
|
|
|
|
public const string NAME = "authenticate";
|
|
|
|
private const string OAUTH2_METHOD = "xoauth2";
|
|
private const string OAUTH2_RESP = "user=%s\001auth=Bearer %s\001\001";
|
|
|
|
|
|
public string method { get; private set; }
|
|
|
|
private LiteralParameter? response_literal = null;
|
|
private bool serialised = false;
|
|
private Geary.Nonblocking.Spinlock error_lock;
|
|
private GLib.Cancellable error_cancellable = new GLib.Cancellable();
|
|
|
|
|
|
private AuthenticateCommand(string method,
|
|
string data,
|
|
GLib.Cancellable? should_send) {
|
|
base(NAME, { method, data }, should_send);
|
|
this.method = method;
|
|
this.error_lock = new Geary.Nonblocking.Spinlock(this.error_cancellable);
|
|
}
|
|
|
|
public AuthenticateCommand.oauth2(string user,
|
|
string token,
|
|
GLib.Cancellable? should_send) {
|
|
string encoded_token = Base64.encode(
|
|
OAUTH2_RESP.printf(user, token).data
|
|
);
|
|
this(OAUTH2_METHOD, encoded_token, should_send);
|
|
}
|
|
|
|
internal override async void send(Serializer ser,
|
|
GLib.Cancellable cancellable)
|
|
throws GLib.Error {
|
|
yield base.send(ser, cancellable);
|
|
this.serialised = true;
|
|
|
|
// Need to manually flush here since the connection will be
|
|
// waiting for all pending commands to complete before
|
|
// flushing it itself
|
|
yield ser.flush_stream(cancellable);
|
|
}
|
|
|
|
public override string to_string() {
|
|
return "%s %s %s <token>".printf(
|
|
tag.to_string(), this.name, this.method
|
|
);
|
|
}
|
|
|
|
internal override async void send_wait(Serializer ser,
|
|
GLib.Cancellable cancellable)
|
|
throws GLib.Error {
|
|
// Wait to either get a response or a continuation request
|
|
yield this.error_lock.wait_async(cancellable);
|
|
if (this.response_literal != null) {
|
|
yield ser.push_literal_data(
|
|
this.response_literal.value.get_uint8_array(), cancellable
|
|
);
|
|
ser.push_eol(cancellable);
|
|
yield ser.flush_stream(cancellable);
|
|
}
|
|
|
|
yield wait_until_complete(cancellable);
|
|
}
|
|
|
|
internal override void completed(StatusResponse new_status)
|
|
throws ImapError {
|
|
this.error_lock.blind_notify();
|
|
base.completed(new_status);
|
|
}
|
|
|
|
internal override void continuation_requested(ContinuationResponse response)
|
|
throws ImapError {
|
|
if (!this.serialised) {
|
|
// Allow any args sent as literals to be processed
|
|
// normally
|
|
base.continuation_requested(response);
|
|
} else {
|
|
if (this.method != AuthenticateCommand.OAUTH2_METHOD ||
|
|
this.response_literal != null) {
|
|
cancel_send();
|
|
throw new ImapError.INVALID(
|
|
"Unexpected AUTHENTICATE continuation request"
|
|
);
|
|
}
|
|
|
|
// Continuation will be a Base64 encoded JSON blob and which
|
|
// indicates a login failure. We don't really care about that
|
|
// (do we?) though since once we acknowledge it with a
|
|
// zero-length response the server will respond with an IMAP
|
|
// error.
|
|
this.response_literal = new LiteralParameter(
|
|
Geary.Memory.EmptyBuffer.instance
|
|
);
|
|
// Notify serialisation to continue
|
|
this.error_lock.blind_notify();
|
|
}
|
|
}
|
|
|
|
protected override void cancel_send() {
|
|
base.cancel_send();
|
|
this.error_cancellable.cancel();
|
|
}
|
|
|
|
}
|