//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.Xml { using System.IO; using System.Runtime; using System.Runtime.Serialization; using System.Security; using System.Text; using System.Threading; abstract class XmlStreamNodeWriter : XmlNodeWriter { Stream stream; byte[] buffer; int offset; bool ownsStream; const int bufferLength = 512; const int maxEntityLength = 32; const int maxBytesPerChar = 3; Encoding encoding; int hasPendingWrite; AsyncEventArgs flushBufferState; static UTF8Encoding UTF8Encoding = new UTF8Encoding(false, true); static AsyncCallback onFlushBufferComplete; static AsyncEventArgsCallback onGetFlushComplete; protected XmlStreamNodeWriter() { this.buffer = new byte[bufferLength]; encoding = XmlStreamNodeWriter.UTF8Encoding; } protected void SetOutput(Stream stream, bool ownsStream, Encoding encoding) { this.stream = stream; this.ownsStream = ownsStream; this.offset = 0; if (encoding != null) { this.encoding = encoding; } } // Getting/Setting the Stream exists for fragmenting public Stream Stream { get { return stream; } set { stream = value; } } // StreamBuffer/BufferOffset exists only for the BinaryWriter to fix up nodes public byte[] StreamBuffer { get { return buffer; } } public int BufferOffset { get { return offset; } } public int Position { get { return (int)stream.Position + offset; } } protected byte[] GetBuffer(int count, out int offset) { Fx.Assert(count >= 0 && count <= bufferLength, ""); int bufferOffset = this.offset; if (bufferOffset + count <= bufferLength) { offset = bufferOffset; } else { FlushBuffer(); offset = 0; } #if DEBUG Fx.Assert(offset + count <= bufferLength, ""); for (int i = 0; i < count; i++) { buffer[offset + i] = (byte)'<'; } #endif return buffer; } internal AsyncCompletionResult GetBufferAsync(GetBufferAsyncEventArgs getBufferState) { Fx.Assert(getBufferState != null, "GetBufferAsyncEventArgs cannot be null."); int count = getBufferState.Arguments.Count; Fx.Assert(count >= 0 && count <= bufferLength, String.Empty); int finalOffset = 0; int bufferOffset = this.offset; if (bufferOffset + count <= bufferLength) { finalOffset = bufferOffset; } else { if (onGetFlushComplete == null) { onGetFlushComplete = new AsyncEventArgsCallback(GetBufferFlushComplete); } if (flushBufferState == null) { this.flushBufferState = new AsyncEventArgs(); } this.flushBufferState.Set(onGetFlushComplete, getBufferState, this); if (FlushBufferAsync(this.flushBufferState) == AsyncCompletionResult.Completed) { finalOffset = 0; this.flushBufferState.Complete(true); } else { return AsyncCompletionResult.Queued; } } #if DEBUG Fx.Assert(finalOffset + count <= bufferLength, ""); for (int i = 0; i < count; i++) { buffer[finalOffset + i] = (byte)'<'; } #endif //return the buffer and finalOffset; getBufferState.Result = getBufferState.Result ?? new GetBufferEventResult(); getBufferState.Result.Buffer = this.buffer; getBufferState.Result.Offset = finalOffset; return AsyncCompletionResult.Completed; } static void GetBufferFlushComplete(IAsyncEventArgs completionState) { XmlStreamNodeWriter thisPtr = (XmlStreamNodeWriter)completionState.AsyncState; GetBufferAsyncEventArgs getBufferState = (GetBufferAsyncEventArgs)thisPtr.flushBufferState.Arguments; getBufferState.Result = getBufferState.Result ?? new GetBufferEventResult(); getBufferState.Result.Buffer = thisPtr.buffer; getBufferState.Result.Offset = 0; getBufferState.Complete(false, completionState.Exception); } AsyncCompletionResult FlushBufferAsync(AsyncEventArgs state) { if (Interlocked.CompareExchange(ref this.hasPendingWrite, 1, 0) != 0) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.GetString(SR.FlushBufferAlreadyInUse))); } if (this.offset != 0) { if (onFlushBufferComplete == null) { onFlushBufferComplete = new AsyncCallback(OnFlushBufferCompete); } IAsyncResult result = stream.BeginWrite(buffer, 0, this.offset, onFlushBufferComplete, this); if (!result.CompletedSynchronously) { return AsyncCompletionResult.Queued; } stream.EndWrite(result); this.offset = 0; } if (Interlocked.CompareExchange(ref this.hasPendingWrite, 0, 1) != 1) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.GetString(SR.NoAsyncWritePending))); } return AsyncCompletionResult.Completed; } static void OnFlushBufferCompete(IAsyncResult result) { if (result.CompletedSynchronously) { return; } XmlStreamNodeWriter thisPtr = (XmlStreamNodeWriter)result.AsyncState; Exception completionException = null; try { thisPtr.stream.EndWrite(result); thisPtr.offset = 0; if (Interlocked.CompareExchange(ref thisPtr.hasPendingWrite, 0, 1) != 1) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.GetString(SR.NoAsyncWritePending))); } } catch (Exception ex) { if (Fx.IsFatal(ex)) { throw; } completionException = ex; } thisPtr.flushBufferState.Complete(false, completionException); } protected IAsyncResult BeginGetBuffer(int count, AsyncCallback callback, object state) { Fx.Assert(count >= 0 && count <= bufferLength, ""); return new GetBufferAsyncResult(count, this, callback, state); } protected byte[] EndGetBuffer(IAsyncResult result, out int offset) { return GetBufferAsyncResult.End(result, out offset); } class GetBufferAsyncResult : AsyncResult { XmlStreamNodeWriter writer; int offset; int count; static AsyncCompletion onComplete = new AsyncCompletion(OnComplete); public GetBufferAsyncResult(int count, XmlStreamNodeWriter writer, AsyncCallback callback, object state) : base(callback, state) { this.count = count; this.writer = writer; int bufferOffset = writer.offset; bool completeSelf = false; if (bufferOffset + count <= bufferLength) { this.offset = bufferOffset; completeSelf = true; } else { IAsyncResult result = writer.BeginFlushBuffer(PrepareAsyncCompletion(onComplete), this); completeSelf = SyncContinue(result); } if (completeSelf) { this.Complete(true); } } static bool OnComplete(IAsyncResult result) { GetBufferAsyncResult thisPtr = (GetBufferAsyncResult)result.AsyncState; return thisPtr.HandleFlushBuffer(result); } bool HandleFlushBuffer(IAsyncResult result) { writer.EndFlushBuffer(result); this.offset = 0; #if DEBUG Fx.Assert(this.offset + this.count <= bufferLength, ""); for (int i = 0; i < this.count; i++) { writer.buffer[this.offset + i] = (byte)'<'; } #endif return true; } public static byte[] End(IAsyncResult result, out int offset) { GetBufferAsyncResult thisPtr = AsyncResult.End(result); offset = thisPtr.offset; return thisPtr.writer.buffer; } } protected void Advance(int count) { Fx.Assert(offset + count <= bufferLength, ""); offset += count; } void EnsureByte() { if (offset >= bufferLength) { FlushBuffer(); } } protected void WriteByte(byte b) { EnsureByte(); buffer[offset++] = b; } protected void WriteByte(char ch) { Fx.Assert(ch < 0x80, ""); WriteByte((byte)ch); } protected void WriteBytes(byte b1, byte b2) { byte[] buffer = this.buffer; int offset = this.offset; if (offset + 1 >= bufferLength) { FlushBuffer(); offset = 0; } buffer[offset + 0] = b1; buffer[offset + 1] = b2; this.offset += 2; } protected void WriteBytes(char ch1, char ch2) { Fx.Assert(ch1 < 0x80 && ch2 < 0x80, ""); WriteBytes((byte)ch1, (byte)ch2); } public void WriteBytes(byte[] byteBuffer, int byteOffset, int byteCount) { if (byteCount < bufferLength) { int offset; byte[] buffer = GetBuffer(byteCount, out offset); Buffer.BlockCopy(byteBuffer, byteOffset, buffer, offset, byteCount); Advance(byteCount); } else { FlushBuffer(); stream.Write(byteBuffer, byteOffset, byteCount); } } public IAsyncResult BeginWriteBytes(byte[] byteBuffer, int byteOffset, int byteCount, AsyncCallback callback, object state) { return new WriteBytesAsyncResult(byteBuffer, byteOffset, byteCount, this, callback, state); } public void EndWriteBytes(IAsyncResult result) { WriteBytesAsyncResult.End(result); } class WriteBytesAsyncResult : AsyncResult { static AsyncCompletion onHandleGetBufferComplete = new AsyncCompletion(OnHandleGetBufferComplete); static AsyncCompletion onHandleFlushBufferComplete = new AsyncCompletion(OnHandleFlushBufferComplete); static AsyncCompletion onHandleWrite = new AsyncCompletion(OnHandleWrite); byte[] byteBuffer; int byteOffset; int byteCount; XmlStreamNodeWriter writer; public WriteBytesAsyncResult(byte[] byteBuffer, int byteOffset, int byteCount, XmlStreamNodeWriter writer, AsyncCallback callback, object state) : base(callback, state) { this.byteBuffer = byteBuffer; this.byteOffset = byteOffset; this.byteCount = byteCount; this.writer = writer; bool completeSelf = false; if (byteCount < bufferLength) { completeSelf = HandleGetBuffer(null); } else { completeSelf = HandleFlushBuffer(null); } if (completeSelf) { this.Complete(true); } } static bool OnHandleGetBufferComplete(IAsyncResult result) { WriteBytesAsyncResult thisPtr = (WriteBytesAsyncResult)result.AsyncState; return thisPtr.HandleGetBuffer(result); } static bool OnHandleFlushBufferComplete(IAsyncResult result) { WriteBytesAsyncResult thisPtr = (WriteBytesAsyncResult)result.AsyncState; return thisPtr.HandleFlushBuffer(result); } static bool OnHandleWrite(IAsyncResult result) { WriteBytesAsyncResult thisPtr = (WriteBytesAsyncResult)result.AsyncState; return thisPtr.HandleWrite(result); } bool HandleGetBuffer(IAsyncResult result) { if (result == null) { result = writer.BeginGetBuffer(this.byteCount, PrepareAsyncCompletion(onHandleGetBufferComplete), this); if (!result.CompletedSynchronously) { return false; } } int offset; byte[] buffer = writer.EndGetBuffer(result, out offset); Buffer.BlockCopy(this.byteBuffer, this.byteOffset, buffer, offset, this.byteCount); writer.Advance(this.byteCount); return true; } bool HandleFlushBuffer(IAsyncResult result) { if (result == null) { result = writer.BeginFlushBuffer(PrepareAsyncCompletion(onHandleFlushBufferComplete), this); if (!result.CompletedSynchronously) { return false; } } writer.EndFlushBuffer(result); return HandleWrite(null); } bool HandleWrite(IAsyncResult result) { if (result == null) { result = writer.stream.BeginWrite(this.byteBuffer, this.byteOffset, this.byteCount, PrepareAsyncCompletion(onHandleWrite), this); if (!result.CompletedSynchronously) { return false; } } writer.stream.EndWrite(result); return true; } public static void End(IAsyncResult result) { AsyncResult.End(result); } } [Fx.Tag.SecurityNote(Critical = "Contains unsafe code. Caller needs to validate arguments.")] [SecurityCritical] unsafe protected void UnsafeWriteBytes(byte* bytes, int byteCount) { FlushBuffer(); byte[] buffer = this.buffer; while (byteCount > bufferLength) { for (int i = 0; i < bufferLength; i++) buffer[i] = bytes[i]; stream.Write(buffer, 0, bufferLength); bytes += bufferLength; byteCount -= bufferLength; } if (byteCount > 0) { for (int i = 0; i < byteCount; i++) buffer[i] = bytes[i]; stream.Write(buffer, 0, byteCount); } } [Fx.Tag.SecurityNote(Critical = "Contains unsafe code.", Safe = "Unsafe code is effectively encapsulated, all inputs are validated.")] [SecuritySafeCritical] unsafe protected void WriteUTF8Char(int ch) { if (ch < 0x80) { WriteByte((byte)ch); } else if (ch <= char.MaxValue) { char* chars = stackalloc char[1]; chars[0] = (char)ch; UnsafeWriteUTF8Chars(chars, 1); } else { SurrogateChar surrogateChar = new SurrogateChar(ch); char* chars = stackalloc char[2]; chars[0] = surrogateChar.HighChar; chars[1] = surrogateChar.LowChar; UnsafeWriteUTF8Chars(chars, 2); } } protected void WriteUTF8Chars(byte[] chars, int charOffset, int charCount) { if (charCount < bufferLength) { int offset; byte[] buffer = GetBuffer(charCount, out offset); Buffer.BlockCopy(chars, charOffset, buffer, offset, charCount); Advance(charCount); } else { FlushBuffer(); stream.Write(chars, charOffset, charCount); } } [Fx.Tag.SecurityNote(Critical = "Contains unsafe code.", Safe = "Unsafe code is effectively encapsulated, all inputs are validated.")] [SecuritySafeCritical] unsafe protected void WriteUTF8Chars(string value) { int count = value.Length; if (count > 0) { fixed (char* chars = value) { UnsafeWriteUTF8Chars(chars, count); } } } [Fx.Tag.SecurityNote(Critical = "Contains unsafe code. Caller needs to validate arguments.")] [SecurityCritical] unsafe protected void UnsafeWriteUTF8Chars(char* chars, int charCount) { const int charChunkSize = bufferLength / maxBytesPerChar; while (charCount > charChunkSize) { int offset; int chunkSize = charChunkSize; if ((int)(chars[chunkSize - 1] & 0xFC00) == 0xD800) // This is a high surrogate chunkSize--; byte[] buffer = GetBuffer(chunkSize * maxBytesPerChar, out offset); Advance(UnsafeGetUTF8Chars(chars, chunkSize, buffer, offset)); charCount -= chunkSize; chars += chunkSize; } if (charCount > 0) { int offset; byte[] buffer = GetBuffer(charCount * maxBytesPerChar, out offset); Advance(UnsafeGetUTF8Chars(chars, charCount, buffer, offset)); } } [Fx.Tag.SecurityNote(Critical = "Contains unsafe code. Caller needs to validate arguments.")] [SecurityCritical] unsafe protected void UnsafeWriteUnicodeChars(char* chars, int charCount) { const int charChunkSize = bufferLength / 2; while (charCount > charChunkSize) { int offset; int chunkSize = charChunkSize; if ((int)(chars[chunkSize - 1] & 0xFC00) == 0xD800) // This is a high surrogate chunkSize--; byte[] buffer = GetBuffer(chunkSize * 2, out offset); Advance(UnsafeGetUnicodeChars(chars, chunkSize, buffer, offset)); charCount -= chunkSize; chars += chunkSize; } if (charCount > 0) { int offset; byte[] buffer = GetBuffer(charCount * 2, out offset); Advance(UnsafeGetUnicodeChars(chars, charCount, buffer, offset)); } } [Fx.Tag.SecurityNote(Critical = "Contains unsafe code. Caller needs to validate arguments.")] [SecurityCritical] unsafe protected int UnsafeGetUnicodeChars(char* chars, int charCount, byte[] buffer, int offset) { char* charsMax = chars + charCount; while (chars < charsMax) { char value = *chars++; buffer[offset++] = (byte)value; value >>= 8; buffer[offset++] = (byte)value; } return charCount * 2; } [Fx.Tag.SecurityNote(Critical = "Contains unsafe code. Caller needs to validate arguments.")] [SecurityCritical] unsafe protected int UnsafeGetUTF8Length(char* chars, int charCount) { char* charsMax = chars + charCount; while (chars < charsMax) { if (*chars >= 0x80) break; chars++; } if (chars == charsMax) return charCount; return (int)(chars - (charsMax - charCount)) + encoding.GetByteCount(chars, (int)(charsMax - chars)); } [Fx.Tag.SecurityNote(Critical = "Contains unsafe code. Caller needs to validate arguments.")] [SecurityCritical] unsafe protected int UnsafeGetUTF8Chars(char* chars, int charCount, byte[] buffer, int offset) { if (charCount > 0) { fixed (byte* _bytes = &buffer[offset]) { byte* bytes = _bytes; byte* bytesMax = &bytes[buffer.Length - offset]; char* charsMax = &chars[charCount]; while (true) { while (chars < charsMax) { char t = *chars; if (t >= 0x80) break; *bytes = (byte)t; bytes++; chars++; } if (chars >= charsMax) break; char* charsStart = chars; while (chars < charsMax && *chars >= 0x80) { chars++; } bytes += encoding.GetBytes(charsStart, (int)(chars - charsStart), bytes, (int)(bytesMax - bytes)); if (chars >= charsMax) break; } return (int)(bytes - _bytes); } } return 0; } protected virtual void FlushBuffer() { if (offset != 0) { stream.Write(buffer, 0, offset); offset = 0; } } protected virtual IAsyncResult BeginFlushBuffer(AsyncCallback callback, object state) { return new FlushBufferAsyncResult(this, callback, state); } protected virtual void EndFlushBuffer(IAsyncResult result) { FlushBufferAsyncResult.End(result); } class FlushBufferAsyncResult : AsyncResult { static AsyncCompletion onComplete = new AsyncCompletion(OnComplete); XmlStreamNodeWriter writer; public FlushBufferAsyncResult(XmlStreamNodeWriter writer, AsyncCallback callback, object state) : base(callback, state) { this.writer = writer; bool completeSelf = true; if (writer.offset != 0) { completeSelf = HandleFlushBuffer(null); } if (completeSelf) { this.Complete(true); } } static bool OnComplete(IAsyncResult result) { FlushBufferAsyncResult thisPtr = (FlushBufferAsyncResult)result.AsyncState; return thisPtr.HandleFlushBuffer(result); } bool HandleFlushBuffer(IAsyncResult result) { if (result == null) { result = this.writer.stream.BeginWrite(writer.buffer, 0, writer.offset, PrepareAsyncCompletion(onComplete), this); if (!result.CompletedSynchronously) { return false; } } this.writer.stream.EndWrite(result); this.writer.offset = 0; return true; } public static void End(IAsyncResult result) { AsyncResult.End(result); } } public override void Flush() { FlushBuffer(); stream.Flush(); } public override void Close() { if (stream != null) { if (ownsStream) { stream.Close(); } stream = null; } } internal class GetBufferArgs { public int Count { get; set; } } internal class GetBufferEventResult { internal byte[] Buffer { get; set; } internal int Offset { get; set; } } internal class GetBufferAsyncEventArgs : AsyncEventArgs { } } }