New tests.
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Channels / HttpRequestChannel.cs
index 199f33d41bfc88399a332d750c7809dc1b7405c4..4bae6026237252bad1bf95b9c5ccf720bea05541 100644 (file)
@@ -40,41 +40,22 @@ namespace System.ServiceModel.Channels
        internal class HttpRequestChannel : RequestChannelBase
        {
                HttpChannelFactory<IRequestChannel> source;
-               EndpointAddress address;
-               Uri via;
 
                WebRequest web_request;
 
-               // FIXME: supply maxSizeOfHeaders.
-               int max_headers = 0x10000;
-
                // Constructor
 
                public HttpRequestChannel (HttpChannelFactory<IRequestChannel> factory,
                        EndpointAddress address, Uri via)
-                       : base (factory)
+                       : base (factory, address, via)
                {
                        this.source = factory;
-                       this.address = address;
-                       this.via = via;
-               }
-
-               public int MaxSizeOfHeaders {
-                       get { return max_headers; }
                }
 
                public MessageEncoder Encoder {
                        get { return source.MessageEncoder; }
                }
 
-               public override EndpointAddress RemoteAddress {
-                       get { return address; }
-               }
-
-               public override Uri Via {
-                       get { return via; }
-               }
-
                // Request
 
                public override Message Request (Message message, TimeSpan timeout)
@@ -87,22 +68,66 @@ namespace System.ServiceModel.Channels
                        Message message = result.Message;
                        TimeSpan timeout = result.Timeout;
                        // FIXME: is distination really like this?
-                       Uri destination = message.Headers.To ?? Via ?? RemoteAddress.Uri;
+                       Uri destination = message.Headers.To;
+                       if (destination == null) {
+                               if (source.Transport.ManualAddressing)
+                                       throw new InvalidOperationException ("When manual addressing is enabled on the transport, every request messages must be set its destination address.");
+                                else
+                                       destination = Via ?? RemoteAddress.Uri;
+                       }
 
                        web_request = HttpWebRequest.Create (destination);
                        web_request.Method = "POST";
                        web_request.ContentType = Encoder.ContentType;
 
+#if NET_2_1
+                       var cmgr = source.GetProperty<IHttpCookieContainerManager> ();
+                       if (cmgr != null)
+                               ((HttpWebRequest) web_request).CookieContainer = cmgr.CookieContainer;
+#endif
+
+#if !MOONLIGHT // until we support NetworkCredential like SL4 will do.
+                       // client authentication (while SL3 has NetworkCredential class, it is not implemented yet. So, it is non-SL only.)
+                       var httpbe = (HttpTransportBindingElement) source.Transport;
+                       string authType = null;
+                       switch (httpbe.AuthenticationScheme) {
+                       // AuthenticationSchemes.Anonymous is the default, ignored.
+                       case AuthenticationSchemes.Basic:
+                               authType = "Basic";
+                               break;
+                       case AuthenticationSchemes.Digest:
+                               authType = "Digest";
+                               break;
+                       case AuthenticationSchemes.Ntlm:
+                               authType = "Ntlm";
+                               break;
+                       case AuthenticationSchemes.Negotiate:
+                               authType = "Negotiate";
+                               break;
+                       }
+                       if (authType != null) {
+                               var cred = source.ClientCredentials;
+                               string user = cred != null ? cred.UserName.UserName : null;
+                               string pwd = cred != null ? cred.UserName.Password : null;
+                               if (String.IsNullOrEmpty (user))
+                                       throw new InvalidOperationException (String.Format ("Use ClientCredentials to specify a user name for required HTTP {0} authentication.", authType));
+                               var nc = new NetworkCredential (user, pwd);
+                               web_request.Credentials = nc;
+                               // FIXME: it is said required in SL4, but it blocks full WCF.
+                               //web_request.UseDefaultCredentials = false;
+                       }
+#endif
+
 #if !NET_2_1 // FIXME: implement this to not depend on Timeout property
                        web_request.Timeout = (int) timeout.TotalMilliseconds;
 #endif
 
                        // There is no SOAP Action/To header when AddressingVersion is None.
-                       if (message.Version.Addressing == AddressingVersion.None) {
+                       if (message.Version.Envelope.Equals (EnvelopeVersion.Soap11) ||
+                           message.Version.Addressing.Equals (AddressingVersion.None)) {
                                if (message.Headers.Action != null) {
-                                       web_request.Headers ["SOAPAction"] = message.Headers.Action;
+                                       web_request.Headers ["SOAPAction"] = String.Concat ("\"", message.Headers.Action, "\"");
                                        message.Headers.RemoveAll ("Action", message.Version.Addressing.Namespace);
-                                       if (message.Headers.Action != null) throw new Exception (message.Headers.Action);
                                }
                        }
 
@@ -112,6 +137,7 @@ namespace System.ServiceModel.Channels
                        string pname = HttpRequestMessageProperty.Name;
                        if (message.Properties.ContainsKey (pname)) {
                                HttpRequestMessageProperty hp = (HttpRequestMessageProperty) message.Properties [pname];
+                               web_request.Headers.Clear ();
                                web_request.Headers.Add (hp.Headers);
                                web_request.Method = hp.Method;
                                // FIXME: do we have to handle hp.QueryString ?
@@ -120,6 +146,13 @@ namespace System.ServiceModel.Channels
                        }
 #endif
 
+/*
+// FIXME: this causes invalid message security.
+var mb = message.CreateBufferedCopy (0x10000);
+message = mb.CreateMessage ();
+Console.WriteLine (mb.CreateMessage ());
+*/
+
                        if (!suppressEntityBody && String.Compare (web_request.Method, "GET", StringComparison.OrdinalIgnoreCase) != 0) {
                                MemoryStream buffer = new MemoryStream ();
                                Encoder.WriteMessage (message, buffer);
@@ -158,22 +191,24 @@ namespace System.ServiceModel.Channels
                                resstr = res.GetResponseStream ();
                        } catch (WebException we) {
                                res = we.Response;
-#if NET_2_1 // debug
-                               Console.WriteLine (we);
-#endif
+                               if (res == null) {
+                                       channelResult.Complete (we);
+                                       return;
+                               }
                                try {
                                        // The response might contain SOAP fault. It might not.
                                        resstr = res.GetResponseStream ();
                                } catch (WebException we2) {
-#if NET_2_1 // debug
-                                       Console.WriteLine (we2);
-#endif
-
                                        channelResult.Complete (we2);
                                        return;
                                }
                        }
-                       
+
+                       var hrr = (HttpWebResponse) res;
+                       if ((int) hrr.StatusCode >= 400 && (int) hrr.StatusCode < 500) {
+                               channelResult.Complete (new WebException (String.Format ("There was an error on processing web request: Status code {0}({1}): {2}", (int) hrr.StatusCode, hrr.StatusCode, hrr.StatusDescription)));
+                       }
+
                        try {
                                using (var responseStream = resstr) {
                                        MemoryStream ms = new MemoryStream ();
@@ -188,9 +223,12 @@ namespace System.ServiceModel.Channels
                                        }
                                        ms.Seek (0, SeekOrigin.Begin);
 
-                                       channelResult.Response = Encoder.ReadMessage (
-                                               //responseStream, MaxSizeOfHeaders);
-                                               ms, MaxSizeOfHeaders, res.ContentType);
+                                       Message ret = Encoder.ReadMessage (
+                                               ms, (int) source.Transport.MaxReceivedMessageSize, res.ContentType);
+                                       var rp = new HttpResponseMessageProperty () { StatusCode = hrr.StatusCode, StatusDescription = hrr.StatusDescription };
+                                       foreach (var key in hrr.Headers.AllKeys)
+                                               rp.Headers [key] = hrr.Headers [key];
+                                       ret.Properties.Add (HttpResponseMessageProperty.Name, rp);
 /*
 MessageBuffer buf = ret.CreateBufferedCopy (0x10000);
 ret = buf.CreateMessage ();
@@ -199,6 +237,7 @@ w.Formatting = System.Xml.Formatting.Indented;
 buf.CreateMessage ().WriteMessage (w);
 w.Close ();
 */
+                                       channelResult.Response = ret;
                                        channelResult.Complete ();
                                }
                        } catch (Exception ex) {
@@ -232,7 +271,9 @@ w.Close ();
 
                protected override void OnAbort ()
                {
-                       throw new NotImplementedException ();
+                       if (web_request != null)
+                               web_request.Abort ();
+                       web_request = null;
                }
 
                // Close
@@ -283,16 +324,15 @@ w.Close ();
                        AsyncCallback callback;
                        ManualResetEvent wait;
                        Exception error;
+                       object locker = new object ();
+                       bool is_completed;
 
                        public HttpChannelRequestAsyncResult (Message message, TimeSpan timeout, AsyncCallback callback, object state)
                        {
-                               CompletedSynchronously = true;
                                Message = message;
                                Timeout = timeout;
                                this.callback = callback;
                                AsyncState = state;
-
-                               wait = new ManualResetEvent (false);
                        }
 
                        public Message Response {
@@ -300,7 +340,13 @@ w.Close ();
                        }
 
                        public WaitHandle AsyncWaitHandle {
-                               get { return wait; }
+                               get {
+                                       lock (locker) {
+                                               if (wait == null)
+                                                       wait = new ManualResetEvent (is_completed);
+                                       }
+                                       return wait;
+                               }
                        }
 
                        public object AsyncState {
@@ -321,7 +367,6 @@ w.Close ();
                                error = error ?? ex;
 
                                IsCompleted = true;
-                               wait.Set ();
                                if (callback != null)
                                        callback (this);
                        }
@@ -331,7 +376,14 @@ w.Close ();
                        }
 
                        public bool IsCompleted {
-                               get; private set;
+                               get { return is_completed; }
+                               set {
+                                       is_completed = value;
+                                       lock (locker) {
+                                               if (is_completed && wait != null)
+                                                       wait.Set ();
+                                       }
+                               }
                        }
 
                        public void WaitEnd ()
@@ -340,8 +392,14 @@ w.Close ();
                                        // FIXME: Do we need to use the timeout? If so, what happens when the timeout is reached.
                                        // Is the current request cancelled and an exception thrown? If so we need to pass the
                                        // exception to the Complete () method and allow the result to complete 'normally'.
-                                       //wait.WaitOne (Timeout, true);
-                                       wait.WaitOne ();
+#if NET_2_1
+                                       // neither Moonlight nor MonoTouch supports contexts (WaitOne default to false)
+                                       bool result = AsyncWaitHandle.WaitOne (Timeout);
+#else
+                                       bool result = AsyncWaitHandle.WaitOne (Timeout, true);
+#endif
+                                       if (!result)
+                                               throw new TimeoutException ();
                                }
                                if (error != null)
                                        throw error;