MultipartFormDataStream.cs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using BestHTTP.Extensions;
  5. using BestHTTP.PlatformSupport.Memory;
  6. namespace BestHTTP
  7. {
  8. /// <summary>
  9. /// Stream based implementation of the multipart/form-data Content-Type. Using this class reading a whole file into memory can be avoided.
  10. /// This implementation expects that all streams has a final, accessible Length.
  11. /// </summary>
  12. public sealed class MultipartFormDataStream : System.IO.Stream
  13. {
  14. public override bool CanRead { get { return true; } }
  15. public override bool CanSeek { get { return false; } }
  16. public override bool CanWrite { get { return false; } }
  17. public override long Length
  18. {
  19. get
  20. {
  21. // multipart/form-data requires a leading boundary that we can add when all streams are added.
  22. // This final preparation could be user initiated, but we can do it automatically too when the HTTPRequest
  23. // first access the Length property.
  24. if (!this.prepared)
  25. {
  26. this.prepared = true;
  27. this.Prepare();
  28. }
  29. return this._length;
  30. }
  31. }
  32. private long _length;
  33. public override long Position { get; set; }
  34. /// <summary>
  35. /// A random boundary generated in the constructor.
  36. /// </summary>
  37. private string boundary;
  38. private Queue<StreamList> fields = new Queue<StreamList>(1);
  39. private StreamList currentField;
  40. private bool prepared;
  41. public MultipartFormDataStream(HTTPRequest request)
  42. {
  43. this.boundary = "BestHTTP_MultipartFormDataStream_" + this.GetHashCode().ToString("X2");
  44. request.SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
  45. request.UploadStream = this;
  46. request.UseUploadStreamLength = true;
  47. }
  48. public void AddField(string fieldName, string value)
  49. {
  50. AddField(fieldName, value, System.Text.Encoding.UTF8);
  51. }
  52. public void AddField(string fieldName, string value, System.Text.Encoding encoding)
  53. {
  54. var enc = encoding ?? System.Text.Encoding.UTF8;
  55. var byteCount = enc.GetByteCount(value);
  56. var buffer = BufferPool.Get(byteCount, true);
  57. var stream = new BufferPoolMemoryStream();
  58. enc.GetBytes(value, 0, value.Length, buffer, 0);
  59. stream.Write(buffer, 0, byteCount);
  60. stream.Position = 0;
  61. string mime = encoding != null ? "text/plain; charset=" + encoding.WebName : null;
  62. AddStreamField(stream, fieldName, null, mime);
  63. }
  64. public void AddStreamField(System.IO.Stream stream, string fieldName)
  65. {
  66. AddStreamField(stream, fieldName, null, null);
  67. }
  68. public void AddStreamField(System.IO.Stream stream, string fieldName, string fileName)
  69. {
  70. AddStreamField(stream, fieldName, fileName, null);
  71. }
  72. public void AddStreamField(System.IO.Stream stream, string fieldName, string fileName, string mimeType)
  73. {
  74. var header = new BufferPoolMemoryStream();
  75. header.WriteLine("--" + this.boundary);
  76. header.WriteLine("Content-Disposition: form-data; name=\"" + fieldName + "\"" + (!string.IsNullOrEmpty(fileName) ? "; filename=\"" + fileName + "\"" : string.Empty));
  77. // Set up Content-Type head for the form.
  78. if (!string.IsNullOrEmpty(mimeType))
  79. header.WriteLine("Content-Type: " + mimeType);
  80. //header.WriteLine("Content-Length: " + stream.Length.ToString());
  81. header.WriteLine();
  82. header.Position = 0;
  83. var footer = new BufferPoolMemoryStream();
  84. footer.Write(HTTPRequest.EOL, 0, HTTPRequest.EOL.Length);
  85. footer.Position = 0;
  86. // all wrapped streams going to be disposed by the StreamList wrapper.
  87. var wrapper = new StreamList(header, stream, footer);
  88. try
  89. {
  90. if (this._length >= 0)
  91. this._length += wrapper.Length;
  92. }
  93. catch
  94. {
  95. this._length = -1;
  96. }
  97. this.fields.Enqueue(wrapper);
  98. }
  99. /// <summary>
  100. /// Adds the final boundary.
  101. /// </summary>
  102. private void Prepare()
  103. {
  104. var boundaryStream = new BufferPoolMemoryStream();
  105. boundaryStream.WriteLine("--" + this.boundary + "--");
  106. boundaryStream.Position = 0;
  107. this.fields.Enqueue(new StreamList(boundaryStream));
  108. if (this._length >= 0)
  109. this._length += boundaryStream.Length;
  110. }
  111. public override int Read(byte[] buffer, int offset, int length)
  112. {
  113. if (this.currentField == null && this.fields.Count == 0)
  114. return -1;
  115. if (this.currentField == null && this.fields.Count > 0)
  116. this.currentField = this.fields.Dequeue();
  117. int readCount = 0;
  118. do
  119. {
  120. // read from the current stream
  121. int count = this.currentField.Read(buffer, offset + readCount, length - readCount);
  122. if (count > 0)
  123. readCount += count;
  124. else
  125. {
  126. // if the current field's stream is empty, go for the next one.
  127. // dispose the current one first
  128. try
  129. {
  130. this.currentField.Dispose();
  131. }
  132. catch
  133. { }
  134. // no more fields/streams? exit
  135. if (this.fields.Count == 0)
  136. break;
  137. // grab the next one
  138. this.currentField = this.fields.Dequeue();
  139. }
  140. // exit when we reach the length goal, or there's no more streams to read from
  141. } while (readCount < length && this.fields.Count > 0);
  142. return readCount;
  143. }
  144. public override long Seek(long offset, SeekOrigin origin)
  145. {
  146. throw new NotImplementedException();
  147. }
  148. public override void SetLength(long value)
  149. {
  150. throw new NotImplementedException();
  151. }
  152. public override void Write(byte[] buffer, int offset, int count)
  153. {
  154. throw new NotImplementedException();
  155. }
  156. public override void Flush() { }
  157. }
  158. }