Merge pull request #3656 from marek-safar/bootstrap
[mono.git] / mcs / class / System.Net.Http / System.Net.Http / MultipartContent.cs
1 //
2 // MultipartContent.cs
3 //
4 // Authors:
5 //      Marek Safar  <marek.safar@gmail.com>
6 //
7 // Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System.IO;
30 using System.Threading.Tasks;
31 using System.Collections;
32 using System.Collections.Generic;
33 using System.Globalization;
34 using System.Net.Http.Headers;
35 using System.Linq;
36 using System.Text;
37
38 namespace System.Net.Http
39 {
40         public class MultipartContent : HttpContent, IEnumerable<HttpContent>
41         {
42                 List<HttpContent> nested_content;
43                 readonly string boundary;
44
45                 public MultipartContent ()
46                         : this ("mixed")
47                 {
48                 }
49
50                 public MultipartContent (string subtype)
51                         : this (subtype, Guid.NewGuid ().ToString ("D", CultureInfo.InvariantCulture))
52                 {
53                 }
54
55                 public MultipartContent (string subtype, string boundary)
56                 {
57                         if (string.IsNullOrWhiteSpace (subtype))
58                                 throw new ArgumentException ("boundary");
59
60                         //
61                         // The only mandatory parameter for the multipart Content-Type is the boundary parameter, which consists
62                         // of 1 to 70 characters from a set of characters known to be very robust through email gateways,
63                         // and NOT ending with white space
64                         //
65                         if (string.IsNullOrWhiteSpace (boundary))
66                                 throw new ArgumentException ("boundary");
67
68                         if (boundary.Length > 70)
69                                 throw new ArgumentOutOfRangeException ("boundary");
70
71                         if (boundary.Last () == ' ' || !IsValidRFC2049 (boundary))
72                                 throw new ArgumentException ("boundary");
73
74                         this.boundary = boundary;
75                         this.nested_content = new List<HttpContent> (2);
76
77                         Headers.ContentType = new MediaTypeHeaderValue ("multipart/" + subtype) {
78                                 Parameters = { new NameValueHeaderValue ("boundary", "\"" + boundary + "\"") }
79                         };
80                 }
81
82                 static bool IsValidRFC2049 (string s)
83                 {
84                         foreach (char c in s) {
85                                 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
86                                         continue;
87
88                                 switch (c) {
89                                 case '\'': case '(': case ')': case '+': case ',':
90                                 case '-': case '.': case '/': case ':': case '=':
91                                 case '?':
92                                         continue;
93                                 }
94
95                                 return false;
96                         }
97
98                         return true;
99                 }
100
101                 public virtual void Add (HttpContent content)
102                 {
103                         if (content == null)
104                                 throw new ArgumentNullException ("content");
105
106                         if (nested_content == null)
107                                 nested_content = new List<HttpContent> ();
108
109                         nested_content.Add (content);
110                 }
111
112                 protected override void Dispose (bool disposing)
113                 {
114                         if (disposing) {
115                                 foreach (var item in nested_content) {
116                                         item.Dispose ();
117                                 }
118                                 
119                                 nested_content = null;
120                         }
121                         
122                         base.Dispose (disposing);
123                 }
124
125                 protected internal override async Task SerializeToStreamAsync (Stream stream, TransportContext context)
126                 {
127                         // RFC 2046
128                         //
129                         // The Content-Type field for multipart entities requires one parameter,
130                         // "boundary". The boundary delimiter line is then defined as a line
131                         // consisting entirely of two hyphen characters ("-", decimal value 45)
132                         // followed by the boundary parameter value from the Content-Type header
133                         // field, optional linear whitespace, and a terminating CRLF.
134                         //
135
136                         byte[] buffer;
137                         var sb = new StringBuilder ();
138                         sb.Append ('-').Append ('-');
139                         sb.Append (boundary);
140                         sb.Append ('\r').Append ('\n');
141
142                         for (int i = 0; i < nested_content.Count; i++) {
143                                 var c = nested_content [i];
144                                         
145                                 foreach (var h in c.Headers) {
146                                         sb.Append (h.Key);
147                                         sb.Append (':').Append (' ');
148                                         foreach (var v in h.Value) {
149                                                 sb.Append (v);
150                                         }
151                                         sb.Append ('\r').Append ('\n');
152                                 }
153                                 sb.Append ('\r').Append ('\n');
154                                         
155                                 buffer = Encoding.ASCII.GetBytes (sb.ToString ());
156                                 sb.Clear ();
157                                 await stream.WriteAsync (buffer, 0, buffer.Length).ConfigureAwait (false);
158
159                                 await c.SerializeToStreamAsync (stream, context).ConfigureAwait (false);
160                                         
161                                 if (i != nested_content.Count - 1) {
162                                         sb.Append ('\r').Append ('\n');
163                                         sb.Append ('-').Append ('-');
164                                         sb.Append (boundary);
165                                         sb.Append ('\r').Append ('\n');
166                                 }
167                         }
168                         
169                         sb.Append ('\r').Append ('\n');
170                         sb.Append ('-').Append ('-');
171                         sb.Append (boundary);
172                         sb.Append ('-').Append ('-');
173                         sb.Append ('\r').Append ('\n');
174
175                         buffer = Encoding.ASCII.GetBytes (sb.ToString ());
176                         await stream.WriteAsync (buffer, 0, buffer.Length).ConfigureAwait (false);
177                 }
178                 
179                 protected internal override bool TryComputeLength (out long length)
180                 {
181                         length = 12 + 2 * boundary.Length;
182                         
183                         for (int i = 0; i < nested_content.Count; i++) {
184                                 var c = nested_content [i];
185                                 foreach (var h in c.Headers) {
186                                         length += h.Key.Length;
187                                         length += 4;
188                                                 
189                                         foreach (var v in h.Value) {
190                                                 length += v.Length;
191                                         }
192                                 }
193                                         
194                                 long l;
195                                 if (!c.TryComputeLength (out l))
196                                         return false;
197
198                                 length += 2;
199                                 length += l;
200                                         
201                                 if (i != nested_content.Count - 1) {
202                                         length += 6;
203                                         length += boundary.Length;
204                                 }
205                         }
206
207                         return true;
208                 }
209
210                 public IEnumerator<HttpContent> GetEnumerator ()
211                 {
212                         return nested_content.GetEnumerator ();
213                 }
214
215                 IEnumerator IEnumerable.GetEnumerator ()
216                 {
217                         return nested_content.GetEnumerator ();
218                 }
219         }
220 }