瀏覽代碼

WebHeaderCollection fixes

Marek Safar 14 年之前
父節點
當前提交
ae11667cff

+ 4 - 2
mcs/class/System/System.Net/HttpWebRequest.cs

@@ -66,7 +66,7 @@ namespace System.Net
 		bool haveResponse;		
 		bool haveRequest;
 		bool requestSent;
-		WebHeaderCollection webHeaders = new WebHeaderCollection (true);
+		WebHeaderCollection webHeaders;
 		bool keepAlive = true;
 		int maxAutoRedirect = 50;
 		string mediaType = String.Empty;
@@ -139,6 +139,7 @@ namespace System.Net
 			this.requestUri = uri;
 			this.actualUri = uri;
 			this.proxy = GlobalProxySelection.Select;
+			this.webHeaders = new WebHeaderCollection (WebHeaderCollection.HeaderInfo.Request);
 		}		
 		
 		[Obsolete ("Serialization is obsoleted for this type", false)]
@@ -363,7 +364,7 @@ namespace System.Net
 			get { return webHeaders; }
 			set {
 				CheckRequestStarted ();
-				WebHeaderCollection newHeaders = new WebHeaderCollection (true);
+				WebHeaderCollection newHeaders = new WebHeaderCollection (WebHeaderCollection.HeaderInfo.Request);
 				int count = value.Count;
 				for (int i = 0; i < count; i++) 
 					newHeaders.Add (value.GetKey (i), value.Get (i));
@@ -371,6 +372,7 @@ namespace System.Net
 				webHeaders = newHeaders;
 			}
 		}
+		
 #if NET_4_0
 		public
 #else

+ 233 - 240
mcs/class/System/System.Net/WebHeaderCollection.cs

@@ -5,10 +5,11 @@
 // 	Lawrence Pit ([email protected])
 //	Gonzalo Paniagua Javier ([email protected])
 //      Miguel de Icaza ([email protected])
+//	Marek Safar ([email protected])
 //
 // Copyright 2003 Ximian, Inc. (http://www.ximian.com)
 // Copyright 2007 Novell, Inc. (http://www.novell.com)
-//
+// Copyright (C) 2011 Xamarin Inc (http://www.xamarin.com)
 //
 //
 // Permission is hereby granted, free of charge, to any person obtaining
@@ -50,77 +51,79 @@ namespace System.Net
 	[ComVisible(true)]
 	public class WebHeaderCollection : NameValueCollection, ISerializable {
 #endif
-		private static readonly Hashtable restricted;
-		private static readonly Hashtable multiValue;
-		static readonly Dictionary<string, bool> restricted_response;
-		private bool internallyCreated = false;
-		
-		// Static Initializer
+		[Flags]
+		internal enum HeaderInfo
+		{
+			Request = 1,
+			Response = 1 << 1,
+			MultiValue = 1 << 10
+		}
+
+		static readonly bool[] allowed_chars = {
+			false, false, false, false, false, false, false, false, false, false, false, false, false, false,
+			false, false, false, false, false, false, false, false, false, false, false, false, false, false,
+			false, false, false, false, false, true, false, true, true, true, true, false, false, false, true,
+			true, false, true, true, false, true, true, true, true, true, true, true, true, true, true, false,
+			false, false, false, false, false, false, true, true, true, true, true, true, true, true, true,
+			true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+			false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true,
+			true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+			false, true, false
+		};
+
+		static readonly Dictionary<string, HeaderInfo> headers;
+		HeaderInfo? headerRestriction;
+		HeaderInfo? headerConsistency;
 		
 		static WebHeaderCollection () 
 		{
-			// the list of restricted header names as defined 
-			// by the ms.net spec
-			restricted = new Hashtable (CaseInsensitiveHashCodeProvider.DefaultInvariant,
-						    CaseInsensitiveComparer.DefaultInvariant);
-
-			restricted.Add ("accept", true);
-			restricted.Add ("connection", true);
-			restricted.Add ("content-length", true);
-			restricted.Add ("content-type", true);
-			restricted.Add ("date", true);
-			restricted.Add ("expect", true);
-			restricted.Add ("host", true);
-			restricted.Add ("if-modified-since", true);
-			restricted.Add ("range", true);
-			restricted.Add ("referer", true);
-			restricted.Add ("transfer-encoding", true);
-			restricted.Add ("user-agent", true);			
-			restricted.Add ("proxy-connection", true);			
-
-			//
-			restricted_response = new Dictionary<string, bool> (StringComparer.InvariantCultureIgnoreCase);
-			restricted_response.Add ("Content-Length", true);
-			restricted_response.Add ("Transfer-Encoding", true);
-			restricted_response.Add ("WWW-Authenticate", true);
-
-			// see par 14 of RFC 2068 to see which header names
-			// accept multiple values each separated by a comma
-			multiValue = new Hashtable (CaseInsensitiveHashCodeProvider.DefaultInvariant,
-						    CaseInsensitiveComparer.DefaultInvariant);
-
-			multiValue.Add ("accept", true);
-			multiValue.Add ("accept-charset", true);
-			multiValue.Add ("accept-encoding", true);
-			multiValue.Add ("accept-language", true);
-			multiValue.Add ("accept-ranges", true);
-			multiValue.Add ("allow", true);
-			multiValue.Add ("authorization", true);
-			multiValue.Add ("cache-control", true);
-			multiValue.Add ("connection", true);
-			multiValue.Add ("content-encoding", true);
-			multiValue.Add ("content-language", true);			
-			multiValue.Add ("expect", true);		
-			multiValue.Add ("if-match", true);
-			multiValue.Add ("if-none-match", true);
-			multiValue.Add ("proxy-authenticate", true);
-			multiValue.Add ("public", true);			
-			multiValue.Add ("range", true);
-			multiValue.Add ("transfer-encoding", true);
-			multiValue.Add ("upgrade", true);
-			multiValue.Add ("vary", true);
-			multiValue.Add ("via", true);
-			multiValue.Add ("warning", true);
-			multiValue.Add ("www-authenticate", true);
-
-			// Extra
-			multiValue.Add ("set-cookie", true);
-			multiValue.Add ("set-cookie2", true);
+			headers = new Dictionary<string, HeaderInfo> (StringComparer.OrdinalIgnoreCase) {
+				{ "Allow", HeaderInfo.MultiValue },
+				{ "Accept", HeaderInfo.Request | HeaderInfo.MultiValue },
+				{ "Accept-Charset", HeaderInfo.MultiValue },
+				{ "Accept-Encoding", HeaderInfo.MultiValue },
+				{ "Accept-Language", HeaderInfo.MultiValue },
+				{ "Accept-Ranges", HeaderInfo.MultiValue },
+				{ "Authorization", HeaderInfo.MultiValue },
+				{ "Cache-Control", HeaderInfo.MultiValue },
+				{ "Cookie", HeaderInfo.MultiValue },
+				{ "Connection", HeaderInfo.Request | HeaderInfo.MultiValue },
+				{ "Content-Encoding", HeaderInfo.MultiValue },
+				{ "Content-Length", HeaderInfo.Request | HeaderInfo.Response },
+				{ "Content-Type", HeaderInfo.Request },
+				{ "Content-Language", HeaderInfo.MultiValue },
+				{ "Date", HeaderInfo.Request },
+				{ "Expect", HeaderInfo.Request | HeaderInfo.MultiValue},
+				{ "Host", HeaderInfo.Request },
+				{ "If-Match", HeaderInfo.MultiValue },
+				{ "If-Modified-Since", HeaderInfo.Request },
+				{ "If-None-Match", HeaderInfo.MultiValue },
+				{ "Keep-Alive", HeaderInfo.Response },
+				{ "Pragma", HeaderInfo.MultiValue },
+				{ "Proxy-Authenticate", HeaderInfo.MultiValue },
+				{ "Proxy-Authorization", HeaderInfo.MultiValue },
+				{ "Proxy-Connection", HeaderInfo.Request | HeaderInfo.MultiValue },
+				{ "Range", HeaderInfo.Request | HeaderInfo.MultiValue },
+				{ "Referer", HeaderInfo.Request },
+				{ "Set-Cookie", HeaderInfo.MultiValue },
+				{ "Set-Cookie2", HeaderInfo.MultiValue },
+				{ "TE", HeaderInfo.MultiValue },
+				{ "Trailer", HeaderInfo.MultiValue },
+				{ "Transfer-Encoding", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo.MultiValue },
+				{ "Upgrade", HeaderInfo.MultiValue },
+				{ "User-Agent", HeaderInfo.Request },
+				{ "Vary", HeaderInfo.MultiValue },
+				{ "Via", HeaderInfo.MultiValue },
+				{ "Warning", HeaderInfo.MultiValue },
+				{ "WWW-Authenticate", HeaderInfo.Response | HeaderInfo. MultiValue }
+			};
 		}
 		
 		// Constructors
 		
-		public WebHeaderCollection () {	}	
+		public WebHeaderCollection ()
+		{
+		}
 		
 		protected WebHeaderCollection (SerializationInfo serializationInfo, 
 					       StreamingContext streamingContext)
@@ -140,10 +143,10 @@ namespace System.Net
 			}
 			
 		}
-		
-		internal WebHeaderCollection (bool internallyCreated)
-		{	
-			this.internallyCreated = internallyCreated;
+
+		internal WebHeaderCollection (HeaderInfo headerRestriction)
+		{
+			this.headerRestriction = headerRestriction;
 		}		
 		
 		// Methods
@@ -154,17 +157,17 @@ namespace System.Net
 				throw new ArgumentNullException ("header");
 			int pos = header.IndexOf (':');
 			if (pos == -1)
-				throw new ArgumentException ("no colon found", "header");				
-			this.Add (header.Substring (0, pos), 
-				  header.Substring (pos + 1));
+				throw new ArgumentException ("no colon found", "header");
+
+			this.Add (header.Substring (0, pos), header.Substring (pos + 1));
 		}
 		
 		public override void Add (string name, string value)
 		{
 			if (name == null)
 				throw new ArgumentNullException ("name");
-			if (internallyCreated && IsRestricted (name))
-				throw new ArgumentException ("This header must be modified with the appropiate property.");
+
+			CheckRestrictedHeader (name);
 			this.AddWithoutValidate (name, value);
 		}
 
@@ -190,11 +193,35 @@ namespace System.Net
 			if (values == null || values.Length == 0)
 				return null;
 
-			/*
 			if (IsMultiValue (header)) {
-				values = GetMultipleValues (values);
+				List<string> separated = null;
+				foreach (var value in values) {
+					if (value.IndexOf (',') < 0)
+						continue;
+
+					if (separated == null) {
+						separated = new List<string> (values.Length + 1);
+						foreach (var v in values) {
+							if (v == value)
+								break;
+
+							separated.Add (v);
+						}
+					}
+
+					var slices = value.Split (',');
+					var slices_length = slices.Length;
+					if (value[value.Length - 1] == ',')
+						--slices_length;
+
+					for (int i = 0; i < slices_length; ++i ) {
+						separated.Add (slices[i].Trim ());
+					}
+				}
+
+				if (separated != null)
+					return separated.ToArray ();
 			}
-			*/
 
 			return values;
 		}
@@ -202,77 +229,36 @@ namespace System.Net
 		public override string[] GetValues (int index)
 		{
 			string[] values = base.GetValues (index);
+
 			if (values == null || values.Length == 0) {
-				return(null);
+				return null;
 			}
 			
-			return(values);
+			return values;
 		}
 
-		/* Now i wonder why this is here...
-		static string [] GetMultipleValues (string [] values)
+		public static bool IsRestricted (string headerName)
 		{
-			ArrayList mvalues = new ArrayList (values.Length);
-			StringBuilder sb = null;
-			for (int i = 0; i < values.Length; ++i) {
-				string val = values [i];
-				if (val.IndexOf (',') == -1) {
-					mvalues.Add (val);
-					continue;
-				}
-
-				if (sb == null)
-					sb = new StringBuilder ();
-
-				bool quote = false;
-				for (int k = 0; k < val.Length; k++) {
-					char c = val [k];
-					if (c == '"') {
-						quote = !quote;
-					} else if (!quote && c == ',') {
-						mvalues.Add (sb.ToString ().Trim ());
-						sb.Length = 0;
-						continue;
-					}
-					sb.Append (c);
-				}
-
-				if (sb.Length > 0) {
-					mvalues.Add (sb.ToString ().Trim ());
-					sb.Length = 0;
-				}
-			}
-
-			return (string []) mvalues.ToArray (typeof (string));
+			return IsRestricted (headerName, false);
 		}
-		*/
 
-		public static bool IsRestricted (string headerName)
+		public static bool IsRestricted (string headerName, bool response)
 		{
 			if (headerName == null)
 				throw new ArgumentNullException ("headerName");
 
-			if (headerName == "") // MS throw nullexception here!
+			if (headerName.Length == 0)
 				throw new ArgumentException ("empty string", "headerName");
 
 			if (!IsHeaderName (headerName))
 				throw new ArgumentException ("Invalid character in header");
 
-			return restricted.ContainsKey (headerName);
-		}
-
-		public static bool IsRestricted (string headerName, bool response)
-		{
-			if (String.IsNullOrEmpty (headerName))
-				throw new ArgumentNullException ("headerName");
-
-			if (!IsHeaderName (headerName))
-				throw new ArgumentException ("Invalid character in header");
-
+			HeaderInfo info;
+			if (!headers.TryGetValue (headerName, out info))
+				return false;
 
-			if (response)
-				return restricted_response.ContainsKey (headerName);
-			return restricted.ContainsKey (headerName);
+			var flag = response ? HeaderInfo.Response : HeaderInfo.Request;
+			return (info & flag) != 0;
 		}
 
 		public override void OnDeserialization (object sender)
@@ -283,8 +269,8 @@ namespace System.Net
 		{
 			if (name == null)
 				throw new ArgumentNullException ("name");
-			if (internallyCreated && IsRestricted (name))
-				throw new ArgumentException ("restricted header");
+
+			CheckRestrictedHeader (name);
 			base.Remove (name);
 		}
 
@@ -292,8 +278,6 @@ namespace System.Net
 		{
 			if (name == null)
 				throw new ArgumentNullException ("name");
-			if (internallyCreated && IsRestricted (name))
-				throw new ArgumentException ("restricted header");
 			if (!IsHeaderName (name))
 				throw new ArgumentException ("invalid header name");
 			if (value == null)
@@ -302,6 +286,8 @@ namespace System.Net
 				value = value.Trim ();
 			if (!IsHeaderValue (value))
 				throw new ArgumentException ("invalid header value");
+
+			CheckRestrictedHeader (name);
 			base.Set (name, value);			
 		}
 
@@ -364,40 +350,37 @@ namespace System.Net
 			}
 		}
 
-		public override string[] AllKeys
-		{
+		public override string[] AllKeys {
 			get {
-				return(base.AllKeys);
+				return base.AllKeys;
 			}
 		}
 		
-		public override int Count 
-		{
+		public override int Count {
 			get {
-				return(base.Count);
+				return base.Count;
 			}
 		}
 
-		public override KeysCollection Keys
-		{
+		public override KeysCollection Keys {
 			get {
-				return(base.Keys);
+				return base.Keys;
 			}
 		}
 
 		public override string Get (int index)
 		{
-			return(base.Get (index));
+			return base.Get (index);
 		}
 		
 		public override string Get (string name)
 		{
-			return(base.Get (name));
+			return base.Get (name);
 		}
 		
 		public override string GetKey (int index)
 		{
-			return(base.GetKey (index));
+			return base.GetKey (index);
 		}
 
 		public void Add (HttpRequestHeader header, string value)
@@ -430,9 +413,90 @@ namespace System.Net
 			Set (ResponseHeaderToString (header), value);
 		}
 
-		static string RequestHeaderToString (HttpRequestHeader value)
+		public string this [HttpRequestHeader header] {
+			get {
+				return Get (RequestHeaderToString (header));
+			}
+			
+			set {
+				Set (header, value);
+			}
+		}
+
+		public string this [HttpResponseHeader header] {
+			get {
+				return Get (ResponseHeaderToString (header));
+			}
+
+			set {
+				Set (header, value);
+			}
+		}
+
+		public override void Clear ()
 		{
-			switch (value){
+			base.Clear ();
+		}
+
+		public override IEnumerator GetEnumerator ()
+		{
+			return base.GetEnumerator ();
+		}
+
+		// Internal Methods
+		
+		// With this we don't check for invalid characters in header. See bug #55994.
+		internal void SetInternal (string header)
+		{
+			int pos = header.IndexOf (':');
+			if (pos == -1)
+				throw new ArgumentException ("no colon found", "header");				
+
+			SetInternal (header.Substring (0, pos), header.Substring (pos + 1));
+		}
+
+		internal void SetInternal (string name, string value)
+		{
+			if (value == null)
+				value = String.Empty;
+			else
+				value = value.Trim ();
+			if (!IsHeaderValue (value))
+				throw new ArgumentException ("invalid header value");
+
+			if (IsMultiValue (name)) {
+				base.Add (name, value);
+			} else {
+				base.Remove (name);
+				base.Set (name, value);	
+			}
+		}
+
+		internal void RemoveAndAdd (string name, string value)
+		{
+			if (value == null)
+				value = String.Empty;
+			else
+				value = value.Trim ();
+
+			base.Remove (name);
+			base.Set (name, value);
+		}
+
+		internal void RemoveInternal (string name)
+		{
+			if (name == null)
+				throw new ArgumentNullException ("name");
+			base.Remove (name);
+		}		
+		
+		// Private Methods
+
+		string RequestHeaderToString (HttpRequestHeader value)
+		{
+			CheckHeaderConsistency (HeaderInfo.Request);
+
+			switch (value) {
 			case HttpRequestHeader.CacheControl:
 				return "Cache-Control";
 			case HttpRequestHeader.Connection:
@@ -520,21 +584,11 @@ namespace System.Net
 			}
 		}
 
-
-		public string this[HttpRequestHeader header]
-		{
-			get {
-				return Get (RequestHeaderToString (header));
-			}
-			
-			set {
-				Add (RequestHeaderToString (header), value);
-			}
-		}
-
 		string ResponseHeaderToString (HttpResponseHeader value)
 		{
-			switch (value){
+			CheckHeaderConsistency (HeaderInfo.Response);
+
+			switch (value) {
 			case HttpResponseHeader.CacheControl:
 				return "Cache-Control";
 			case HttpResponseHeader.Connection:
@@ -599,85 +653,38 @@ namespace System.Net
 				throw new InvalidOperationException ();
 			}
 		}
-		public string this[HttpResponseHeader header]
-		{
-			get
-			{
-				return Get (ResponseHeaderToString (header));
-			}
 
-			set
-			{
-				Add (ResponseHeaderToString (header), value);
-			}
-		}
-
-		public override void Clear ()
+		void CheckRestrictedHeader (string headerName)
 		{
-			base.Clear ();
-		}
-
+			if (!headerRestriction.HasValue)
+				return;
 
-		public override IEnumerator GetEnumerator ()
-		{
-			return(base.GetEnumerator ());
-		}
-
-		// Internal Methods
-		
-		// With this we don't check for invalid characters in header. See bug #55994.
-		internal void SetInternal (string header)
-		{
-			int pos = header.IndexOf (':');
-			if (pos == -1)
-				throw new ArgumentException ("no colon found", "header");				
+			HeaderInfo info;
+			if (!headers.TryGetValue (headerName, out info))
+				return;
 
-			SetInternal (header.Substring (0, pos), header.Substring (pos + 1));
+			if ((info & headerRestriction.Value) != 0)
+				throw new ArgumentException ("This header must be modified with the appropiate property.");
 		}
 
-		internal void SetInternal (string name, string value)
+		void CheckHeaderConsistency (HeaderInfo value)
 		{
-			if (value == null)
-				value = String.Empty;
-			else
-				value = value.Trim ();
-			if (!IsHeaderValue (value))
-				throw new ArgumentException ("invalid header value");
-
-			if (IsMultiValue (name)) {
-				base.Add (name, value);
-			} else {
-				base.Remove (name);
-				base.Set (name, value);	
+			if (!headerConsistency.HasValue) {
+				headerConsistency = value;
+				return;
 			}
-		}
 
-		internal void RemoveAndAdd (string name, string value)
-		{
-			if (value == null)
-				value = String.Empty;
-			else
-				value = value.Trim ();
-
-			base.Remove (name);
-			base.Set (name, value);
+			if ((headerConsistency & value) == 0)
+				throw new InvalidOperationException ();
 		}
-
-		internal void RemoveInternal (string name)
-		{
-			if (name == null)
-				throw new ArgumentNullException ("name");
-			base.Remove (name);
-		}		
-		
-		// Private Methods
 		
 		internal static bool IsMultiValue (string headerName)
 		{
-			if (headerName == null || headerName == "")
+			if (headerName == null)
 				return false;
 
-			return multiValue.ContainsKey (headerName);
+			HeaderInfo info;
+			return headers.TryGetValue (headerName, out info) && (info & HeaderInfo.MultiValue) != 0;
 		}		
 		
 		internal static bool IsHeaderValue (string value)
@@ -712,25 +719,11 @@ namespace System.Net
 			int len = name.Length;
 			for (int i = 0; i < len; i++) {			
 				char c = name [i];
-				if (c > 126 || !allowed_chars [(int) c])
+				if (c > 126 || !allowed_chars [c])
 					return false;
 			}
 			
 			return true;
 		}
-
-		static bool [] allowed_chars = new bool [126] {
-			false, false, false, false, false, false, false, false, false, false, false, false, false, false,
-			false, false, false, false, false, false, false, false, false, false, false, false, false, false,
-			false, false, false, false, false, true, false, true, true, true, true, false, false, false, true,
-			true, false, true, true, false, true, true, true, true, true, true, true, true, true, true, false,
-			false, false, false, false, false, false, true, true, true, true, true, true, true, true, true,
-			true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
-			false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true,
-			true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
-			false, true, false
-			};
 	}
 }
-
-

+ 88 - 63
mcs/class/System/Test/System.Net/WebHeaderCollectionTest.cs

@@ -6,6 +6,7 @@
 //   Martin Willemoes Hansen ([email protected])
 //   Gert Driesen ([email protected])
 //   Gonzalo Paniagua Javier ([email protected])
+//   Marek Safar  <[email protected]>
 //
 // (C) 2003 Martin Willemoes Hansen
 //
@@ -80,7 +81,11 @@ namespace MonoTests.System.Net
 				col.Add ("XHost: foo" + (char) 0x7f);
 				Assert.Fail ("#10");
 			} catch (ArgumentException) {
-
+			}
+			try {
+				col.Add (":value");
+				Assert.Fail ("#100");
+			} catch (ArgumentException) {
 			}
 
 			try {
@@ -93,20 +98,49 @@ namespace MonoTests.System.Net
 			} catch (ArgumentException) {
 				Assert.Fail ("#12");
 			}
+		}
+
+		[Test]
+		public void AddRequestHeader ()
+		{
+			col.Add (HttpRequestHeader.Host, "hh");
+
+			try {
+				col.Add (HttpResponseHeader.Age, "aa");
+				Assert.Fail ("#1");
+			} catch (InvalidOperationException) {
+			}
+		}
+
+		[Test]
+		public void AddRestrictedDisabled ()
+		{
+			col.Add ("Accept", "aa");
+			col.Add ("Content-Length", "bb");
+			col.Add ("Keep-Alive", "cc");
+			col.Add ("If-Modified-Since", "dd");
+			col.Add ("aaany", null);
+		}
+
+		[Test]
+		public void AddRestricted ()
+		{
+			col = CreateRestrictedHeaders ();
+
+			try {
+				col.Add ("Accept", "cc");
+				Assert.Fail ("#1");
+			} catch (ArgumentException) {
+			}
 
-			// restricted
-			/*
-			// this can only be tested in namespace System.Net
 			try {
-				WebHeaderCollection col2 = new WebHeaderCollection (true);
-				col2.Add ("Host: foo");
-				Assert.Fail ("#13: should fail according to spec");
-			} catch (ArgumentException) {}		
-			*/
+				col.Add (HttpRequestHeader.Host, "dd");
+				Assert.Fail ("#2");
+			} catch (ArgumentException) {
+			}
 		}
 
 		[Test]
-		[Category ("NotWorking")]
 		public void GetValues ()
 		{
 			WebHeaderCollection w = new WebHeaderCollection ();
@@ -116,15 +150,16 @@ namespace MonoTests.System.Net
 
 			string [] sa = w.GetValues ("Hello");
 			Assert.AreEqual (3, sa.Length, "#1");
-			Assert.AreEqual ("H1, H2,H3,H4", w.Get ("Hello"), "#2");
+			Assert.AreEqual ("H1,H2,H3,H4", w.Get ("Hello"), "#2");
 
 			w = new WebHeaderCollection ();
 			w.Add ("Accept", "H1");
 			w.Add ("Accept", "H2");
-			w.Add ("Accept", "H3,H4");
+			w.Add ("Accept", "H3,  H4  ");
 			Assert.AreEqual (3, w.GetValues (0).Length, "#3a");
 			Assert.AreEqual (4, w.GetValues ("Accept").Length, "#3b");
-			Assert.AreEqual ("H1, H2,H3,H4", w.Get ("Accept"), "#4");
+			Assert.AreEqual ("H4", w.GetValues ("Accept")[3], "#3c");
+			Assert.AreEqual ("H1,H2,H3,  H4", w.Get ("Accept"), "#4");
 
 			w = new WebHeaderCollection ();
 			w.Add ("Allow", "H1");
@@ -132,7 +167,7 @@ namespace MonoTests.System.Net
 			w.Add ("Allow", "H3,H4");
 			sa = w.GetValues ("Allow");
 			Assert.AreEqual (4, sa.Length, "#5");
-			Assert.AreEqual ("H1, H2,H3,H4", w.Get ("Allow"), "#6");
+			Assert.AreEqual ("H1,H2,H3,H4", w.Get ("Allow"), "#6");
 
 			w = new WebHeaderCollection ();
 			w.Add ("AUTHorization", "H1, H2, H3");
@@ -157,19 +192,29 @@ namespace MonoTests.System.Net
 			} catch (ArgumentNullException) { }
 			Assert.AreEqual (null, w.GetValues (""), "#14");
 			Assert.AreEqual (null, w.GetValues ("NotExistent"), "#15");
+
+			w = new WebHeaderCollection ();
+			w.Add ("Accept", null);
+			Assert.AreEqual (1, w.GetValues ("Accept").Length, "#16");
+
+			w = new WebHeaderCollection ();
+			w.Add ("Accept", ",,,");
+			Assert.AreEqual (3, w.GetValues ("Accept").Length, "#17");
 		}
 
 		[Test]
 		public void Indexers ()
 		{
 			Assert.AreEqual ("Value1", ((NameValueCollection)col)[0], "#1.1");
-			//FIXME: test also HttpRequestHeader and HttpResponseHeader
-			//FIXME: is this enough?
 			WebHeaderCollection w = new WebHeaderCollection ();
 			w [HttpRequestHeader.CacheControl] = "Value2";
-			Assertion.AssertEquals ("#1.2", "Value2", w [HttpRequestHeader.CacheControl]);
-			w [HttpResponseHeader.Pragma] = "Value3";
-			Assertion.AssertEquals ("#1.3", "Value3", w [HttpResponseHeader.Pragma]);
+			Assert.AreEqual ("Value2", w[HttpRequestHeader.CacheControl], "#1.2");
+
+			try {
+				w[HttpResponseHeader.Pragma] = "Value3";
+				Assert.Fail ("#1.3");
+			} catch (InvalidOperationException) {
+			}
 		}
 
 		[Test]
@@ -178,16 +223,18 @@ namespace MonoTests.System.Net
 			col.Remove ("Name1");
 			col.Remove ("NameNotExist");
 			Assert.AreEqual (1, col.Count, "#1");
+		}
+
+		[Test]
+		public void RemoveRestricted ()
+		{
+			col = CreateRestrictedHeaders ();
 
-			/*
-			// this can only be tested in namespace System.Net
 			try {
-				WebHeaderCollection col2 = new WebHeaderCollection (true);
-				col2.Add ("Host", "foo");
-				col2.Remove ("Host");
+				col.Add ("Host", "foo");
+				col.Remove ("Host");
 				Assert.Fail ("#2: should fail according to spec");
 			} catch (ArgumentException) {}
-			*/
 		}
 
 		[Test]
@@ -221,11 +268,11 @@ namespace MonoTests.System.Net
 			w = new WebHeaderCollection ();
 			w.Add (HttpResponseHeader.KeepAlive, "Value1");
 			w.Add (HttpResponseHeader.WwwAuthenticate, "Value2");
-			Assertion.AssertEquals ("#2", "Keep-Alive: Value1\r\nWWW-Authenticate: Value2\r\n\r\n", w.ToString ());
+			Assert.AreEqual ("Keep-Alive: Value1\r\nWWW-Authenticate: Value2\r\n\r\n", w.ToString (), "#2");
 			w = new WebHeaderCollection ();
 			w.Add (HttpRequestHeader.UserAgent, "Value1");
 			w.Add (HttpRequestHeader.ContentMd5, "Value2");
-			Assertion.AssertEquals ("#2", "User-Agent: Value1\r\nContent-MD5: Value2\r\n\r\n", w.ToString ());
+			Assert.AreEqual ("User-Agent: Value1\r\nContent-MD5: Value2\r\n\r\n", w.ToString (), "#3");
 		}
 
 		[Test]
@@ -333,7 +380,6 @@ namespace MonoTests.System.Net
 		}
 
 		private static readonly byte [] _serialized = new byte [] {
-#if NET_2_0
 			0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00,
 			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x02, 0x00, 0x00, 0x00,
 			0x49, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2c, 0x20, 0x56, 0x65,
@@ -362,32 +408,6 @@ namespace MonoTests.System.Net
 			0x0b, 0x44, 0x69, 0x73, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f,
 			0x6e, 0x06, 0x08, 0x00, 0x00, 0x00, 0x06, 0x61, 0x74, 0x74, 0x61,
 			0x63, 0x68, 0x0b
-#else
-			0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00,
-			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x02, 0x00, 0x00, 0x00,
-			0x4c, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2c, 0x20, 0x56, 0x65,
-			0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x31, 0x2e, 0x30, 0x2e, 0x35,
-			0x30, 0x30, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x43, 0x75, 0x6c, 0x74,
-			0x75, 0x72, 0x65, 0x3d, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x61, 0x6c,
-			0x2c, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79,
-			0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x3d, 0x62, 0x37, 0x37, 0x61, 0x35,
-			0x63, 0x35, 0x36, 0x31, 0x39, 0x33, 0x34, 0x65, 0x30, 0x38, 0x39,
-			0x05, 0x01, 0x00, 0x00, 0x00, 0x1e, 0x53, 0x79, 0x73, 0x74, 0x65,
-			0x6d, 0x2e, 0x4e, 0x65, 0x74, 0x2e, 0x57, 0x65, 0x62, 0x48, 0x65,
-			0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74,
-			0x69, 0x6f, 0x6e, 0x07, 0x00, 0x00, 0x00, 0x05, 0x43, 0x6f, 0x75,
-			0x6e, 0x74, 0x01, 0x30, 0x01, 0x33, 0x01, 0x31, 0x01, 0x34, 0x01,
-			0x32, 0x01, 0x35, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x08,
-			0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00,
-			0x00, 0x00, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d,
-			0x54, 0x79, 0x70, 0x65, 0x06, 0x04, 0x00, 0x00, 0x00, 0x09, 0x69,
-			0x6d, 0x61, 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x06, 0x05, 0x00,
-			0x00, 0x00, 0x08, 0x4e, 0x6f, 0x2d, 0x43, 0x61, 0x63, 0x68, 0x65,
-			0x06, 0x06, 0x00, 0x00, 0x00, 0x03, 0x6f, 0x66, 0x66, 0x06, 0x07,
-			0x00, 0x00, 0x00, 0x0b, 0x44, 0x69, 0x73, 0x70, 0x6f, 0x73, 0x69,
-			0x74, 0x69, 0x6f, 0x6e, 0x06, 0x08, 0x00, 0x00, 0x00, 0x06, 0x61,
-			0x74, 0x74, 0x61, 0x63, 0x68, 0x0b
-#endif
 		};
 
 		[Test]
@@ -440,7 +460,7 @@ namespace MonoTests.System.Net
 				}
 			}
 		}
-#if NET_2_0
+
 		[Test]
 		public void IsRestricted_InvalidChars_Request_2 ()
 		{
@@ -547,27 +567,27 @@ namespace MonoTests.System.Net
 			"Accept", "Accept-Charset", "Accept-Encoding", "Accept-Language", "Accept-Ranges", "Authorization", 
 			"Cache-Control", "Connection", "Cookie", "Content-Length", "Content-Type", "Date", 
 			"Expect", "From", "Host", "If-Match", "If-Modified-Since", "If-None-Match", 
-			"If-Range", "If-Unmodified-Since", "Max-Forwards", "Pragma", "Proxy-Authorization", 
-			"Range", "Referer", "TE", "Upgrade", "User-Agent", "Via", "Warn" };
+			"If-Range", "If-Unmodified-Since", "Max-Forwards", "Pragma", "Proxy-Authorization", "Proxy-Connection",
+			"Range", "Referer", "TE", "Transfer-Encoding", "Upgrade", "User-Agent", "Via", "Warn" };
 
 		static string [] response_headers = new string [] {
 			"Accept-Ranges", "Age", "Allow", "Cache-Control", "Content-Encoding", "Content-Language", 
 			"Content-Length", "Content-Location", "Content-Disposition", "Content-MD5", "Content-Range", 
-			"Content-Type", "Date", "ETag", "Expires", "Last-Modified", "Location", "Pragma", 
+			"Content-Type", "Date", "ETag", "Expires", "Keep-Alive", "Last-Modified", "Location", "Pragma", 
 			"Proxy-Authenticate", "Retry-After", "Server", "Set-Cookie", "Trailer", 
 			"Transfer-Encoding", "Vary", "Via", "Warn", "WWW-Authenticate" };
 
 		static string [] restricted_request_request = new string [] {
 			"Accept", "Connection", "Content-Length", "Content-Type", "Date",
-			"Expect", "Host", "If-Modified-Since", "Range", "Referer",
-			"User-Agent" };
+			"Expect", "Host", "If-Modified-Since", "Proxy-Connection", "Range", "Referer",
+			"Transfer-Encoding", "User-Agent" };
 		static string [] restricted_response_request = new string [] {
 			"Content-Length", "Content-Type", "Date", "Transfer-Encoding" };
 
 		static string [] restricted_request_response = new string [] {
-			 "Content-Length" };
+			 "Content-Length", "Transfer-Encoding" };
 		static string [] restricted_response_response = new string [] {
-			 "Content-Length", "Transfer-Encoding", "WWW-Authenticate" };
+			 "Content-Length", "Keep-Alive", "Transfer-Encoding", "WWW-Authenticate" };
 
 		[Test]
 		public void IsRestricted_2_0_RequestRequest ()
@@ -628,7 +648,12 @@ namespace MonoTests.System.Net
 			}
 			Assert.IsTrue (count == restricted_response_response.Length, "length");
 		}
-#endif
+
+		static WebHeaderCollection CreateRestrictedHeaders ()
+		{
+			var factory = Activator.CreateInstance (typeof (IWebRequestCreate).Assembly.GetType ("System.Net.HttpRequestCreator"), true) as IWebRequestCreate;
+			return factory.Create (new Uri ("http://localhost")).Headers;
+		}
 	}
 }