/// <summary> /// Sends the next part of the command to the server. /// </summary> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// An IMAP protocol error occurred. /// </exception> public async Task <bool> StepAsync(bool doAsync) { var supportsLiteralPlus = (Engine.Capabilities & ImapCapabilities.LiteralPlus) != 0; int timeout = Engine.Stream.CanTimeout ? Engine.Stream.ReadTimeout : -1; var idle = UserData as ImapIdleContext; var result = ImapCommandResponse.None; ImapToken token; // construct and write the command tag if this is the initial state if (current == 0) { Tag = string.Format(CultureInfo.InvariantCulture, "{0}{1:D8}", Engine.TagPrefix, Engine.Tag++); var buf = Encoding.ASCII.GetBytes(Tag + " "); if (doAsync) { await Engine.Stream.WriteAsync(buf, 0, buf.Length, CancellationToken).ConfigureAwait(false); } else { Engine.Stream.Write(buf, 0, buf.Length, CancellationToken); } } do { var command = parts[current].Command; if (doAsync) { await Engine.Stream.WriteAsync(command, 0, command.Length, CancellationToken).ConfigureAwait(false); } else { Engine.Stream.Write(command, 0, command.Length, CancellationToken); } // if the server doesn't support LITERAL+ (or LITERAL-), we'll need to wait // for a "+" response before writing out the any literals... if (parts[current].WaitForContinuation) { break; } // otherwise, we can write out any and all literal tokens we have... await parts[current].Literal.WriteToAsync(Engine.Stream, doAsync, CancellationToken).ConfigureAwait(false); if (current + 1 >= parts.Count) { break; } current++; } while (true); if (doAsync) { await Engine.Stream.FlushAsync(CancellationToken).ConfigureAwait(false); } else { Engine.Stream.Flush(CancellationToken); } // now we need to read the response... do { if (Engine.State == ImapEngineState.Idle) { try { if (Engine.Stream.CanTimeout) { Engine.Stream.ReadTimeout = -1; } token = await Engine.ReadTokenAsync(doAsync, idle.LinkedToken).ConfigureAwait(false); if (Engine.Stream.CanTimeout) { Engine.Stream.ReadTimeout = timeout; } } catch (OperationCanceledException) { if (Engine.Stream.CanTimeout) { Engine.Stream.ReadTimeout = timeout; } if (idle.IsCancellationRequested) { throw; } Engine.Stream.IsConnected = true; token = await Engine.ReadTokenAsync(doAsync, CancellationToken).ConfigureAwait(false); } } else { token = await Engine.ReadTokenAsync(doAsync, CancellationToken).ConfigureAwait(false); } if (token.Type == ImapTokenType.Atom && token.Value.ToString() == "+") { // we've gotten a continuation response from the server var text = (await Engine.ReadLineAsync(doAsync, CancellationToken).ConfigureAwait(false)).Trim(); // if we've got a Literal pending, the '+' means we can send it now... if (!supportsLiteralPlus && parts[current].Literal != null) { await parts[current].Literal.WriteToAsync(Engine.Stream, doAsync, CancellationToken).ConfigureAwait(false); break; } if (ContinuationHandler != null) { await ContinuationHandler(Engine, this, text, doAsync).ConfigureAwait(false); } else if (doAsync) { await Engine.Stream.WriteAsync(NewLine, 0, NewLine.Length, CancellationToken).ConfigureAwait(false); await Engine.Stream.FlushAsync(CancellationToken).ConfigureAwait(false); } else { Engine.Stream.Write(NewLine, 0, NewLine.Length, CancellationToken); Engine.Stream.Flush(CancellationToken); } } else if (token.Type == ImapTokenType.Asterisk) { // we got an untagged response, let the engine handle this... await Engine.ProcessUntaggedResponseAsync(doAsync, CancellationToken).ConfigureAwait(false); } else if (token.Type == ImapTokenType.Atom && (string)token.Value == Tag) { // the next token should be "OK", "NO", or "BAD" token = await Engine.ReadTokenAsync(doAsync, CancellationToken).ConfigureAwait(false); ImapEngine.AssertToken(token, ImapTokenType.Atom, "Syntax error in tagged response. Unexpected token: {0}", token); string atom = (string)token.Value; switch (atom) { case "BAD": result = ImapCommandResponse.Bad; break; case "OK": result = ImapCommandResponse.Ok; break; case "NO": result = ImapCommandResponse.No; break; default: throw ImapEngine.UnexpectedToken("Syntax error in tagged response. Unexpected token: {0}", token); } token = await Engine.ReadTokenAsync(doAsync, CancellationToken).ConfigureAwait(false); if (token.Type == ImapTokenType.OpenBracket) { var code = await Engine.ParseResponseCodeAsync(true, doAsync, CancellationToken).ConfigureAwait(false); RespCodes.Add(code); break; } if (token.Type != ImapTokenType.Eoln) { // consume the rest of the line... var line = await Engine.ReadLineAsync(doAsync, CancellationToken).ConfigureAwait(false); ResponseText = ((string)(token.Value) + line).TrimEnd(); break; } } else if (token.Type == ImapTokenType.OpenBracket) { // Note: this is a work-around for broken IMAP servers like Office365.com that // return RESP-CODES that are not preceded by "* OK " such as the example in // issue #115 (https://github.com/jstedfast/MailKit/issues/115). var code = await Engine.ParseResponseCodeAsync(false, doAsync, CancellationToken).ConfigureAwait(false); RespCodes.Add(code); } else { // no clue what we got... throw ImapEngine.UnexpectedToken("Syntax error in response. Unexpected token: {0}", token); } } while (true); // the status should always be Active at this point, but just to be sure... if (Status == ImapCommandStatus.Active) { current++; if (current >= parts.Count || result != ImapCommandResponse.None) { Status = ImapCommandStatus.Complete; Response = result; return(false); } } return(true); }