HttpWebResponse.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. //
  2. // System.Net.HttpWebResponse
  3. //
  4. // Authors:
  5. // Lawrence Pit ([email protected])
  6. // Gonzalo Paniagua Javier ([email protected])
  7. // Daniel Nauck (dna(at)mono-project(dot)de)
  8. //
  9. // (c) 2002 Lawrence Pit
  10. // (c) 2003 Ximian, Inc. (http://www.ximian.com)
  11. // (c) 2008 Daniel Nauck
  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.Globalization;
  36. using System.IO;
  37. using System.Net.Sockets;
  38. using System.Runtime.Serialization;
  39. using System.Text;
  40. namespace System.Net
  41. {
  42. [Serializable]
  43. public class HttpWebResponse : WebResponse, ISerializable, IDisposable
  44. {
  45. Uri uri;
  46. WebHeaderCollection webHeaders;
  47. CookieCollection cookieCollection;
  48. string method;
  49. Version version;
  50. HttpStatusCode statusCode;
  51. string statusDescription;
  52. long contentLength = -1;
  53. string contentType;
  54. CookieContainer cookie_container;
  55. bool disposed = false;
  56. Stream stream;
  57. // Constructors
  58. internal HttpWebResponse (Uri uri, string method, WebConnectionData data, CookieContainer container)
  59. {
  60. this.uri = uri;
  61. this.method = method;
  62. webHeaders = data.Headers;
  63. version = data.Version;
  64. statusCode = (HttpStatusCode) data.StatusCode;
  65. statusDescription = data.StatusDescription;
  66. stream = data.stream;
  67. if (container != null) {
  68. this.cookie_container = container;
  69. FillCookies ();
  70. }
  71. }
  72. #if NET_2_0
  73. [Obsolete ("Serialization is obsoleted for this type", false)]
  74. #endif
  75. protected HttpWebResponse (SerializationInfo serializationInfo, StreamingContext streamingContext)
  76. {
  77. SerializationInfo info = serializationInfo;
  78. uri = (Uri) info.GetValue ("uri", typeof (Uri));
  79. contentLength = info.GetInt64 ("contentLength");
  80. contentType = info.GetString ("contentType");
  81. method = info.GetString ("method");
  82. statusDescription = info.GetString ("statusDescription");
  83. cookieCollection = (CookieCollection) info.GetValue ("cookieCollection", typeof (CookieCollection));
  84. version = (Version) info.GetValue ("version", typeof (Version));
  85. statusCode = (HttpStatusCode) info.GetValue ("statusCode", typeof (HttpStatusCode));
  86. }
  87. // Properties
  88. public string CharacterSet {
  89. // Content-Type = "Content-Type" ":" media-type
  90. // media-type = type "/" subtype *( ";" parameter )
  91. // parameter = attribute "=" value
  92. // 3.7.1. default is ISO-8859-1
  93. get {
  94. CheckDisposed ();
  95. string contentType = ContentType;
  96. if (contentType == null)
  97. return "ISO-8859-1";
  98. string val = contentType.ToLower ();
  99. int pos = val.IndexOf ("charset=");
  100. if (pos == -1)
  101. return "ISO-8859-1";
  102. pos += 8;
  103. int pos2 = val.IndexOf (';', pos);
  104. return (pos2 == -1)
  105. ? contentType.Substring (pos)
  106. : contentType.Substring (pos, pos2 - pos);
  107. }
  108. }
  109. public string ContentEncoding {
  110. get {
  111. CheckDisposed ();
  112. string h = webHeaders ["Content-Encoding"];
  113. return h != null ? h : "";
  114. }
  115. }
  116. public override long ContentLength {
  117. get {
  118. CheckDisposed ();
  119. if (contentLength != -1)
  120. return contentLength;
  121. try {
  122. contentLength = (long) UInt64.Parse (webHeaders ["Content-Length"]);
  123. } catch (Exception) {
  124. return -1;
  125. }
  126. return contentLength;
  127. }
  128. }
  129. public override string ContentType {
  130. get {
  131. CheckDisposed ();
  132. if (contentType == null)
  133. contentType = webHeaders ["Content-Type"];
  134. return contentType;
  135. }
  136. }
  137. public CookieCollection Cookies {
  138. get {
  139. CheckDisposed ();
  140. if (cookieCollection == null)
  141. cookieCollection = new CookieCollection ();
  142. return cookieCollection;
  143. }
  144. set {
  145. CheckDisposed ();
  146. cookieCollection = value;
  147. }
  148. }
  149. public override WebHeaderCollection Headers {
  150. get {
  151. CheckDisposed ();
  152. return webHeaders;
  153. }
  154. }
  155. #if NET_2_0
  156. static Exception GetMustImplement ()
  157. {
  158. return new NotImplementedException ();
  159. }
  160. [MonoTODO]
  161. public override bool IsMutuallyAuthenticated
  162. {
  163. get {
  164. throw GetMustImplement ();
  165. }
  166. }
  167. #endif
  168. public DateTime LastModified {
  169. get {
  170. CheckDisposed ();
  171. try {
  172. string dtStr = webHeaders ["Last-Modified"];
  173. return MonoHttpDate.Parse (dtStr);
  174. } catch (Exception) {
  175. return DateTime.Now;
  176. }
  177. }
  178. }
  179. public string Method {
  180. get {
  181. CheckDisposed ();
  182. return method;
  183. }
  184. }
  185. public Version ProtocolVersion {
  186. get {
  187. CheckDisposed ();
  188. return version;
  189. }
  190. }
  191. public override Uri ResponseUri {
  192. get {
  193. CheckDisposed ();
  194. return uri;
  195. }
  196. }
  197. public string Server {
  198. get {
  199. CheckDisposed ();
  200. return webHeaders ["Server"];
  201. }
  202. }
  203. public HttpStatusCode StatusCode {
  204. get {
  205. CheckDisposed ();
  206. return statusCode;
  207. }
  208. }
  209. public string StatusDescription {
  210. get {
  211. CheckDisposed ();
  212. return statusDescription;
  213. }
  214. }
  215. // Methods
  216. #if !NET_2_0
  217. public override int GetHashCode ()
  218. {
  219. CheckDisposed ();
  220. return base.GetHashCode ();
  221. }
  222. #endif
  223. public string GetResponseHeader (string headerName)
  224. {
  225. CheckDisposed ();
  226. string value = webHeaders [headerName];
  227. return (value != null) ? value : "";
  228. }
  229. internal void ReadAll ()
  230. {
  231. WebConnectionStream wce = stream as WebConnectionStream;
  232. if (wce == null)
  233. return;
  234. try {
  235. wce.ReadAll ();
  236. } catch {}
  237. }
  238. public override Stream GetResponseStream ()
  239. {
  240. CheckDisposed ();
  241. if (stream == null)
  242. return Stream.Null;
  243. if (0 == String.Compare (method, "HEAD", true)) // see par 4.3 & 9.4
  244. return Stream.Null;
  245. return stream;
  246. }
  247. void ISerializable.GetObjectData (SerializationInfo serializationInfo,
  248. StreamingContext streamingContext)
  249. {
  250. GetObjectData (serializationInfo, streamingContext);
  251. }
  252. #if NET_2_0
  253. protected override
  254. #endif
  255. void GetObjectData (SerializationInfo serializationInfo,
  256. StreamingContext streamingContext)
  257. {
  258. SerializationInfo info = serializationInfo;
  259. info.AddValue ("uri", uri);
  260. info.AddValue ("contentLength", contentLength);
  261. info.AddValue ("contentType", contentType);
  262. info.AddValue ("method", method);
  263. info.AddValue ("statusDescription", statusDescription);
  264. info.AddValue ("cookieCollection", cookieCollection);
  265. info.AddValue ("version", version);
  266. info.AddValue ("statusCode", statusCode);
  267. }
  268. // Cleaning up stuff
  269. public override void Close ()
  270. {
  271. ((IDisposable) this).Dispose ();
  272. }
  273. void IDisposable.Dispose ()
  274. {
  275. Dispose (true);
  276. GC.SuppressFinalize (this);
  277. }
  278. #if !NET_2_0
  279. protected virtual
  280. #endif
  281. void Dispose (bool disposing)
  282. {
  283. if (this.disposed)
  284. return;
  285. this.disposed = true;
  286. if (disposing) {
  287. // release managed resources
  288. uri = null;
  289. webHeaders = null;
  290. cookieCollection = null;
  291. method = null;
  292. version = null;
  293. statusDescription = null;
  294. }
  295. // release unmanaged resources
  296. Stream st = stream;
  297. stream = null;
  298. if (st != null)
  299. st.Close ();
  300. }
  301. private void CheckDisposed ()
  302. {
  303. if (disposed)
  304. throw new ObjectDisposedException (GetType ().FullName);
  305. }
  306. void FillCookies ()
  307. {
  308. if (webHeaders == null)
  309. return;
  310. string [] values = webHeaders.GetValues ("Set-Cookie");
  311. if (values != null) {
  312. foreach (string va in values)
  313. SetCookie (va);
  314. }
  315. values = webHeaders.GetValues ("Set-Cookie2");
  316. if (values != null) {
  317. foreach (string va in values)
  318. SetCookie2 (va);
  319. }
  320. }
  321. void SetCookie (string header)
  322. {
  323. string name, val;
  324. Cookie cookie = null;
  325. CookieParser parser = new CookieParser (header);
  326. while (parser.GetNextNameValue (out name, out val)) {
  327. if ((name == null || name == "") && cookie == null)
  328. continue;
  329. if (cookie == null) {
  330. cookie = new Cookie (name, val);
  331. continue;
  332. }
  333. name = name.ToUpper ();
  334. switch (name) {
  335. case "COMMENT":
  336. if (cookie.Comment == null)
  337. cookie.Comment = val;
  338. break;
  339. case "COMMENTURL":
  340. if (cookie.CommentUri == null)
  341. cookie.CommentUri = new Uri (val);
  342. break;
  343. case "DISCARD":
  344. cookie.Discard = true;
  345. break;
  346. case "DOMAIN":
  347. if (cookie.Domain == "")
  348. cookie.Domain = val;
  349. break;
  350. #if NET_2_0
  351. case "HTTPONLY":
  352. cookie.HttpOnly = true;
  353. break;
  354. #endif
  355. case "MAX-AGE": // RFC Style Set-Cookie2
  356. if (cookie.Expires == DateTime.MinValue) {
  357. try {
  358. cookie.Expires = cookie.TimeStamp.AddSeconds (UInt32.Parse (val));
  359. } catch {}
  360. }
  361. break;
  362. case "EXPIRES": // Netscape Style Set-Cookie
  363. if (cookie.Expires != DateTime.MinValue)
  364. break;
  365. cookie.Expires = TryParseCookieExpires (val);
  366. break;
  367. case "PATH":
  368. cookie.Path = val;
  369. break;
  370. case "PORT":
  371. if (cookie.Port == null)
  372. cookie.Port = val;
  373. break;
  374. case "SECURE":
  375. cookie.Secure = true;
  376. break;
  377. case "VERSION":
  378. try {
  379. cookie.Version = (int) UInt32.Parse (val);
  380. } catch {}
  381. break;
  382. }
  383. }
  384. if (cookieCollection == null)
  385. cookieCollection = new CookieCollection ();
  386. if (cookie.Domain == "")
  387. cookie.Domain = uri.Host;
  388. cookieCollection.Add (cookie);
  389. if (cookie_container != null)
  390. cookie_container.Add (uri, cookie);
  391. }
  392. void SetCookie2 (string cookies_str)
  393. {
  394. string [] cookies = cookies_str.Split (',');
  395. foreach (string cookie_str in cookies)
  396. SetCookie (cookie_str);
  397. }
  398. string[] cookieExpiresFormats =
  399. new string[] { "r",
  400. "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'",
  401. "ddd, dd'-'MMM'-'yy HH':'mm':'ss 'GMT'" };
  402. DateTime TryParseCookieExpires (string value)
  403. {
  404. if (value == null || value.Length == 0)
  405. return DateTime.MinValue;
  406. for (int i = 0; i <= cookieExpiresFormats.Length; i++)
  407. {
  408. try {
  409. DateTime cookieExpiresUtc = DateTime.ParseExact (value, cookieExpiresFormats [i], CultureInfo.InvariantCulture);
  410. //convert UTC/GMT time to local time
  411. #if NET_2_0
  412. cookieExpiresUtc = DateTime.SpecifyKind (cookieExpiresUtc, DateTimeKind.Utc);
  413. return TimeZone.CurrentTimeZone.ToLocalTime (cookieExpiresUtc);
  414. #else
  415. //DateTime.Kind is only available on .NET 2.0, so do some calculation
  416. TimeSpan localOffset = TimeZone.CurrentTimeZone.GetUtcOffset (cookieExpiresUtc.Date);
  417. return cookieExpiresUtc.Add (localOffset);
  418. #endif
  419. } catch {}
  420. }
  421. //If we can't parse Expires, use cookie as session cookie (expires is DateTime.MinValue)
  422. return DateTime.MinValue;
  423. }
  424. }
  425. class CookieParser {
  426. string header;
  427. int pos;
  428. int length;
  429. public CookieParser (string header) : this (header, 0)
  430. {
  431. }
  432. public CookieParser (string header, int position)
  433. {
  434. this.header = header;
  435. this.pos = position;
  436. this.length = header.Length;
  437. }
  438. public bool GetNextNameValue (out string name, out string val)
  439. {
  440. name = null;
  441. val = null;
  442. if (pos >= length)
  443. return false;
  444. name = GetCookieName ();
  445. if (pos < header.Length && header [pos] == '=') {
  446. pos++;
  447. val = GetCookieValue ();
  448. }
  449. if (pos < length && header [pos] == ';')
  450. pos++;
  451. return true;
  452. }
  453. string GetCookieName ()
  454. {
  455. int k = pos;
  456. while (k < length && Char.IsWhiteSpace (header [k]))
  457. k++;
  458. int begin = k;
  459. while (k < length && header [k] != ';' && header [k] != '=')
  460. k++;
  461. pos = k;
  462. return header.Substring (begin, k - begin).Trim ();
  463. }
  464. string GetCookieValue ()
  465. {
  466. if (pos >= length)
  467. return null;
  468. int k = pos;
  469. while (k < length && Char.IsWhiteSpace (header [k]))
  470. k++;
  471. int begin;
  472. if (header [k] == '"'){
  473. int j;
  474. begin = ++k;
  475. while (k < length && header [k] != '"')
  476. k++;
  477. for (j = k; j < length && header [j] != ';'; j++)
  478. ;
  479. pos = j;
  480. } else {
  481. begin = k;
  482. while (k < length && header [k] != ';')
  483. k++;
  484. pos = k;
  485. }
  486. return header.Substring (begin, k - begin).Trim ();
  487. }
  488. }
  489. }