Merge branch 'BigIntegerParse'
[mono.git] / mcs / class / System.Net.Http / System.Net.Http.Headers / ContentDispositionHeaderValue.cs
index 47f2938bc3944c7a12f1ace04ff8cd04e4d0964a..9dd41b3d330bddb745042e56b29ec59b05f72a83 100644 (file)
@@ -34,17 +34,381 @@ namespace System.Net.Http.Headers
 {
        public class ContentDispositionHeaderValue : ICloneable
        {
+               string dispositionType;
+               List<NameValueHeaderValue> parameters;
+
                private ContentDispositionHeaderValue ()
                {
                }
+
+               public ContentDispositionHeaderValue (string dispositionType)
+               {
+                       DispositionType = dispositionType;
+               }
+
+               protected ContentDispositionHeaderValue (ContentDispositionHeaderValue source)
+               {
+                       if (source == null)
+                               throw new ArgumentNullException ("source");
+
+                       dispositionType = source.dispositionType;
+                       if (source.parameters != null) {
+                               foreach (var item in source.parameters)
+                                       Parameters.Add (new NameValueHeaderValue (item));
+                       }
+               }
+
+               public DateTimeOffset? CreationDate {
+                       get {
+                               return GetDateValue ("creation-date");
+                       }
+                       set {
+                               SetDateValue ("creation-date", value);
+                       }
+               }
                
-               public string DispositionType { get; set; }
-               public string FileName { get; set; }
-               public string Name { get; set; }
+               public string DispositionType {
+                       get {
+                               return dispositionType;
+                       }
+                       set {
+                               Parser.Token.Check (value);
+                               dispositionType = value;
+                       }
+               }
+
+               public string FileName {
+                       get {
+                               var value = FindParameter ("filename");
+                               if (value == null)
+                                       return null;
+
+                               return DecodeValue (value, false);
+                       }
+                       set {
+                               if (value != null)
+                                       value = EncodeBase64Value (value);
+
+                               SetValue ("filename", value);
+                       }
+               }
+
+               public string FileNameStar {
+                       get {
+                               var value = FindParameter ("filename*");
+                               if (value == null)
+                                       return null;
+
+                               return DecodeValue (value, true);
+                       }
+                       set {
+                               if (value != null)
+                                       value = EncodeRFC5987 (value);
+
+                               SetValue ("filename*", value);
+                       }
+               }
+
+               public DateTimeOffset? ModificationDate {
+                       get {
+                               return GetDateValue ("modification-date");
+                       }
+                       set {
+                               SetDateValue ("modification-date", value);
+                       }
+               }
+
+               public string Name {
+                       get {
+                               return FindParameter ("name");
+                       }
+                       set {
+                               SetValue ("name", value);
+                       }
+               }
+
+               public ICollection<NameValueHeaderValue> Parameters {
+                       get {
+                               return parameters ?? (parameters = new List<NameValueHeaderValue> ());
+                       }
+               }
+
+               public DateTimeOffset? ReadDate {
+                       get {
+                               return GetDateValue ("read-date");
+                       }
+                       set {
+                               SetDateValue ("read-date", value);
+                       }
+               }
+
+               public long? Size {
+                       get {
+                               var found = FindParameter ("size");
+                               long result;
+                               if (Parser.Long.TryParse (found, out result))
+                                       return result;
+
+                               return null;
+                       }
+                       set {
+                               if (value == null) {
+                                       SetValue ("size", null);
+                                       return;
+                               }
+
+                               if (value < 0)
+                                       throw new ArgumentOutOfRangeException ("value");
+
+                               SetValue ("size", value.Value.ToString (CultureInfo.InvariantCulture));
+                       }
+               }
 
                object ICloneable.Clone ()
                {
-                       return MemberwiseClone ();
+                       return new ContentDispositionHeaderValue (this);
+               }
+
+               public override bool Equals (object obj)
+               {
+                       var source = obj as ContentDispositionHeaderValue;
+                       return source != null &&
+                               string.Equals (source.dispositionType, dispositionType, StringComparison.OrdinalIgnoreCase) &&
+                               source.parameters.SequenceEqual (parameters);
+               }
+
+               string FindParameter (string name)
+               {
+                       if (parameters == null)
+                               return null;
+
+                       foreach (var entry in parameters) {
+                               if (string.Equals (entry.Name, name, StringComparison.OrdinalIgnoreCase))
+                                       return entry.Value;
+                       }
+
+                       return null;
+               }
+
+               DateTimeOffset? GetDateValue (string name)
+               {
+                       var value = FindParameter (name);
+                       if (value == null || value == null)
+                               return null;
+
+                       if (value.Length < 3)
+                               return null;
+
+                       if (value[0] == '\"')
+                               value = value.Substring (1, value.Length - 2);
+
+                       DateTimeOffset offset;
+                       if (Lexer.TryGetDateValue (value, out offset))
+                               return offset;
+
+                       return null;
+               }
+
+               static string EncodeBase64Value (string value)
+               {
+                       for (int i = 0; i < value.Length; ++i) {
+                               var ch = value[i];
+                               if (ch > 127) {
+                                       var encoding = Encoding.UTF8;
+                                       return string.Format ("\"=?{0}?B?{1}?=\"",
+                                               encoding.WebName, Convert.ToBase64String (encoding.GetBytes (value)));
+                               }
+                       }
+
+                       if (!Lexer.IsValidToken (value))
+                               return "\"" + value + "\"";
+
+                       return value;
+               }
+
+               static string EncodeRFC5987 (string value)
+               {
+                       var encoding = Encoding.UTF8;
+                       StringBuilder sb = new StringBuilder (value.Length + 11);
+                       sb.Append (encoding.WebName);
+                       sb.Append ('\'');
+                       sb.Append ('\'');
+
+                       for (int i = 0; i < value.Length; ++i) {
+                               var ch = value[i];
+                               if (ch > 127) {
+                                       foreach (var b in encoding.GetBytes (new[] { ch })) {
+                                               sb.Append ('%');
+                                               sb.Append (b.ToString ("X2"));
+                                       }
+
+                                       continue;
+                               }
+
+                               sb.Append (ch);
+                       }
+
+                       return sb.ToString ();
+               }
+
+               static string DecodeValue (string value, bool extendedNotation)
+               {
+                       //
+                       // A short (length <= 78 characters)
+                       // parameter value containing only non-`tspecials' characters SHOULD be
+                       // represented as a single `token'.  A short parameter value containing
+                       // only ASCII characters, but including `tspecials' characters, SHOULD
+                       // be represented as `quoted-string'.  Parameter values longer than 78
+                       // characters, or which contain non-ASCII characters, MUST be encoded as
+                       // specified in [RFC 2184].
+                       //
+                       if (value.Length < 2)
+                               return value;
+
+                       string[] sep;
+                       Encoding encoding;
+
+                       // Quoted string
+                       if (value[0] == '\"') {
+                               //
+                               // Is Base64 encoded ?
+                               // encoded-word := "=?" charset "?" encoding "?" encoded-text "?="
+                               //
+                               sep = value.Split ('?');
+                               if (sep.Length != 5 || sep[0] != "\"=" || sep[4] != "=\"" || (sep[2] != "B" && sep[2] != "b"))
+                                       return value;
+
+                               try {
+                                       encoding = Encoding.GetEncoding (sep[1]);
+                                       return encoding.GetString (Convert.FromBase64String (sep[3]));
+                               } catch {
+                                       return value;
+                               }
+                       }
+
+                       if (!extendedNotation)
+                               return value;
+
+                       //
+                       // RFC 5987: Charset/Language Encoding
+                       //
+                       sep = value.Split ('\'');
+                       if (sep.Length != 3)
+                               return null;
+
+                       try {
+                               encoding = Encoding.GetEncoding (sep[0]);
+                       } catch {
+                               return null;
+                       }
+
+                       // TODO: What to do with sep[1] language
+
+                       value = sep[2];
+
+                       int pct_encoded = value.IndexOf ('%');
+                       if (pct_encoded < 0)
+                               return value;
+
+                       StringBuilder sb = new StringBuilder ();
+                       byte[] buffer = null;
+                       int buffer_pos = 0;
+
+                       for (int i = 0; i < value.Length;) {
+                               var ch = value[i];
+                               if (ch == '%') {
+                                       var unescaped = ch;
+                                       ch = Uri.HexUnescape (value, ref i);
+                                       if (ch != unescaped) {
+                                               if (buffer == null)
+                                                       buffer = new byte[value.Length - i + 1];
+
+                                               buffer[buffer_pos++] = (byte) ch;
+                                               continue;
+                                       }
+                               } else {
+                                       ++i;
+                               }
+
+                               if (buffer_pos != 0) {
+                                       sb.Append (encoding.GetChars (buffer, 0, buffer_pos));
+                                       buffer_pos = 0;
+                               }
+
+                               sb.Append (ch);
+                       }
+
+                       if (buffer_pos != 0) {
+                               sb.Append (encoding.GetChars (buffer, 0, buffer_pos));
+                       }
+
+                       return sb.ToString ();
+               }
+
+               public override int GetHashCode ()
+               {
+                       return dispositionType.ToLowerInvariant ().GetHashCode () ^
+                               HashCodeCalculator.Calculate (parameters);
+               }
+
+               public static ContentDispositionHeaderValue Parse (string input)
+               {
+                       ContentDispositionHeaderValue value;
+                       if (TryParse (input, out value))
+                               return value;
+
+                       throw new FormatException (input);
+               }
+
+               void SetDateValue (string key, DateTimeOffset? value)
+               {
+                       SetValue (key, value == null ? null : ("\"" + value.Value.ToString ("r", CultureInfo.InvariantCulture)) + "\"");
+               }
+
+               void SetValue (string key, string value)
+               {
+                       if (parameters == null)
+                               parameters = new List<NameValueHeaderValue> ();
+
+                       parameters.SetValue (key, value);
+               }
+
+               public override string ToString ()
+               {
+                       return dispositionType + CollectionExtensions.ToString (parameters);
+               }
+
+               public static bool TryParse (string input, out ContentDispositionHeaderValue parsedValue)
+               {
+                       parsedValue = null;
+
+                       var lexer = new Lexer (input);
+                       var t = lexer.Scan ();
+                       if (t.Kind != Token.Type.Token)
+                               return false;
+
+                       List<NameValueHeaderValue> parameters = null;
+                       var type = lexer.GetStringValue (t);
+
+                       t = lexer.Scan ();
+
+                       switch (t.Kind) {
+                       case Token.Type.SeparatorSemicolon:
+                               if (!NameValueHeaderValue.TryParseParameters (lexer, out parameters, out t) || t != Token.Type.End)
+                                       return false;
+                               break;
+                       case Token.Type.End:
+                               break;
+                       default:
+                               return false;
+                       }
+
+                       parsedValue = new ContentDispositionHeaderValue () {
+                               dispositionType = type,
+                               parameters = parameters
+                       };
+
+                       return true;
                }
        }
 }