WebHeaderCollection.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. //
  2. // System.Net.WebHeaderCollection
  3. //
  4. // Authors:
  5. // Lawrence Pit ([email protected])
  6. // Gonzalo Paniagua Javier ([email protected])
  7. // Miguel de Icaza ([email protected])
  8. //
  9. // Copyright 2003 Ximian, Inc. (http://www.ximian.com)
  10. // Copyright 2007 Novell, Inc. (http://www.novell.com)
  11. //
  12. //
  13. //
  14. // Permission is hereby granted, free of charge, to any person obtaining
  15. // a copy of this software and associated documentation files (the
  16. // "Software"), to deal in the Software without restriction, including
  17. // without limitation the rights to use, copy, modify, merge, publish,
  18. // distribute, sublicense, and/or sell copies of the Software, and to
  19. // permit persons to whom the Software is furnished to do so, subject to
  20. // the following conditions:
  21. //
  22. // The above copyright notice and this permission notice shall be
  23. // included in all copies or substantial portions of the Software.
  24. //
  25. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  26. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  27. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  28. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  29. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  30. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  31. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  32. //
  33. using System;
  34. using System.Collections;
  35. using System.Collections.Specialized;
  36. using System.Runtime.InteropServices;
  37. using System.Runtime.Serialization;
  38. using System.Text;
  39. // See RFC 2068 par 4.2 Message Headers
  40. namespace System.Net
  41. {
  42. [Serializable]
  43. [ComVisible(true)]
  44. public class WebHeaderCollection : NameValueCollection, ISerializable
  45. {
  46. private static readonly Hashtable restricted;
  47. private static readonly Hashtable multiValue;
  48. private bool internallyCreated = false;
  49. // Static Initializer
  50. static WebHeaderCollection ()
  51. {
  52. // the list of restricted header names as defined
  53. // by the ms.net spec
  54. restricted = new Hashtable (CaseInsensitiveHashCodeProvider.DefaultInvariant,
  55. CaseInsensitiveComparer.DefaultInvariant);
  56. restricted.Add ("accept", true);
  57. restricted.Add ("connection", true);
  58. restricted.Add ("content-length", true);
  59. restricted.Add ("content-type", true);
  60. restricted.Add ("date", true);
  61. restricted.Add ("expect", true);
  62. restricted.Add ("host", true);
  63. restricted.Add ("if-modified-since", true);
  64. restricted.Add ("range", true);
  65. restricted.Add ("referer", true);
  66. restricted.Add ("transfer-encoding", true);
  67. restricted.Add ("user-agent", true);
  68. // see par 14 of RFC 2068 to see which header names
  69. // accept multiple values each separated by a comma
  70. multiValue = new Hashtable (CaseInsensitiveHashCodeProvider.DefaultInvariant,
  71. CaseInsensitiveComparer.DefaultInvariant);
  72. multiValue.Add ("accept", true);
  73. multiValue.Add ("accept-charset", true);
  74. multiValue.Add ("accept-encoding", true);
  75. multiValue.Add ("accept-language", true);
  76. multiValue.Add ("accept-ranges", true);
  77. multiValue.Add ("allow", true);
  78. multiValue.Add ("authorization", true);
  79. multiValue.Add ("cache-control", true);
  80. multiValue.Add ("connection", true);
  81. multiValue.Add ("content-encoding", true);
  82. multiValue.Add ("content-language", true);
  83. multiValue.Add ("expect", true);
  84. multiValue.Add ("if-match", true);
  85. multiValue.Add ("if-none-match", true);
  86. multiValue.Add ("proxy-authenticate", true);
  87. multiValue.Add ("public", true);
  88. multiValue.Add ("range", true);
  89. multiValue.Add ("transfer-encoding", true);
  90. multiValue.Add ("upgrade", true);
  91. multiValue.Add ("vary", true);
  92. multiValue.Add ("via", true);
  93. multiValue.Add ("warning", true);
  94. multiValue.Add ("www-authenticate", true);
  95. // Extra
  96. multiValue.Add ("set-cookie", true);
  97. multiValue.Add ("set-cookie2", true);
  98. }
  99. // Constructors
  100. public WebHeaderCollection () { }
  101. protected WebHeaderCollection (SerializationInfo serializationInfo,
  102. StreamingContext streamingContext)
  103. {
  104. int count;
  105. try {
  106. count = serializationInfo.GetInt32("Count");
  107. for (int i = 0; i < count; i++)
  108. this.Add (serializationInfo.GetString (i.ToString ()),
  109. serializationInfo.GetString ((count + i).ToString ()));
  110. } catch (SerializationException){
  111. count = serializationInfo.GetInt32("count");
  112. for (int i = 0; i < count; i++)
  113. this.Add (serializationInfo.GetString ("k" + i),
  114. serializationInfo.GetString ("v" + i));
  115. }
  116. }
  117. internal WebHeaderCollection (bool internallyCreated)
  118. {
  119. this.internallyCreated = internallyCreated;
  120. }
  121. // Methods
  122. public void Add (string header)
  123. {
  124. if (header == null)
  125. throw new ArgumentNullException ("header");
  126. int pos = header.IndexOf (':');
  127. if (pos == -1)
  128. throw new ArgumentException ("no colon found", "header");
  129. this.Add (header.Substring (0, pos),
  130. header.Substring (pos + 1));
  131. }
  132. public override void Add (string name, string value)
  133. {
  134. if (name == null)
  135. throw new ArgumentNullException ("name");
  136. if (internallyCreated && IsRestricted (name))
  137. throw new ArgumentException ("This header must be modified with the appropiate property.");
  138. this.AddWithoutValidate (name, value);
  139. }
  140. protected void AddWithoutValidate (string headerName, string headerValue)
  141. {
  142. if (!IsHeaderName (headerName))
  143. throw new ArgumentException ("invalid header name: " + headerName, "headerName");
  144. if (headerValue == null)
  145. headerValue = String.Empty;
  146. else
  147. headerValue = headerValue.Trim ();
  148. if (!IsHeaderValue (headerValue))
  149. throw new ArgumentException ("invalid header value: " + headerValue, "headerValue");
  150. base.Add (headerName, headerValue);
  151. }
  152. public override string [] GetValues (string header)
  153. {
  154. if (header == null)
  155. throw new ArgumentNullException ("header");
  156. string [] values = base.GetValues (header);
  157. if (values == null || values.Length == 0)
  158. return null;
  159. /*
  160. if (IsMultiValue (header)) {
  161. values = GetMultipleValues (values);
  162. }
  163. */
  164. return values;
  165. }
  166. /* Now i wonder why this is here...
  167. static string [] GetMultipleValues (string [] values)
  168. {
  169. ArrayList mvalues = new ArrayList (values.Length);
  170. StringBuilder sb = null;
  171. for (int i = 0; i < values.Length; ++i) {
  172. string val = values [i];
  173. if (val.IndexOf (',') == -1) {
  174. mvalues.Add (val);
  175. continue;
  176. }
  177. if (sb == null)
  178. sb = new StringBuilder ();
  179. bool quote = false;
  180. for (int k = 0; k < val.Length; k++) {
  181. char c = val [k];
  182. if (c == '"') {
  183. quote = !quote;
  184. } else if (!quote && c == ',') {
  185. mvalues.Add (sb.ToString ().Trim ());
  186. sb.Length = 0;
  187. continue;
  188. }
  189. sb.Append (c);
  190. }
  191. if (sb.Length > 0) {
  192. mvalues.Add (sb.ToString ().Trim ());
  193. sb.Length = 0;
  194. }
  195. }
  196. return (string []) mvalues.ToArray (typeof (string));
  197. }
  198. */
  199. public static bool IsRestricted (string headerName)
  200. {
  201. if (headerName == null)
  202. throw new ArgumentNullException ("headerName");
  203. if (headerName == "") // MS throw nullexception here!
  204. throw new ArgumentException ("empty string", "headerName");
  205. return restricted.ContainsKey (headerName);
  206. }
  207. #if NET_2_0
  208. [MonoNotSupported("")]
  209. public static bool IsRestricted (string headerName, bool response)
  210. {
  211. throw new NotImplementedException ();
  212. }
  213. #endif
  214. public override void OnDeserialization (object sender)
  215. {
  216. }
  217. public override void Remove (string name)
  218. {
  219. if (name == null)
  220. throw new ArgumentNullException ("name");
  221. if (internallyCreated && IsRestricted (name))
  222. throw new ArgumentException ("restricted header");
  223. base.Remove (name);
  224. }
  225. public override void Set (string name, string value)
  226. {
  227. if (name == null)
  228. throw new ArgumentNullException ("name");
  229. if (internallyCreated && IsRestricted (name))
  230. throw new ArgumentException ("restricted header");
  231. if (!IsHeaderName (name))
  232. throw new ArgumentException ("invalid header name");
  233. if (value == null)
  234. value = String.Empty;
  235. else
  236. value = value.Trim ();
  237. if (!IsHeaderValue (value))
  238. throw new ArgumentException ("invalid header value");
  239. base.Set (name, value);
  240. }
  241. public byte[] ToByteArray ()
  242. {
  243. return Encoding.UTF8.GetBytes(ToString ());
  244. }
  245. public override string ToString ()
  246. {
  247. StringBuilder sb = new StringBuilder();
  248. int count = base.Count;
  249. for (int i = 0; i < count ; i++)
  250. sb.Append (GetKey (i))
  251. .Append (": ")
  252. .Append (Get (i))
  253. .Append ("\r\n");
  254. return sb.Append("\r\n").ToString();
  255. }
  256. void ISerializable.GetObjectData (SerializationInfo serializationInfo,
  257. StreamingContext streamingContext)
  258. {
  259. int count = base.Count;
  260. serializationInfo.AddValue ("Count", count);
  261. for (int i = 0; i < count; i++) {
  262. serializationInfo.AddValue (i.ToString (), GetKey (i));
  263. serializationInfo.AddValue ((count + i).ToString (), Get (i));
  264. }
  265. }
  266. #if NET_2_0
  267. public void Add (HttpRequestHeader header, string value)
  268. {
  269. Add (RequestHeaderToString (header), value);
  270. }
  271. public void Remove (HttpRequestHeader header)
  272. {
  273. Remove (RequestHeaderToString (header));
  274. }
  275. public void Set (HttpRequestHeader header, string value)
  276. {
  277. Set (RequestHeaderToString (header), value);
  278. }
  279. public void Add (HttpResponseHeader header, string value)
  280. {
  281. Add (ResponseHeaderToString (header), value);
  282. }
  283. public void Remove (HttpResponseHeader header)
  284. {
  285. Remove (ResponseHeaderToString (header));
  286. }
  287. public void Set (HttpResponseHeader header, string value)
  288. {
  289. Set (ResponseHeaderToString (header), value);
  290. }
  291. string RequestHeaderToString (HttpRequestHeader value)
  292. {
  293. switch (value){
  294. case HttpRequestHeader.CacheControl:
  295. return "cache-control";
  296. case HttpRequestHeader.Connection:
  297. return "connection";
  298. case HttpRequestHeader.Date:
  299. return "date";
  300. case HttpRequestHeader.KeepAlive:
  301. return "keep-alive";
  302. case HttpRequestHeader.Pragma:
  303. return "pragma";
  304. case HttpRequestHeader.Trailer:
  305. return "trailer";
  306. case HttpRequestHeader.TransferEncoding:
  307. return "transfer-encoding";
  308. case HttpRequestHeader.Upgrade:
  309. return "upgrade";
  310. case HttpRequestHeader.Via:
  311. return "via";
  312. case HttpRequestHeader.Warning:
  313. return "warning";
  314. case HttpRequestHeader.Allow:
  315. return "allow";
  316. case HttpRequestHeader.ContentLength:
  317. return "content-length";
  318. case HttpRequestHeader.ContentType:
  319. return "content-type";
  320. case HttpRequestHeader.ContentEncoding:
  321. return "content-encoding";
  322. case HttpRequestHeader.ContentLanguage:
  323. return "content-language";
  324. case HttpRequestHeader.ContentLocation:
  325. return "content-location";
  326. case HttpRequestHeader.ContentMd5:
  327. return "content-md5";
  328. case HttpRequestHeader.ContentRange:
  329. return "content-range";
  330. case HttpRequestHeader.Expires:
  331. return "expires";
  332. case HttpRequestHeader.LastModified:
  333. return "last-modified";
  334. case HttpRequestHeader.Accept:
  335. return "accept";
  336. case HttpRequestHeader.AcceptCharset:
  337. return "accept-charset";
  338. case HttpRequestHeader.AcceptEncoding:
  339. return "accept-encoding";
  340. case HttpRequestHeader.AcceptLanguage:
  341. return "accept-language";
  342. case HttpRequestHeader.Authorization:
  343. return "authorization";
  344. case HttpRequestHeader.Cookie:
  345. return "cookie";
  346. case HttpRequestHeader.Expect:
  347. return "expect";
  348. case HttpRequestHeader.From:
  349. return "from";
  350. case HttpRequestHeader.Host:
  351. return "host";
  352. case HttpRequestHeader.IfMatch:
  353. return "if-match";
  354. case HttpRequestHeader.IfModifiedSince:
  355. return "if-modified-since";
  356. case HttpRequestHeader.IfNoneMatch:
  357. return "if-none-match";
  358. case HttpRequestHeader.IfRange:
  359. return "if-range";
  360. case HttpRequestHeader.IfUnmodifiedSince:
  361. return "if-unmodified-since";
  362. case HttpRequestHeader.MaxForwards:
  363. return "max-forwards";
  364. case HttpRequestHeader.ProxyAuthorization:
  365. return "proxy-authorization";
  366. case HttpRequestHeader.Referer:
  367. return "referer";
  368. case HttpRequestHeader.Range:
  369. return "range";
  370. case HttpRequestHeader.Te:
  371. return "te";
  372. case HttpRequestHeader.Translate:
  373. return "translate";
  374. case HttpRequestHeader.UserAgent:
  375. return "user-agent";
  376. default:
  377. throw new InvalidOperationException ();
  378. }
  379. }
  380. public string this[HttpRequestHeader hrh]
  381. {
  382. get {
  383. return Get (RequestHeaderToString (hrh));
  384. }
  385. set {
  386. Add (RequestHeaderToString (hrh), value);
  387. }
  388. }
  389. string ResponseHeaderToString (HttpResponseHeader value)
  390. {
  391. switch (value){
  392. case HttpResponseHeader.CacheControl:
  393. return "cache-control";
  394. case HttpResponseHeader.Connection:
  395. return "connection";
  396. case HttpResponseHeader.Date:
  397. return "date";
  398. case HttpResponseHeader.KeepAlive:
  399. return "keep-alive";
  400. case HttpResponseHeader.Pragma:
  401. return "pragma";
  402. case HttpResponseHeader.Trailer:
  403. return "trailer";
  404. case HttpResponseHeader.TransferEncoding:
  405. return "transfer-encoding";
  406. case HttpResponseHeader.Upgrade:
  407. return "upgrade";
  408. case HttpResponseHeader.Via:
  409. return "via";
  410. case HttpResponseHeader.Warning:
  411. return "warning";
  412. case HttpResponseHeader.Allow:
  413. return "allow";
  414. case HttpResponseHeader.ContentLength:
  415. return "content-length";
  416. case HttpResponseHeader.ContentType:
  417. return "content-type";
  418. case HttpResponseHeader.ContentEncoding:
  419. return "content-encoding";
  420. case HttpResponseHeader.ContentLanguage:
  421. return "content-language";
  422. case HttpResponseHeader.ContentLocation:
  423. return "content-location";
  424. case HttpResponseHeader.ContentMd5:
  425. return "content-md5";
  426. case HttpResponseHeader.ContentRange:
  427. return "content-range";
  428. case HttpResponseHeader.Expires:
  429. return "expires";
  430. case HttpResponseHeader.LastModified:
  431. return "last-modified";
  432. case HttpResponseHeader.AcceptRanges:
  433. return "accept-ranges";
  434. case HttpResponseHeader.Age:
  435. return "age";
  436. case HttpResponseHeader.ETag:
  437. return "etag";
  438. case HttpResponseHeader.Location:
  439. return "location";
  440. case HttpResponseHeader.ProxyAuthenticate:
  441. return "proxy-authenticate";
  442. case HttpResponseHeader.RetryAfter:
  443. return "RetryAfter";
  444. case HttpResponseHeader.Server:
  445. return "server";
  446. case HttpResponseHeader.SetCookie:
  447. return "set-cookie";
  448. case HttpResponseHeader.Vary:
  449. return "vary";
  450. case HttpResponseHeader.WwwAuthenticate:
  451. return "www-authenticate";
  452. default:
  453. throw new InvalidOperationException ();
  454. }
  455. }
  456. public string this[HttpResponseHeader hrh]
  457. {
  458. get
  459. {
  460. return Get (ResponseHeaderToString (hrh));
  461. }
  462. set
  463. {
  464. Add (ResponseHeaderToString (hrh), value);
  465. }
  466. }
  467. #endif
  468. // Internal Methods
  469. // With this we don't check for invalid characters in header. See bug #55994.
  470. internal void SetInternal (string header)
  471. {
  472. int pos = header.IndexOf (':');
  473. if (pos == -1)
  474. throw new ArgumentException ("no colon found", "header");
  475. SetInternal (header.Substring (0, pos), header.Substring (pos + 1));
  476. }
  477. internal void SetInternal (string name, string value)
  478. {
  479. if (value == null)
  480. value = String.Empty;
  481. else
  482. value = value.Trim ();
  483. if (!IsHeaderValue (value))
  484. throw new ArgumentException ("invalid header value");
  485. if (IsMultiValue (name)) {
  486. base.Add (name, value);
  487. } else {
  488. base.Remove (name);
  489. base.Set (name, value);
  490. }
  491. }
  492. internal void RemoveAndAdd (string name, string value)
  493. {
  494. if (value == null)
  495. value = String.Empty;
  496. else
  497. value = value.Trim ();
  498. base.Remove (name);
  499. base.Set (name, value);
  500. }
  501. internal void RemoveInternal (string name)
  502. {
  503. if (name == null)
  504. throw new ArgumentNullException ("name");
  505. base.Remove (name);
  506. }
  507. // Private Methods
  508. internal static bool IsMultiValue (string headerName)
  509. {
  510. if (headerName == null || headerName == "")
  511. return false;
  512. return multiValue.ContainsKey (headerName);
  513. }
  514. internal static bool IsHeaderValue (string value)
  515. {
  516. // TEXT any 8 bit value except CTL's (0-31 and 127)
  517. // but including \r\n space and \t
  518. // after a newline at least one space or \t must follow
  519. // certain header fields allow comments ()
  520. int len = value.Length;
  521. for (int i = 0; i < len; i++) {
  522. char c = value [i];
  523. if (c == 127)
  524. return false;
  525. if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))
  526. return false;
  527. if (c == '\n' && ++i < len) {
  528. c = value [i];
  529. if (c != ' ' && c != '\t')
  530. return false;
  531. }
  532. }
  533. return true;
  534. }
  535. internal static bool IsHeaderName (string name)
  536. {
  537. // token = 1*<any CHAR except CTLs or tspecials>
  538. // tspecials = "(" | ")" | "<" | ">" | "@"
  539. // | "," | ";" | ":" | "\" | <">
  540. // | "/" | "[" | "]" | "?" | "="
  541. // | "{" | "}" | SP | HT
  542. if (name == null || name.Length == 0)
  543. return false;
  544. int len = name.Length;
  545. for (int i = 0; i < len; i++) {
  546. char c = name [i];
  547. if (c < 0x20 || c >= 0x7f)
  548. return false;
  549. }
  550. return name.IndexOfAny (tspecials) == -1;
  551. }
  552. private static char [] tspecials =
  553. new char [] {'(', ')', '<', '>', '@',
  554. ',', ';', ':', '\\', '"',
  555. '/', '[', ']', '?', '=',
  556. '{', '}', ' ', '\t'};
  557. }
  558. }