Implement MachineKey.Protect and MachineKey.Unprotect
[mono.git] / mcs / class / System.Net.Http / System.Net.Http.Headers / ContentDispositionHeaderValue.cs
1 //
2 // ContentDispositionHeaderValue.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.Collections.Generic;
30 using System.Text;
31 using System.Globalization;
32
33 namespace System.Net.Http.Headers
34 {
35         public class ContentDispositionHeaderValue : ICloneable
36         {
37                 string dispositionType;
38                 List<NameValueHeaderValue> parameters;
39
40                 private ContentDispositionHeaderValue ()
41                 {
42                 }
43
44                 public ContentDispositionHeaderValue (string dispositionType)
45                 {
46                         DispositionType = dispositionType;
47                 }
48
49                 protected ContentDispositionHeaderValue (ContentDispositionHeaderValue source)
50                 {
51                         if (source == null)
52                                 throw new ArgumentNullException ("source");
53
54                         dispositionType = source.dispositionType;
55                         if (source.parameters != null) {
56                                 foreach (var item in source.parameters)
57                                         Parameters.Add (new NameValueHeaderValue (item));
58                         }
59                 }
60
61                 public DateTimeOffset? CreationDate {
62                         get {
63                                 return GetDateValue ("creation-date");
64                         }
65                         set {
66                                 SetDateValue ("creation-date", value);
67                         }
68                 }
69                 
70                 public string DispositionType {
71                         get {
72                                 return dispositionType;
73                         }
74                         set {
75                                 Parser.Token.Check (value);
76                                 dispositionType = value;
77                         }
78                 }
79
80                 public string FileName {
81                         get {
82                                 var value = FindParameter ("filename");
83                                 if (value == null)
84                                         return null;
85
86                                 return DecodeValue (value, false);
87                         }
88                         set {
89                                 if (value != null)
90                                         value = EncodeBase64Value (value);
91
92                                 SetValue ("filename", value);
93                         }
94                 }
95
96                 public string FileNameStar {
97                         get {
98                                 var value = FindParameter ("filename*");
99                                 if (value == null)
100                                         return null;
101
102                                 return DecodeValue (value, true);
103                         }
104                         set {
105                                 if (value != null)
106                                         value = EncodeRFC5987 (value);
107
108                                 SetValue ("filename*", value);
109                         }
110                 }
111
112                 public DateTimeOffset? ModificationDate {
113                         get {
114                                 return GetDateValue ("modification-date");
115                         }
116                         set {
117                                 SetDateValue ("modification-date", value);
118                         }
119                 }
120
121                 public string Name {
122                         get {
123                                 var value = FindParameter ("name");
124
125                                 if (value == null)
126                                         return null;
127
128                                 return DecodeValue (value, false);
129                         }
130                         set {
131                                 if (value != null)
132                                         value = EncodeBase64Value (value);
133
134                                 SetValue ("name", value);
135                         }
136                 }
137
138                 public ICollection<NameValueHeaderValue> Parameters {
139                         get {
140                                 return parameters ?? (parameters = new List<NameValueHeaderValue> ());
141                         }
142                 }
143
144                 public DateTimeOffset? ReadDate {
145                         get {
146                                 return GetDateValue ("read-date");
147                         }
148                         set {
149                                 SetDateValue ("read-date", value);
150                         }
151                 }
152
153                 public long? Size {
154                         get {
155                                 var found = FindParameter ("size");
156                                 long result;
157                                 if (Parser.Long.TryParse (found, out result))
158                                         return result;
159
160                                 return null;
161                         }
162                         set {
163                                 if (value == null) {
164                                         SetValue ("size", null);
165                                         return;
166                                 }
167
168                                 if (value < 0)
169                                         throw new ArgumentOutOfRangeException ("value");
170
171                                 SetValue ("size", value.Value.ToString (CultureInfo.InvariantCulture));
172                         }
173                 }
174
175                 object ICloneable.Clone ()
176                 {
177                         return new ContentDispositionHeaderValue (this);
178                 }
179
180                 public override bool Equals (object obj)
181                 {
182                         var source = obj as ContentDispositionHeaderValue;
183                         return source != null &&
184                                 string.Equals (source.dispositionType, dispositionType, StringComparison.OrdinalIgnoreCase) &&
185                                 source.parameters.SequenceEqual (parameters);
186                 }
187
188                 string FindParameter (string name)
189                 {
190                         if (parameters == null)
191                                 return null;
192
193                         foreach (var entry in parameters) {
194                                 if (string.Equals (entry.Name, name, StringComparison.OrdinalIgnoreCase))
195                                         return entry.Value;
196                         }
197
198                         return null;
199                 }
200
201                 DateTimeOffset? GetDateValue (string name)
202                 {
203                         var value = FindParameter (name);
204                         if (value == null || value == null)
205                                 return null;
206
207                         if (value.Length < 3)
208                                 return null;
209
210                         if (value[0] == '\"')
211                                 value = value.Substring (1, value.Length - 2);
212
213                         DateTimeOffset offset;
214                         if (Lexer.TryGetDateValue (value, out offset))
215                                 return offset;
216
217                         return null;
218                 }
219
220                 static string EncodeBase64Value (string value)
221                 {
222                         bool quoted = value.Length > 1 && value [0] == '"' && value [value.Length - 1] == '"';
223                         if (quoted)
224                                 value = value.Substring (1, value.Length - 2);
225
226                         for (int i = 0; i < value.Length; ++i) {
227                                 var ch = value[i];
228                                 if (ch > 127) {
229                                         var encoding = Encoding.UTF8;
230                                         return string.Format ("\"=?{0}?B?{1}?=\"",
231                                                 encoding.WebName, Convert.ToBase64String (encoding.GetBytes (value)));
232                                 }
233                         }
234
235                         if (quoted || !Lexer.IsValidToken (value))
236                                 return "\"" + value + "\"";
237
238                         return value;
239                 }
240
241                 static string EncodeRFC5987 (string value)
242                 {
243                         var encoding = Encoding.UTF8;
244                         StringBuilder sb = new StringBuilder (value.Length + 11);
245                         sb.Append (encoding.WebName);
246                         sb.Append ('\'');
247                         sb.Append ('\'');
248
249                         for (int i = 0; i < value.Length; ++i) {
250                                 var ch = value[i];
251                                 if (ch > 127) {
252                                         foreach (var b in encoding.GetBytes (new[] { ch })) {
253                                                 sb.Append ('%');
254                                                 sb.Append (b.ToString ("X2"));
255                                         }
256
257                                         continue;
258                                 }
259
260                                 sb.Append (ch);
261                         }
262
263                         return sb.ToString ();
264                 }
265
266                 static string DecodeValue (string value, bool extendedNotation)
267                 {
268                         //
269                         // A short (length <= 78 characters)
270                         // parameter value containing only non-`tspecials' characters SHOULD be
271                         // represented as a single `token'.  A short parameter value containing
272                         // only ASCII characters, but including `tspecials' characters, SHOULD
273                         // be represented as `quoted-string'.  Parameter values longer than 78
274                         // characters, or which contain non-ASCII characters, MUST be encoded as
275                         // specified in [RFC 2184].
276                         //
277                         if (value.Length < 2)
278                                 return value;
279
280                         string[] sep;
281                         Encoding encoding;
282
283                         // Quoted string
284                         if (value[0] == '\"') {
285                                 //
286                                 // Is Base64 encoded ?
287                                 // encoded-word := "=?" charset "?" encoding "?" encoded-text "?="
288                                 //
289                                 sep = value.Split ('?');
290                                 if (sep.Length != 5 || sep[0] != "\"=" || sep[4] != "=\"" || (sep[2] != "B" && sep[2] != "b"))
291                                         return value;
292
293                                 try {
294                                         encoding = Encoding.GetEncoding (sep[1]);
295                                         return encoding.GetString (Convert.FromBase64String (sep[3]));
296                                 } catch {
297                                         return value;
298                                 }
299                         }
300
301                         if (!extendedNotation)
302                                 return value;
303
304                         //
305                         // RFC 5987: Charset/Language Encoding
306                         //
307                         sep = value.Split ('\'');
308                         if (sep.Length != 3)
309                                 return null;
310
311                         try {
312                                 encoding = Encoding.GetEncoding (sep[0]);
313                         } catch {
314                                 return null;
315                         }
316
317                         // TODO: What to do with sep[1] language
318
319                         value = sep[2];
320
321                         int pct_encoded = value.IndexOf ('%');
322                         if (pct_encoded < 0)
323                                 return value;
324
325                         StringBuilder sb = new StringBuilder ();
326                         byte[] buffer = null;
327                         int buffer_pos = 0;
328
329                         for (int i = 0; i < value.Length;) {
330                                 var ch = value[i];
331                                 if (ch == '%') {
332                                         var unescaped = ch;
333                                         ch = Uri.HexUnescape (value, ref i);
334                                         if (ch != unescaped) {
335                                                 if (buffer == null)
336                                                         buffer = new byte[value.Length - i + 1];
337
338                                                 buffer[buffer_pos++] = (byte) ch;
339                                                 continue;
340                                         }
341                                 } else {
342                                         ++i;
343                                 }
344
345                                 if (buffer_pos != 0) {
346                                         sb.Append (encoding.GetChars (buffer, 0, buffer_pos));
347                                         buffer_pos = 0;
348                                 }
349
350                                 sb.Append (ch);
351                         }
352
353                         if (buffer_pos != 0) {
354                                 sb.Append (encoding.GetChars (buffer, 0, buffer_pos));
355                         }
356
357                         return sb.ToString ();
358                 }
359
360                 public override int GetHashCode ()
361                 {
362                         return dispositionType.ToLowerInvariant ().GetHashCode () ^
363                                 HashCodeCalculator.Calculate (parameters);
364                 }
365
366                 public static ContentDispositionHeaderValue Parse (string input)
367                 {
368                         ContentDispositionHeaderValue value;
369                         if (TryParse (input, out value))
370                                 return value;
371
372                         throw new FormatException (input);
373                 }
374
375                 void SetDateValue (string key, DateTimeOffset? value)
376                 {
377                         SetValue (key, value == null ? null : ("\"" + value.Value.ToString ("r", CultureInfo.InvariantCulture)) + "\"");
378                 }
379
380                 void SetValue (string key, string value)
381                 {
382                         if (parameters == null)
383                                 parameters = new List<NameValueHeaderValue> ();
384
385                         parameters.SetValue (key, value);
386                 }
387
388                 public override string ToString ()
389                 {
390                         return dispositionType + CollectionExtensions.ToString (parameters);
391                 }
392
393                 public static bool TryParse (string input, out ContentDispositionHeaderValue parsedValue)
394                 {
395                         parsedValue = null;
396
397                         var lexer = new Lexer (input);
398                         var t = lexer.Scan ();
399                         if (t.Kind != Token.Type.Token)
400                                 return false;
401
402                         List<NameValueHeaderValue> parameters = null;
403                         var type = lexer.GetStringValue (t);
404
405                         t = lexer.Scan ();
406
407                         switch (t.Kind) {
408                         case Token.Type.SeparatorSemicolon:
409                                 if (!NameValueHeaderValue.TryParseParameters (lexer, out parameters, out t) || t != Token.Type.End)
410                                         return false;
411                                 break;
412                         case Token.Type.End:
413                                 break;
414                         default:
415                                 return false;
416                         }
417
418                         parsedValue = new ContentDispositionHeaderValue () {
419                                 dispositionType = type,
420                                 parameters = parameters
421                         };
422
423                         return true;
424                 }
425         }
426 }