{
public abstract class HttpContent : IDisposable
{
- MemoryStream buffer;
+ sealed class FixedMemoryStream : MemoryStream
+ {
+ readonly long maxSize;
+
+ public FixedMemoryStream (long maxSize)
+ : base ()
+ {
+ this.maxSize = maxSize;
+ }
+
+ void CheckOverflow (int count)
+ {
+ if (Length + count > maxSize)
+ throw new HttpRequestException (string.Format ("Cannot write more bytes to the buffer than the configured maximum buffer size: {0}", maxSize));
+ }
+
+ public override void WriteByte (byte value)
+ {
+ CheckOverflow (1);
+ base.WriteByte (value);
+ }
+
+ public override void Write (byte[] buffer, int offset, int count)
+ {
+ CheckOverflow (count);
+ base.Write (buffer, offset, count);
+ }
+ }
+
+ FixedMemoryStream buffer;
+ Stream stream;
bool disposed;
HttpContentHeaders headers;
}
}
+ internal long? LoadedBufferLength {
+ get {
+ return buffer == null ? (long?)null : buffer.Length;
+ }
+ }
+
public Task CopyToAsync (Stream stream)
{
return CopyToAsync (stream, null);
if (stream == null)
throw new ArgumentNullException ("stream");
+ if (buffer != null)
+ return buffer.CopyToAsync (stream);
+
return SerializeToStreamAsync (stream, context);
}
+ protected async virtual Task<Stream> CreateContentReadStreamAsync ()
+ {
+ await LoadIntoBufferAsync ().ConfigureAwait (false);
+ return buffer;
+ }
+
+ static FixedMemoryStream CreateFixedMemoryStream (long maxBufferSize)
+ {
+ return new FixedMemoryStream (maxBufferSize);
+ }
+
public void Dispose ()
{
Dispose (true);
public Task LoadIntoBufferAsync ()
{
- return LoadIntoBufferAsync (0x2000);
+ return LoadIntoBufferAsync (int.MaxValue);
}
- public async Task LoadIntoBufferAsync (int maxBufferSize)
+ public async Task LoadIntoBufferAsync (long maxBufferSize)
{
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (buffer != null)
return;
- buffer = new MemoryStream ();
+ buffer = CreateFixedMemoryStream (maxBufferSize);
await SerializeToStreamAsync (buffer, null).ConfigureAwait (false);
buffer.Seek (0, SeekOrigin.Begin);
}
+
+ public async Task<Stream> ReadAsStreamAsync ()
+ {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ if (buffer != null)
+ return new MemoryStream (buffer.GetBuffer (), 0, (int)buffer.Length, false);
+
+ if (stream == null)
+ stream = await CreateContentReadStreamAsync ().ConfigureAwait (false);
+
+ return stream;
+ }
public async Task<byte[]> ReadAsByteArrayAsync ()
{
public async Task<string> ReadAsStringAsync ()
{
- await LoadIntoBufferAsync ();
+ await LoadIntoBufferAsync ().ConfigureAwait (false);
if (buffer.Length == 0)
return string.Empty;
+ var buf = buffer.GetBuffer ();
+ var buf_length = (int) buffer.Length;
+ int preambleLength = 0;
Encoding encoding;
+
if (headers != null && headers.ContentType != null && headers.ContentType.CharSet != null) {
encoding = Encoding.GetEncoding (headers.ContentType.CharSet);
+ preambleLength = StartsWith (buf, buf_length, encoding.GetPreamble ());
} else {
- encoding = Encoding.UTF8;
+ encoding = GetEncodingFromBuffer (buf, buf_length, ref preambleLength) ?? Encoding.UTF8;
+ }
+
+ return encoding.GetString (buf, preambleLength, buf_length - preambleLength);
+ }
+
+ static Encoding GetEncodingFromBuffer (byte[] buffer, int length, ref int preambleLength)
+ {
+ var encodings_with_preamble = new [] { Encoding.UTF8, Encoding.UTF32, Encoding.Unicode };
+ foreach (var enc in encodings_with_preamble) {
+ if ((preambleLength = StartsWith (buffer, length, enc.GetPreamble ())) != 0)
+ return enc;
+ }
+
+ return null;
+ }
+
+ static int StartsWith (byte[] array, int length, byte[] value)
+ {
+ if (length < value.Length)
+ return 0;
+
+ for (int i = 0; i < value.Length; ++i) {
+ if (array [i] != value [i])
+ return 0;
}
- return encoding.GetString (buffer.GetBuffer (), 0, (int) buffer.Length);
+ return value.Length;
}
- protected abstract Task SerializeToStreamAsync (Stream stream, TransportContext context);
+ protected internal abstract Task SerializeToStreamAsync (Stream stream, TransportContext context);
protected internal abstract bool TryComputeLength (out long length);
}
}