Commit
[mono.git] / mcs / class / System / System.Net / HttpWebResponse.cs
index 9c0c9c0e94ac7f4d4a94d21e0c3fb9ec6450d89c..3dc8bb97ec1cee776c1679b473e099153dda62a5 100644 (file)
@@ -4,12 +4,37 @@
 // Authors:
 //     Lawrence Pit (loz@cable.a2000.nl)
 //     Gonzalo Paniagua Javier (gonzalo@ximian.com)
+//      Daniel Nauck    (dna(at)mono-project(dot)de)
 //
 // (c) 2002 Lawrence Pit
 // (c) 2003 Ximian, Inc. (http://www.ximian.com)
+// (c) 2008 Daniel Nauck
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
 using System;
+using System.Collections;
+using System.Globalization;
 using System.IO;
 using System.Net.Sockets;
 using System.Runtime.Serialization;
@@ -27,16 +52,16 @@ namespace System.Net
                Version version;
                HttpStatusCode statusCode;
                string statusDescription;
-               bool chunked;
                long contentLength = -1;
                string contentType;
+               CookieContainer cookie_container;
 
                bool disposed = false;
-               WebConnectionStream stream;
+               Stream stream;
                
                // Constructors
                
-               internal HttpWebResponse (Uri uri, string method, WebConnectionData data, bool cookiesSet)
+               internal HttpWebResponse (Uri uri, string method, WebConnectionData data, CookieContainer container)
                {
                        this.uri = uri;
                        this.method = method;
@@ -45,27 +70,27 @@ namespace System.Net
                        statusCode = (HttpStatusCode) data.StatusCode;
                        statusDescription = data.StatusDescription;
                        stream = data.stream;
-                       if (cookiesSet) {
+                       if (container != null) {
+                               this.cookie_container = container;      
                                FillCookies ();
-                       } else if (webHeaders != null) {
-                               webHeaders.RemoveInternal ("Set-Cookie");
-                               webHeaders.RemoveInternal ("Set-Cookie2");
                        }
                }
 
-               [MonoTODO("Check this out and update if needed")] //Gon
+#if NET_2_0
+               [Obsolete ("Serialization is obsoleted for this type", false)]
+#endif
                protected HttpWebResponse (SerializationInfo serializationInfo, StreamingContext streamingContext)
                {
-                       uri = (Uri) serializationInfo.GetValue ("uri", typeof (Uri));
-                       webHeaders = (WebHeaderCollection) serializationInfo.GetValue ("webHeaders",
-                                                                                       typeof (WebHeaderCollection));
-                       cookieCollection = (CookieCollection) serializationInfo.GetValue ("cookieCollection",
-                                                                                          typeof (CookieCollection));
-                       method = serializationInfo.GetString ("method");
-                       version = (Version) serializationInfo.GetValue ("version", typeof (Version));
-                       statusCode = (HttpStatusCode) serializationInfo.GetValue ("statusCode", typeof (HttpStatusCode));
-                       statusDescription = serializationInfo.GetString ("statusDescription");
-                       chunked = serializationInfo.GetBoolean ("chunked");
+                       SerializationInfo info = serializationInfo;
+
+                       uri = (Uri) info.GetValue ("uri", typeof (Uri));
+                       contentLength = info.GetInt64 ("contentLength");
+                       contentType = info.GetString ("contentType");
+                       method = info.GetString ("method");
+                       statusDescription = info.GetString ("statusDescription");
+                       cookieCollection = (CookieCollection) info.GetValue ("cookieCollection", typeof (CookieCollection));
+                       version = (Version) info.GetValue ("version", typeof (Version));
+                       statusCode = (HttpStatusCode) info.GetValue ("statusCode", typeof (HttpStatusCode));
                }
                
                // Properties
@@ -95,7 +120,8 @@ namespace System.Net
                public string ContentEncoding {
                        get { 
                                CheckDisposed ();
-                               return webHeaders ["Content-Encoding"];
+                               string h = webHeaders ["Content-Encoding"];
+                               return h != null ? h : "";
                        }
                }
                
@@ -145,6 +171,21 @@ namespace System.Net
                                return webHeaders; 
                        }
                }
+
+#if NET_2_0
+               static Exception GetMustImplement ()
+               {
+                       return new NotImplementedException ();
+               }
+               
+               [MonoTODO]
+               public override bool IsMutuallyAuthenticated
+               {
+                       get {
+                               throw GetMustImplement ();
+                       }
+               }
+#endif
                
                public DateTime LastModified {
                        get {
@@ -201,12 +242,13 @@ namespace System.Net
                }
 
                // Methods
-               
+#if !NET_2_0
                public override int GetHashCode ()
                {
                        CheckDisposed ();
                        return base.GetHashCode ();
                }
+#endif
                
                public string GetResponseHeader (string headerName)
                {
@@ -214,10 +256,23 @@ namespace System.Net
                        string value = webHeaders [headerName];
                        return (value != null) ? value : "";
                }
-               
+
+               internal void ReadAll ()
+               {
+                       WebConnectionStream wce = stream as WebConnectionStream;
+                       if (wce == null)
+                               return;
+                               
+                       try {
+                               wce.ReadAll ();
+                       } catch {}
+               }
+
                public override Stream GetResponseStream ()
                {
                        CheckDisposed ();
+                       if (stream == null)
+                               return Stream.Null;  
                        if (0 == String.Compare (method, "HEAD", true)) // see par 4.3 & 9.4
                                return Stream.Null;  
 
@@ -227,17 +282,26 @@ namespace System.Net
                void ISerializable.GetObjectData (SerializationInfo serializationInfo,
                                                  StreamingContext streamingContext)
                {
-                       CheckDisposed ();
-                       serializationInfo.AddValue ("uri", uri);
-                       serializationInfo.AddValue ("webHeaders", webHeaders);
-                       serializationInfo.AddValue ("cookieCollection", cookieCollection);
-                       serializationInfo.AddValue ("method", method);
-                       serializationInfo.AddValue ("version", version);
-                       serializationInfo.AddValue ("statusCode", statusCode);
-                       serializationInfo.AddValue ("statusDescription", statusDescription);
-                       serializationInfo.AddValue ("chunked", chunked);
-               }               
+                       GetObjectData (serializationInfo, streamingContext);
+               }
+
+#if NET_2_0
+               protected override
+#endif
+               void GetObjectData (SerializationInfo serializationInfo,
+                                   StreamingContext streamingContext)
+               {
+                       SerializationInfo info = serializationInfo;
 
+                       info.AddValue ("uri", uri);
+                       info.AddValue ("contentLength", contentLength);
+                       info.AddValue ("contentType", contentType);
+                       info.AddValue ("method", method);
+                       info.AddValue ("statusDescription", statusDescription);
+                       info.AddValue ("cookieCollection", cookieCollection);
+                       info.AddValue ("version", version);
+                       info.AddValue ("statusCode", statusCode);
+               }
 
                // Cleaning up stuff
 
@@ -251,8 +315,11 @@ namespace System.Net
                        Dispose (true);
                        GC.SuppressFinalize (this);  
                }
-               
-               protected virtual void Dispose (bool disposing) 
+
+#if !NET_2_0
+               protected virtual
+#endif
+               void Dispose (bool disposing) 
                {
                        if (this.disposed)
                                return;
@@ -286,93 +353,96 @@ namespace System.Net
                        if (webHeaders == null)
                                return;
 
-                       string val = webHeaders ["Set-Cookie"];
-                       if (val != null && val.Trim () != "")
-                               SetCookie (val);
+                       string [] values = webHeaders.GetValues ("Set-Cookie");
+                       if (values != null) {
+                               foreach (string va in values)
+                                       SetCookie (va);
+                       }
 
-                       val = webHeaders ["Set-Cookie2"];
-                       if (val != null && val.Trim () != "")
-                               SetCookie2 (val);
-               }
-               
-               static string [] SplitValue (string input)
-               {
-                       string [] result = new string [2];
-                       int eq = input.IndexOf ('=');
-                       if (eq == -1) {
-                               result [0] = "invalid";
-                       } else {
-                               result [0] = input.Substring (0, eq).Trim ().ToUpper ();
-                               result [1] = input.Substring (eq + 1);
+                       values = webHeaders.GetValues ("Set-Cookie2");
+                       if (values != null) {
+                               foreach (string va in values)
+                                       SetCookie2 (va);
                        }
-                       
-                       return result;
                }
-               
-               [MonoTODO ("Parse dates")]
-               void SetCookie (string cookie_str)
+
+               void SetCookie (string header)
                {
-                       string[] parts = null;
-                       Collections.Queue options = null;
+                       string name, val;
                        Cookie cookie = null;
+                       CookieParser parser = new CookieParser (header);
 
-                       options = new Collections.Queue (cookie_str.Split (';'));
-                       parts = SplitValue ((string) options.Dequeue()); // NAME=VALUE must be first
+                       while (parser.GetNextNameValue (out name, out val)) {
+                               if ((name == null || name == "") && cookie == null)
+                                       continue;
 
-                       cookie = new Cookie (parts[0], parts[1]);
+                               if (cookie == null) {
+                                       cookie = new Cookie (name, val);
+                                       continue;
+                               }
 
-                       while (options.Count > 0) {
-                               parts = SplitValue ((string) options.Dequeue());
-                               switch (parts [0]) {
-                                       case "COMMENT":
-                                               if (cookie.Comment == null)
-                                                       cookie.Comment = parts[1];
+                               name = name.ToUpper ();
+                               switch (name) {
+                               case "COMMENT":
+                                       if (cookie.Comment == null)
+                                               cookie.Comment = val;
                                        break;
-                                       case "COMMENTURL":
-                                               if (cookie.CommentUri == null)
-                                                       cookie.CommentUri = new Uri(parts[1]);
+                               case "COMMENTURL":
+                                       if (cookie.CommentUri == null)
+                                               cookie.CommentUri = new Uri (val);
                                        break;
-                                       case "DISCARD":
-                                               cookie.Discard = true;
+                               case "DISCARD":
+                                       cookie.Discard = true;
                                        break;
-                                       case "DOMAIN":
-                                               if (cookie.Domain == "")
-                                                       cookie.Domain = parts[1];
+                               case "DOMAIN":
+                                       if (cookie.Domain == "")
+                                               cookie.Domain = val;
                                        break;
-                                       case "MAX-AGE": // RFC Style Set-Cookie2
-                                               if (cookie.Expires == DateTime.MinValue)
-                                                       cookie.Expires = cookie.TimeStamp.AddSeconds (Int32.Parse (parts[1]));
+#if NET_2_0
+                               case "HTTPONLY":
+                                       cookie.HttpOnly = true;
                                        break;
-                                       case "EXPIRES": // Netscape Style Set-Cookie
-                                               if (cookie.Expires == DateTime.MinValue) {
-                                                       //FIXME: Does DateTime parse something like: "Sun, 17-Jan-2038 19:14:07 GMT"?
-                                                       //cookie.Expires = DateTime.ParseExact (parts[1]);
-                                                       cookie.Expires = DateTime.Now.AddDays (1);
-                                               }
+#endif
+                               case "MAX-AGE": // RFC Style Set-Cookie2
+                                       if (cookie.Expires == DateTime.MinValue) {
+                                               try {
+                                               cookie.Expires = cookie.TimeStamp.AddSeconds (UInt32.Parse (val));
+                                               } catch {}
+                                       }
                                        break;
-                                       case "PATH":
-                                                       cookie.Path = parts[1];
+                               case "EXPIRES": // Netscape Style Set-Cookie
+                                       if (cookie.Expires != DateTime.MinValue)
+                                               break;
+
+                                       cookie.Expires = TryParseCookieExpires (val);
                                        break;
-                                       case "PORT":
-                                               if (cookie.Port == null)
-                                                       cookie.Port = parts[1];
+                               case "PATH":
+                                       cookie.Path = val;
                                        break;
-                                       case "SECURE":
-                                               cookie.Secure = true;
+                               case "PORT":
+                                       if (cookie.Port == null)
+                                               cookie.Port = val;
                                        break;
-                                       case "VERSION":
-                                               cookie.Version = Int32.Parse (parts[1]);
+                               case "SECURE":
+                                       cookie.Secure = true;
                                        break;
-                               } // switch
-                       } // while
+                               case "VERSION":
+                                       try {
+                                               cookie.Version = (int) UInt32.Parse (val);
+                                       } catch {}
+                                       break;
+                               }
+                       }
 
                        if (cookieCollection == null)
-                               cookieCollection = new CookieCollection();
+                               cookieCollection = new CookieCollection ();
 
                        if (cookie.Domain == "")
                                cookie.Domain = uri.Host;
 
                        cookieCollection.Add (cookie);
+                       if (cookie_container != null)
+                               cookie_container.Add (uri, cookie);
                }
 
                void SetCookie2 (string cookies_str)
@@ -382,6 +452,118 @@ namespace System.Net
                        foreach (string cookie_str in cookies)
                                SetCookie (cookie_str);
                }
+
+               string[] cookieExpiresFormats =
+                       new string[] { "r",
+                                       "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'",
+                                       "ddd, dd'-'MMM'-'yy HH':'mm':'ss 'GMT'" };
+
+               DateTime TryParseCookieExpires (string value)
+               {
+                       if (value == null || value.Length == 0)
+                               return DateTime.MinValue;
+
+                       for (int i = 0; i < cookieExpiresFormats.Length; i++)
+                       {
+                               try {
+                                       DateTime cookieExpiresUtc = DateTime.ParseExact (value, cookieExpiresFormats [i], CultureInfo.InvariantCulture);
+
+                                       //convert UTC/GMT time to local time
+#if NET_2_0
+                                       cookieExpiresUtc = DateTime.SpecifyKind (cookieExpiresUtc, DateTimeKind.Utc);
+                                       return TimeZone.CurrentTimeZone.ToLocalTime (cookieExpiresUtc);
+#else
+                                       //DateTime.Kind is only available on .NET 2.0, so do some calculation
+                                       TimeSpan localOffset = TimeZone.CurrentTimeZone.GetUtcOffset (cookieExpiresUtc.Date);
+                                       return cookieExpiresUtc.Add (localOffset);
+#endif
+                               } catch {}
+                       }
+
+                       //If we can't parse Expires, use cookie as session cookie (expires is DateTime.MinValue)
+                       return DateTime.MinValue;
+               }
        }       
+
+       class CookieParser {
+               string header;
+               int pos;
+               int length;
+
+               public CookieParser (string header) : this (header, 0)
+               {
+               }
+
+               public CookieParser (string header, int position)
+               {
+                       this.header = header;
+                       this.pos = position;
+                       this.length = header.Length;
+               }
+
+               public bool GetNextNameValue (out string name, out string val)
+               {
+                       name = null;
+                       val = null;
+
+                       if (pos >= length)
+                               return false;
+
+                       name = GetCookieName ();
+                       if (pos < header.Length && header [pos] == '=') {
+                               pos++;
+                               val = GetCookieValue ();
+                       }
+
+                       if (pos < length && header [pos] == ';')
+                               pos++;
+
+                       return true;
+               }
+
+               string GetCookieName ()
+               {
+                       int k = pos;
+                       while (k < length && Char.IsWhiteSpace (header [k]))
+                               k++;
+
+                       int begin = k;
+                       while (k < length && header [k] != ';' &&  header [k] != '=')
+                               k++;
+
+                       pos = k;
+                       return header.Substring (begin, k - begin).Trim ();
+               }
+
+               string GetCookieValue ()
+               {
+                       if (pos >= length)
+                               return null;
+
+                       int k = pos;
+                       while (k < length && Char.IsWhiteSpace (header [k]))
+                               k++;
+
+                       int begin;
+                       if (header [k] == '"'){
+                               int j;
+                               begin = ++k;
+
+                               while (k < length && header [k] != '"')
+                                       k++;
+
+                               for (j = k; j < length && header [j] != ';'; j++)
+                                       ;
+                               pos = j;
+                       } else {
+                               begin = k;
+                               while (k < length && header [k] != ';')
+                                       k++;
+                               pos = k;
+                       }
+                               
+                       return header.Substring (begin, k - begin).Trim ();
+               }
+       }
 }