ContentDispositionHeaderValue.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  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. var value = FindParameter ("name");
  108. if (value == null)
  109. return null;
  110. return DecodeValue (value, false);
  111. }
  112. set {
  113. if (value != null)
  114. value = EncodeBase64Value (value);
  115. SetValue ("name", value);
  116. }
  117. }
  118. public ICollection<NameValueHeaderValue> Parameters {
  119. get {
  120. return parameters ?? (parameters = new List<NameValueHeaderValue> ());
  121. }
  122. }
  123. public DateTimeOffset? ReadDate {
  124. get {
  125. return GetDateValue ("read-date");
  126. }
  127. set {
  128. SetDateValue ("read-date", value);
  129. }
  130. }
  131. public long? Size {
  132. get {
  133. var found = FindParameter ("size");
  134. long result;
  135. if (Parser.Long.TryParse (found, out result))
  136. return result;
  137. return null;
  138. }
  139. set {
  140. if (value == null) {
  141. SetValue ("size", null);
  142. return;
  143. }
  144. if (value < 0)
  145. throw new ArgumentOutOfRangeException ("value");
  146. SetValue ("size", value.Value.ToString (CultureInfo.InvariantCulture));
  147. }
  148. }
  149. object ICloneable.Clone ()
  150. {
  151. return new ContentDispositionHeaderValue (this);
  152. }
  153. public override bool Equals (object obj)
  154. {
  155. var source = obj as ContentDispositionHeaderValue;
  156. return source != null &&
  157. string.Equals (source.dispositionType, dispositionType, StringComparison.OrdinalIgnoreCase) &&
  158. source.parameters.SequenceEqual (parameters);
  159. }
  160. string FindParameter (string name)
  161. {
  162. if (parameters == null)
  163. return null;
  164. foreach (var entry in parameters) {
  165. if (string.Equals (entry.Name, name, StringComparison.OrdinalIgnoreCase))
  166. return entry.Value;
  167. }
  168. return null;
  169. }
  170. DateTimeOffset? GetDateValue (string name)
  171. {
  172. var value = FindParameter (name);
  173. if (value == null || value == null)
  174. return null;
  175. if (value.Length < 3)
  176. return null;
  177. if (value[0] == '\"')
  178. value = value.Substring (1, value.Length - 2);
  179. DateTimeOffset offset;
  180. if (Lexer.TryGetDateValue (value, out offset))
  181. return offset;
  182. return null;
  183. }
  184. static string EncodeBase64Value (string value)
  185. {
  186. bool quoted = value.Length > 1 && value [0] == '"' && value [value.Length - 1] == '"';
  187. if (quoted)
  188. value = value.Substring (1, value.Length - 2);
  189. for (int i = 0; i < value.Length; ++i) {
  190. var ch = value[i];
  191. if (ch > 127) {
  192. var encoding = Encoding.UTF8;
  193. return string.Format ("\"=?{0}?B?{1}?=\"",
  194. encoding.WebName, Convert.ToBase64String (encoding.GetBytes (value)));
  195. }
  196. }
  197. if (quoted || !Lexer.IsValidToken (value))
  198. return "\"" + value + "\"";
  199. return value;
  200. }
  201. static string EncodeRFC5987 (string value)
  202. {
  203. var encoding = Encoding.UTF8;
  204. StringBuilder sb = new StringBuilder (value.Length + 11);
  205. sb.Append (encoding.WebName);
  206. sb.Append ('\'');
  207. sb.Append ('\'');
  208. for (int i = 0; i < value.Length; ++i) {
  209. var ch = value[i];
  210. if (ch > 127) {
  211. foreach (var b in encoding.GetBytes (new[] { ch })) {
  212. sb.Append ('%');
  213. sb.Append (b.ToString ("X2"));
  214. }
  215. continue;
  216. }
  217. if (!Lexer.IsValidCharacter (ch) || ch == '*' || ch == '?' || ch == '%') {
  218. sb.Append (Uri.HexEscape (ch));
  219. continue;
  220. }
  221. sb.Append (ch);
  222. }
  223. return sb.ToString ();
  224. }
  225. static string DecodeValue (string value, bool extendedNotation)
  226. {
  227. //
  228. // A short (length <= 78 characters)
  229. // parameter value containing only non-`tspecials' characters SHOULD be
  230. // represented as a single `token'. A short parameter value containing
  231. // only ASCII characters, but including `tspecials' characters, SHOULD
  232. // be represented as `quoted-string'. Parameter values longer than 78
  233. // characters, or which contain non-ASCII characters, MUST be encoded as
  234. // specified in [RFC 2184].
  235. //
  236. if (value.Length < 2)
  237. return value;
  238. string[] sep;
  239. Encoding encoding;
  240. // Quoted string
  241. if (value[0] == '\"') {
  242. //
  243. // Is Base64 encoded ?
  244. // encoded-word := "=?" charset "?" encoding "?" encoded-text "?="
  245. //
  246. sep = value.Split ('?');
  247. if (sep.Length != 5 || sep[0] != "\"=" || sep[4] != "=\"" || (sep[2] != "B" && sep[2] != "b"))
  248. return value;
  249. try {
  250. encoding = Encoding.GetEncoding (sep[1]);
  251. return encoding.GetString (Convert.FromBase64String (sep[3]));
  252. } catch {
  253. return value;
  254. }
  255. }
  256. if (!extendedNotation)
  257. return value;
  258. //
  259. // RFC 5987: Charset/Language Encoding
  260. //
  261. sep = value.Split ('\'');
  262. if (sep.Length != 3)
  263. return null;
  264. try {
  265. encoding = Encoding.GetEncoding (sep[0]);
  266. } catch {
  267. return null;
  268. }
  269. // TODO: What to do with sep[1] language
  270. value = sep[2];
  271. int pct_encoded = value.IndexOf ('%');
  272. if (pct_encoded < 0)
  273. return value;
  274. StringBuilder sb = new StringBuilder ();
  275. byte[] buffer = null;
  276. int buffer_pos = 0;
  277. for (int i = 0; i < value.Length;) {
  278. var ch = value[i];
  279. if (ch == '%') {
  280. var unescaped = ch;
  281. ch = Uri.HexUnescape (value, ref i);
  282. if (ch != unescaped) {
  283. if (buffer == null)
  284. buffer = new byte[value.Length - i + 1];
  285. buffer[buffer_pos++] = (byte) ch;
  286. continue;
  287. }
  288. } else {
  289. ++i;
  290. }
  291. if (buffer_pos != 0) {
  292. sb.Append (encoding.GetChars (buffer, 0, buffer_pos));
  293. buffer_pos = 0;
  294. }
  295. sb.Append (ch);
  296. }
  297. if (buffer_pos != 0) {
  298. sb.Append (encoding.GetChars (buffer, 0, buffer_pos));
  299. }
  300. return sb.ToString ();
  301. }
  302. public override int GetHashCode ()
  303. {
  304. return dispositionType.ToLowerInvariant ().GetHashCode () ^
  305. HashCodeCalculator.Calculate (parameters);
  306. }
  307. public static ContentDispositionHeaderValue Parse (string input)
  308. {
  309. ContentDispositionHeaderValue value;
  310. if (TryParse (input, out value))
  311. return value;
  312. throw new FormatException (input);
  313. }
  314. void SetDateValue (string key, DateTimeOffset? value)
  315. {
  316. SetValue (key, value == null ? null : ("\"" + value.Value.ToString ("r", CultureInfo.InvariantCulture)) + "\"");
  317. }
  318. void SetValue (string key, string value)
  319. {
  320. if (parameters == null)
  321. parameters = new List<NameValueHeaderValue> ();
  322. parameters.SetValue (key, value);
  323. }
  324. public override string ToString ()
  325. {
  326. return dispositionType + CollectionExtensions.ToString (parameters);
  327. }
  328. public static bool TryParse (string input, out ContentDispositionHeaderValue parsedValue)
  329. {
  330. parsedValue = null;
  331. var lexer = new Lexer (input);
  332. var t = lexer.Scan ();
  333. if (t.Kind != Token.Type.Token)
  334. return false;
  335. List<NameValueHeaderValue> parameters = null;
  336. var type = lexer.GetStringValue (t);
  337. t = lexer.Scan ();
  338. switch (t.Kind) {
  339. case Token.Type.SeparatorSemicolon:
  340. if (!NameValueHeaderValue.TryParseParameters (lexer, out parameters, out t) || t != Token.Type.End)
  341. return false;
  342. break;
  343. case Token.Type.End:
  344. break;
  345. default:
  346. return false;
  347. }
  348. parsedValue = new ContentDispositionHeaderValue () {
  349. dispositionType = type,
  350. parameters = parameters
  351. };
  352. return true;
  353. }
  354. }
  355. }