// // ImapStream.cs // // Author: Jeffrey Stedfast // // Copyright (c) 2013-2020 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // using System; using System.IO; using System.Text; using System.Threading; using System.Net.Sockets; using System.Globalization; using System.Threading.Tasks; using MimeKit.IO; using Buffer = System.Buffer; using SslStream = MailKit.Net.SslStream; using NetworkStream = MailKit.Net.NetworkStream; namespace MailKit.Net.Imap { /// /// An enumeration of the possible IMAP streaming modes. /// /// /// Normal operation is done in the mode, /// but when reading literal string data, the /// mode should be used. /// enum ImapStreamMode { /// /// Reads 1 token at a time. /// Token, /// /// Reads literal string data. /// Literal } class ImapStream : Stream, ICancellableStream { public const string AtomSpecials = "(){%*\\\"\n"; public const string DefaultSpecials = "[]" + AtomSpecials; const int ReadAheadSize = 128; const int BlockSize = 4096; const int PadSize = 4; static readonly Encoding Latin1; static readonly Encoding UTF8; // I/O buffering readonly byte[] input = new byte[ReadAheadSize + BlockSize + PadSize]; const int inputStart = ReadAheadSize; int inputIndex = ReadAheadSize; int inputEnd = ReadAheadSize; readonly byte[] output = new byte[BlockSize]; int outputIndex; readonly IProtocolLogger logger; int literalDataLeft; ImapToken nextToken; bool disposed; static ImapStream () { UTF8 = Encoding.GetEncoding (65001, new EncoderExceptionFallback (), new DecoderExceptionFallback ()); try { Latin1 = Encoding.GetEncoding (28591); } catch (NotSupportedException) { Latin1 = Encoding.GetEncoding (1252); } } /// /// Initializes a new instance of the class. /// /// /// Creates a new . /// /// The underlying network stream. /// The protocol logger. public ImapStream (Stream source, IProtocolLogger protocolLogger) { logger = protocolLogger; IsConnected = true; Stream = source; } /// /// Get or sets the underlying network stream. /// /// /// Gets or sets the underlying network stream. /// /// The underlying network stream. public Stream Stream { get; internal set; } /// /// Get or sets the mode used for reading. /// /// /// Gets or sets the mode used for reading. /// /// The mode. public ImapStreamMode Mode { get; set; } /// /// Get the length of the literal. /// /// /// Gets the length of the literal. /// /// The length of the literal. public int LiteralLength { get { return literalDataLeft; } internal set { literalDataLeft = value; } } /// /// Get whether or not the stream is connected. /// /// /// Gets whether or not the stream is connected. /// /// true if the stream is connected; otherwise, false. public bool IsConnected { get; private set; } /// /// Get whether the stream supports reading. /// /// /// Gets whether the stream supports reading. /// /// true if the stream supports reading; otherwise, false. public override bool CanRead { get { return Stream.CanRead; } } /// /// Get whether the stream supports writing. /// /// /// Gets whether the stream supports writing. /// /// true if the stream supports writing; otherwise, false. public override bool CanWrite { get { return Stream.CanWrite; } } /// /// Get whether the stream supports seeking. /// /// /// Gets whether the stream supports seeking. /// /// true if the stream supports seeking; otherwise, false. public override bool CanSeek { get { return false; } } /// /// Get whether the stream supports I/O timeouts. /// /// /// Gets whether the stream supports I/O timeouts. /// /// true if the stream supports I/O timeouts; otherwise, false. public override bool CanTimeout { get { return Stream.CanTimeout; } } /// /// Get or set a value, in milliseconds, that determines how long the stream will attempt to read before timing out. /// /// /// Gets or sets a value, in milliseconds, that determines how long the stream will attempt to read before timing out. /// /// A value, in milliseconds, that determines how long the stream will attempt to read before timing out. /// The read timeout. public override int ReadTimeout { get { return Stream.ReadTimeout; } set { Stream.ReadTimeout = value; } } /// /// Get or set a value, in milliseconds, that determines how long the stream will attempt to write before timing out. /// /// /// Gets or sets a value, in milliseconds, that determines how long the stream will attempt to write before timing out. /// /// A value, in milliseconds, that determines how long the stream will attempt to write before timing out. /// The write timeout. public override int WriteTimeout { get { return Stream.WriteTimeout; } set { Stream.WriteTimeout = value; } } /// /// Get or set the position within the current stream. /// /// /// Gets or sets the position within the current stream. /// /// The current position within the stream. /// The position of the stream. /// /// An I/O error occurred. /// /// /// The stream does not support seeking. /// /// /// The stream has been disposed. /// public override long Position { get { return Stream.Position; } set { throw new NotSupportedException (); } } /// /// Get the length of the stream, in bytes. /// /// /// Gets the length of the stream, in bytes. /// /// A long value representing the length of the stream in bytes. /// The length of the stream. /// /// The stream does not support seeking. /// /// /// The stream has been disposed. /// public override long Length { get { return Stream.Length; } } async Task ReadAheadAsync (int atleast, bool doAsync, CancellationToken cancellationToken) { int left = inputEnd - inputIndex; if (left >= atleast) return left; int start = inputStart; int end = inputEnd; int nread; if (left > 0) { int index = inputIndex; // attempt to align the end of the remaining input with ReadAheadSize if (index >= start) { start -= Math.Min (ReadAheadSize, left); Buffer.BlockCopy (input, index, input, start, left); index = start; start += left; } else if (index > 0) { int shift = Math.Min (index, end - start); Buffer.BlockCopy (input, index, input, index - shift, left); index -= shift; start = index + left; } else { // we can't shift... start = end; } inputIndex = index; inputEnd = start; } else { inputIndex = start; inputEnd = start; } end = input.Length - PadSize; try { var network = Stream as NetworkStream; cancellationToken.ThrowIfCancellationRequested (); if (doAsync) { nread = await Stream.ReadAsync (input, start, end - start, cancellationToken).ConfigureAwait (false); } else { network?.Poll (SelectMode.SelectRead, cancellationToken); nread = Stream.Read (input, start, end - start); } if (nread > 0) { logger.LogServer (input, start, nread); inputEnd += nread; } else { throw new ImapProtocolException ("The IMAP server has unexpectedly disconnected."); } if (network == null) cancellationToken.ThrowIfCancellationRequested (); } catch { IsConnected = false; throw; } return inputEnd - inputIndex; } static void ValidateArguments (byte[] buffer, int offset, int count) { if (buffer == null) throw new ArgumentNullException (nameof (buffer)); if (offset < 0 || offset > buffer.Length) throw new ArgumentOutOfRangeException (nameof (offset)); if (count < 0 || count > (buffer.Length - offset)) throw new ArgumentOutOfRangeException (nameof (count)); } void CheckDisposed () { if (disposed) throw new ObjectDisposedException (nameof (ImapStream)); } async Task ReadAsync (byte[] buffer, int offset, int count, bool doAsync, CancellationToken cancellationToken) { CheckDisposed (); ValidateArguments (buffer, offset, count); if (Mode != ImapStreamMode.Literal) return 0; count = Math.Min (count, literalDataLeft); int length = inputEnd - inputIndex; int n; if (length < count && length <= ReadAheadSize) await ReadAheadAsync (BlockSize, doAsync, cancellationToken).ConfigureAwait (false); length = inputEnd - inputIndex; n = Math.Min (count, length); Buffer.BlockCopy (input, inputIndex, buffer, offset, n); literalDataLeft -= n; inputIndex += n; if (literalDataLeft == 0) Mode = ImapStreamMode.Token; return n; } /// /// Reads a sequence of bytes from the stream and advances the position /// within the stream by the number of bytes read. /// /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many /// bytes are not currently available, or zero (0) if the end of the stream has been reached. /// The buffer. /// The buffer offset. /// The number of bytes to read. /// The cancellation token. /// /// is null. /// /// /// is less than zero or greater than the length of . /// -or- /// The is not large enough to contain bytes strting /// at the specified . /// /// /// The stream has been disposed. /// /// /// The stream is in token mode (see ). /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// public int Read (byte[] buffer, int offset, int count, CancellationToken cancellationToken) { return ReadAsync (buffer, offset, count, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Reads a sequence of bytes from the stream and advances the position /// within the stream by the number of bytes read. /// /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many /// bytes are not currently available, or zero (0) if the end of the stream has been reached. /// The buffer. /// The buffer offset. /// The number of bytes to read. /// /// is null. /// /// /// is less than zero or greater than the length of . /// -or- /// The is not large enough to contain bytes strting /// at the specified . /// /// /// The stream has been disposed. /// /// /// The stream is in token mode (see ). /// /// /// An I/O error occurred. /// public override int Read (byte[] buffer, int offset, int count) { return Read (buffer, offset, count, CancellationToken.None); } /// /// Reads a sequence of bytes from the stream and advances the position /// within the stream by the number of bytes read. /// /// /// Reads a sequence of bytes from the stream and advances the position /// within the stream by the number of bytes read. /// /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many /// bytes are not currently available, or zero (0) if the end of the stream has been reached. /// The buffer. /// The buffer offset. /// The number of bytes to read. /// The cancellation token. /// /// is null. /// /// /// is less than zero or greater than the length of . /// -or- /// The is not large enough to contain bytes strting /// at the specified . /// /// /// The stream has been disposed. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// public override Task ReadAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken) { return ReadAsync (buffer, offset, count, true, cancellationToken); } static bool IsAtom (byte c, string specials) { return !IsCtrl (c) && !IsWhiteSpace (c) && specials.IndexOf ((char) c) == -1; } static bool IsCtrl (byte c) { return c <= 0x1f || c == 0x7f; } static bool IsWhiteSpace (byte c) { return c == (byte) ' ' || c == (byte) '\t' || c == (byte) '\r'; } async Task ReadQuotedStringTokenAsync (bool doAsync, CancellationToken cancellationToken) { bool escaped = false; // skip over the opening '"' inputIndex++; using (var memory = new MemoryStream ()) { do { while (inputIndex < inputEnd) { if (input[inputIndex] == (byte) '"' && !escaped) break; if (input[inputIndex] == (byte) '\\' && !escaped) { escaped = true; } else { memory.WriteByte (input[inputIndex]); escaped = false; } inputIndex++; } if (inputIndex + 1 < inputEnd) { // skip over closing '"' inputIndex++; // Note: Some IMAP servers do not properly escape double-quotes inside // of a qstring token and so, as an attempt at working around this // problem, check that the closing '"' character is not immediately // followed by any character that we would expect immediately following // a qstring token. // // See https://github.com/jstedfast/MailKit/issues/485 for details. if ("]) \r\n".IndexOf ((char) input[inputIndex]) != -1) break; memory.WriteByte ((byte) '"'); continue; } await ReadAheadAsync (2, doAsync, cancellationToken).ConfigureAwait (false); } while (true); #if !NETSTANDARD1_3 && !NETSTANDARD1_6 var buffer = memory.GetBuffer (); #else var buffer = memory.ToArray (); #endif int length = (int) memory.Length; return new ImapToken (ImapTokenType.QString, Encoding.UTF8.GetString (buffer, 0, length)); } } async Task ReadAtomStringAsync (bool flag, string specials, bool doAsync, CancellationToken cancellationToken) { using (var memory = new MemoryStream ()) { do { input[inputEnd] = (byte) '\n'; if (flag && memory.Length == 0 && input[inputIndex] == (byte) '*') { // this is a special wildcard flag inputIndex++; return "*"; } while (IsAtom (input[inputIndex], specials)) memory.WriteByte (input[inputIndex++]); if (inputIndex < inputEnd) break; await ReadAheadAsync (1, doAsync, cancellationToken).ConfigureAwait (false); } while (true); var count = (int) memory.Length; #if !NETSTANDARD1_3 && !NETSTANDARD1_6 var buf = memory.GetBuffer (); #else var buf = memory.ToArray (); #endif try { return UTF8.GetString (buf, 0, count); } catch (DecoderFallbackException) { return Latin1.GetString (buf, 0, count); } } } async Task ReadAtomTokenAsync (string specials, bool doAsync, CancellationToken cancellationToken) { var atom = await ReadAtomStringAsync (false, specials, doAsync, cancellationToken).ConfigureAwait (false); return atom == "NIL" ? new ImapToken (ImapTokenType.Nil, atom) : new ImapToken (ImapTokenType.Atom, atom); } async Task ReadFlagTokenAsync (string specials, bool doAsync, CancellationToken cancellationToken) { inputIndex++; var flag = "\\" + await ReadAtomStringAsync (true, specials, doAsync, cancellationToken).ConfigureAwait (false); return new ImapToken (ImapTokenType.Flag, flag); } async Task ReadLiteralTokenAsync (bool doAsync, CancellationToken cancellationToken) { var builder = new StringBuilder (); // skip over the '{' inputIndex++; do { input[inputEnd] = (byte) '}'; while (input[inputIndex] != (byte) '}' && input[inputIndex] != '+') builder.Append ((char) input[inputIndex++]); if (inputIndex < inputEnd) break; await ReadAheadAsync (1, doAsync, cancellationToken).ConfigureAwait (false); } while (true); if (input[inputIndex] == (byte) '+') inputIndex++; // technically, we need "}\r\n", but in order to be more lenient, we'll accept "}\n" await ReadAheadAsync (2, doAsync, cancellationToken).ConfigureAwait (false); if (input[inputIndex] != (byte) '}') { // PROTOCOL ERROR... but maybe we can work around it? do { input[inputEnd] = (byte) '}'; while (input[inputIndex] != (byte) '}') inputIndex++; if (inputIndex < inputEnd) break; await ReadAheadAsync (1, doAsync, cancellationToken).ConfigureAwait (false); } while (true); } // skip over the '}' inputIndex++; // read until we get a new line... do { input[inputEnd] = (byte) '\n'; while (input[inputIndex] != (byte) '\n') inputIndex++; if (inputIndex < inputEnd) break; await ReadAheadAsync (1, doAsync, cancellationToken).ConfigureAwait (false); } while (true); // skip over the '\n' inputIndex++; if (!int.TryParse (builder.ToString (), NumberStyles.None, CultureInfo.InvariantCulture, out literalDataLeft) || literalDataLeft < 0) return new ImapToken (ImapTokenType.Error, builder.ToString ()); Mode = ImapStreamMode.Literal; return new ImapToken (ImapTokenType.Literal, literalDataLeft); } internal async Task ReadTokenAsync (string specials, bool doAsync, CancellationToken cancellationToken) { CheckDisposed (); if (nextToken != null) { var token = nextToken; nextToken = null; return token; } input[inputEnd] = (byte) '\n'; // skip over white space between tokens... do { while (IsWhiteSpace (input[inputIndex])) inputIndex++; if (inputIndex < inputEnd) break; await ReadAheadAsync (1, doAsync, cancellationToken).ConfigureAwait (false); input[inputEnd] = (byte) '\n'; } while (true); char c = (char) input[inputIndex]; if (c == '"') return await ReadQuotedStringTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); if (c == '{') return await ReadLiteralTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); if (c == '\\') return await ReadFlagTokenAsync (specials, doAsync, cancellationToken).ConfigureAwait (false); if (IsAtom (input[inputIndex], specials)) return await ReadAtomTokenAsync (specials, doAsync, cancellationToken).ConfigureAwait (false); // special character token inputIndex++; return new ImapToken ((ImapTokenType) c, c); } internal Task ReadTokenAsync (bool doAsync, CancellationToken cancellationToken) { return ReadTokenAsync (DefaultSpecials, doAsync, cancellationToken); } /// /// Reads the next available token from the stream. /// /// The token. /// The cancellation token. /// /// The stream has been disposed. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// public ImapToken ReadToken (CancellationToken cancellationToken) { return ReadTokenAsync (false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously reads the next available token from the stream. /// /// The token. /// The cancellation token. /// /// The stream has been disposed. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// public Task ReadTokenAsync (CancellationToken cancellationToken) { return ReadTokenAsync (true, cancellationToken); } /// /// Ungets a token. /// /// The token. public void UngetToken (ImapToken token) { if (token == null) throw new ArgumentNullException (nameof (token)); nextToken = token; } async Task ReadLineAsync (Stream ostream, bool doAsync, CancellationToken cancellationToken) { CheckDisposed (); if (inputIndex == inputEnd) await ReadAheadAsync (1, doAsync, cancellationToken).ConfigureAwait (false); unsafe { fixed (byte* inbuf = input) { byte* start, inptr, inend; int offset = inputIndex; int count; start = inbuf + inputIndex; inend = inbuf + inputEnd; *inend = (byte) '\n'; inptr = start; // FIXME: use SIMD to optimize this while (*inptr != (byte) '\n') inptr++; inputIndex = (int) (inptr - inbuf); count = (int) (inptr - start); if (inptr == inend) { ostream.Write (input, offset, count); return false; } // consume the '\n' inputIndex++; count++; ostream.Write (input, offset, count); return true; } } } /// /// Reads a single line of input from the stream. /// /// /// This method should be called in a loop until it returns true. /// /// true, if reading the line is complete, false otherwise. /// The output stream to write the line data into. /// The cancellation token. /// /// The stream has been disposed. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// internal bool ReadLine (Stream ostream, CancellationToken cancellationToken) { return ReadLineAsync (ostream, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously reads a single line of input from the stream. /// /// /// This method should be called in a loop until it returns true. /// /// true, if reading the line is complete, false otherwise. /// The output stream to write the line data into. /// The cancellation token. /// /// The stream has been disposed. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// internal Task ReadLineAsync (Stream ostream, CancellationToken cancellationToken) { return ReadLineAsync (ostream, true, cancellationToken); } async Task WriteAsync (byte[] buffer, int offset, int count, bool doAsync, CancellationToken cancellationToken) { CheckDisposed (); ValidateArguments (buffer, offset, count); try { var network = NetworkStream.Get (Stream); int index = offset; int left = count; while (left > 0) { int n = Math.Min (BlockSize - outputIndex, left); if (outputIndex > 0 || n < BlockSize) { // append the data to the output buffer Buffer.BlockCopy (buffer, index, output, outputIndex, n); outputIndex += n; index += n; left -= n; } if (outputIndex == BlockSize) { // flush the output buffer if (doAsync) { await Stream.WriteAsync (output, 0, BlockSize, cancellationToken).ConfigureAwait (false); } else { network?.Poll (SelectMode.SelectWrite, cancellationToken); Stream.Write (output, 0, BlockSize); } logger.LogClient (output, 0, BlockSize); outputIndex = 0; } if (outputIndex == 0) { // write blocks of data to the stream without buffering while (left >= BlockSize) { if (doAsync) { await Stream.WriteAsync (buffer, index, BlockSize, cancellationToken).ConfigureAwait (false); } else { network?.Poll (SelectMode.SelectWrite, cancellationToken); Stream.Write (buffer, index, BlockSize); } logger.LogClient (buffer, index, BlockSize); index += BlockSize; left -= BlockSize; } } } } catch (Exception ex) { IsConnected = false; if (!(ex is OperationCanceledException)) cancellationToken.ThrowIfCancellationRequested (); throw; } } /// /// Writes a sequence of bytes to the stream and advances the current /// position within this stream by the number of bytes written. /// /// /// Writes a sequence of bytes to the stream and advances the current /// position within this stream by the number of bytes written. /// /// The buffer to write. /// The offset of the first byte to write. /// The number of bytes to write. /// The cancellation token. /// /// is null. /// /// /// is less than zero or greater than the length of . /// -or- /// The is not large enough to contain bytes strting /// at the specified . /// /// /// The stream has been disposed. /// /// /// The stream does not support writing. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// public void Write (byte[] buffer, int offset, int count, CancellationToken cancellationToken) { WriteAsync (buffer, offset, count, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Writes a sequence of bytes to the stream and advances the current /// position within this stream by the number of bytes written. /// /// /// Writes a sequence of bytes to the stream and advances the current /// position within this stream by the number of bytes written. /// /// The buffer to write. /// The offset of the first byte to write. /// The number of bytes to write. /// /// is null. /// /// /// is less than zero or greater than the length of . /// -or- /// The is not large enough to contain bytes strting /// at the specified . /// /// /// The stream has been disposed. /// /// /// The stream does not support writing. /// /// /// An I/O error occurred. /// public override void Write (byte[] buffer, int offset, int count) { Write (buffer, offset, count, CancellationToken.None); } /// /// Writes a sequence of bytes to the stream and advances the current /// position within this stream by the number of bytes written. /// /// /// Writes a sequence of bytes to the stream and advances the current /// position within this stream by the number of bytes written. /// /// A task that represents the asynchronous write operation. /// The buffer to write. /// The offset of the first byte to write. /// The number of bytes to write. /// The cancellation token. /// /// is null. /// /// /// is less than zero or greater than the length of . /// -or- /// The is not large enough to contain bytes strting /// at the specified . /// /// /// The stream has been disposed. /// /// /// The stream does not support writing. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// public override Task WriteAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken) { return WriteAsync (buffer, offset, count, true, cancellationToken); } async Task FlushAsync (bool doAsync, CancellationToken cancellationToken) { CheckDisposed (); if (outputIndex == 0) return; try { if (doAsync) { await Stream.WriteAsync (output, 0, outputIndex, cancellationToken).ConfigureAwait (false); await Stream.FlushAsync (cancellationToken).ConfigureAwait (false); } else { var network = NetworkStream.Get (Stream); network?.Poll (SelectMode.SelectWrite, cancellationToken); Stream.Write (output, 0, outputIndex); Stream.Flush (); } logger.LogClient (output, 0, outputIndex); outputIndex = 0; } catch (Exception ex) { IsConnected = false; if (!(ex is OperationCanceledException)) cancellationToken.ThrowIfCancellationRequested (); throw; } } /// /// Clears all output buffers for this stream and causes any buffered data to be written /// to the underlying device. /// /// /// Clears all output buffers for this stream and causes any buffered data to be written /// to the underlying device. /// /// The cancellation token. /// /// The stream has been disposed. /// /// /// The stream does not support writing. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// public void Flush (CancellationToken cancellationToken) { FlushAsync (false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Clears all output buffers for this stream and causes any buffered data to be written /// to the underlying device. /// /// /// Clears all output buffers for this stream and causes any buffered data to be written /// to the underlying device. /// /// /// The stream has been disposed. /// /// /// The stream does not support writing. /// /// /// An I/O error occurred. /// public override void Flush () { Flush (CancellationToken.None); } /// /// Clears all buffers for this stream and causes any buffered data to be written /// to the underlying device. /// /// /// Clears all buffers for this stream and causes any buffered data to be written /// to the underlying device. /// /// A task that represents the asynchronous flush operation. /// The cancellation token. /// /// The stream has been disposed. /// /// /// The stream does not support writing. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// public override Task FlushAsync (CancellationToken cancellationToken) { return FlushAsync (true, cancellationToken); } /// /// Sets the position within the current stream. /// /// /// It is not possible to seek within a . /// /// The new position within the stream. /// The offset into the stream relative to the . /// The origin to seek from. /// /// The stream does not support seeking. /// public override long Seek (long offset, SeekOrigin origin) { throw new NotSupportedException (); } /// /// Sets the length of the stream. /// /// /// It is not possible to set the length of a . /// /// The desired length of the stream in bytes. /// /// The stream does not support setting the length. /// public override void SetLength (long value) { throw new NotSupportedException (); } /// /// Releases the unmanaged resources used by the and /// optionally releases the managed resources. /// /// /// Releases the unmanaged resources used by the and /// optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; /// false to release only the unmanaged resources. protected override void Dispose (bool disposing) { if (disposing && !disposed) { IsConnected = false; Stream.Dispose (); } disposed = true; base.Dispose (disposing); } } }