ContentDispositionHeaderValue.cs 9.5 KB

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