HttpWebResponse.cs 13 KB

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