Merge pull request #4033 from ntherning/no-stdcall-for-icalls-on-windows-32-bit
[mono.git] / mcs / class / System.ServiceModel.Web / System.ServiceModel.Description / WebHttpBehavior.cs
1 //
2 // WebHttpBehavior.cs
3 //
4 // Author:
5 //      Atsushi Enomoto  <atsushi@ximian.com>
6 //
7 // Copyright (C) 2008 Novell, Inc (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 using System;
29 using System.Net;
30 using System.ServiceModel;
31 using System.ServiceModel.Channels;
32 using System.ServiceModel.Dispatcher;
33 using System.ServiceModel.Web;
34
35 namespace System.ServiceModel.Description
36 {
37
38         internal static class WebHttpBehaviorExtensions
39         {
40                 public static WebAttributeInfo GetWebAttributeInfo (this OperationDescription od)
41                 {
42                         foreach (IOperationBehavior ob in od.Behaviors) {
43                                 var wg = ob as WebGetAttribute;
44                                 if (wg != null)
45                                         return wg.Info;
46                                 var wi = ob as WebInvokeAttribute;
47                                 if (wi != null)
48                                         return wi.Info;
49                         }
50                         return new WebGetAttribute ().Info; // blank one
51                 }
52         }
53
54         public class WebHttpBehavior : IEndpointBehavior
55         {
56                 public WebHttpBehavior ()
57                 {
58                         DefaultBodyStyle = WebMessageBodyStyle.Bare;
59                         DefaultOutgoingRequestFormat = WebMessageFormat.Xml;
60                         DefaultOutgoingResponseFormat = WebMessageFormat.Xml;
61                 }
62
63                 public virtual bool AutomaticFormatSelectionEnabled { get; set; }
64
65                 public virtual bool FaultExceptionEnabled { get; set; }
66
67                 public virtual bool HelpEnabled { get; set; }
68
69                 public virtual WebMessageBodyStyle DefaultBodyStyle { get; set; }
70
71                 public virtual WebMessageFormat DefaultOutgoingRequestFormat { get; set; }
72
73                 public virtual WebMessageFormat DefaultOutgoingResponseFormat { get; set; }
74
75                 public virtual void AddBindingParameters (ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
76                 {
77                         // nothing
78                 }
79
80                 [MonoTODO]
81                 protected virtual void AddClientErrorInspector (ServiceEndpoint endpoint, ClientRuntime clientRuntime)
82                 {
83                         // clientRuntime.MessageInspectors.Add (something);
84                 }
85
86 #if !MOBILE && !XAMMAC_4_5
87                 protected virtual void AddServerErrorHandlers (ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
88                 {
89                         endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add (new WebHttpErrorHandler ());
90                 }
91 #endif
92
93                 public virtual void ApplyClientBehavior (ServiceEndpoint endpoint, ClientRuntime clientRuntime)
94                 {
95                         AddClientErrorInspector (endpoint, clientRuntime);
96                         foreach (ClientOperation oper in clientRuntime.Operations) {
97                                 var req = GetRequestClientFormatter (endpoint.Contract.Operations.Find (oper.Name), endpoint);
98                                 var res = GetReplyClientFormatter (endpoint.Contract.Operations.Find (oper.Name), endpoint);
99                                 oper.Formatter = new ClientPairFormatter (req, res);
100                         }
101                 }
102
103                 public virtual void ApplyDispatchBehavior (ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
104                 {
105 #if MOBILE || XAMMAC_4_5
106                         throw new NotImplementedException ();
107 #else
108                         endpointDispatcher.DispatchRuntime.OperationSelector = GetOperationSelector (endpoint);
109                         // FIXME: get HostNameComparisonMode from WebHttpBinding by some means.
110                         endpointDispatcher.FilterPriority = 1; // It is to take higher priority than that of ServiceMetadataExtension (whose URL likely conflicts with this one).
111                         endpointDispatcher.AddressFilter = new PrefixEndpointAddressMessageFilter (endpoint.Address);
112                         endpointDispatcher.ContractFilter = new MatchAllMessageFilter ();
113                         AddServerErrorHandlers (endpoint, endpointDispatcher);
114
115                         foreach (DispatchOperation oper in endpointDispatcher.DispatchRuntime.Operations) {
116                                 var req = GetRequestDispatchFormatter (endpoint.Contract.Operations.Find (oper.Name), endpoint);
117                                 var res = GetReplyDispatchFormatter (endpoint.Contract.Operations.Find (oper.Name), endpoint);
118                                 oper.Formatter = new DispatchPairFormatter (req, res);
119                         }
120                         endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation = new DispatchOperation (endpointDispatcher.DispatchRuntime, "*", "*", "*") {
121                                 Invoker = new EndpointNotFoundOperationInvoker (),
122                                 DeserializeRequest = false,
123                                 SerializeReply = false};
124 #endif
125                 }
126
127                 internal class ClientPairFormatter : IClientMessageFormatter
128                 {
129                         public ClientPairFormatter (IClientMessageFormatter request, IClientMessageFormatter reply)
130                         {
131                                 this.request = request;
132                                 this.reply = reply;
133                         }
134
135                         IClientMessageFormatter request, reply;
136
137                         public Message SerializeRequest (MessageVersion messageVersion, object [] parameters)
138                         {
139                                 return request.SerializeRequest (messageVersion, parameters);
140                         }
141
142                         public object DeserializeReply (Message message, object [] parameters)
143                         {
144                                 return reply.DeserializeReply (message, parameters);
145                         }
146                 }
147
148 #if !MOBILE && !XAMMAC_4_5
149                 internal class DispatchPairFormatter : IDispatchMessageFormatter
150                 {
151                         public DispatchPairFormatter (IDispatchMessageFormatter request, IDispatchMessageFormatter reply)
152                         {
153                                 this.request = request;
154                                 this.reply = reply;
155                         }
156
157                         IDispatchMessageFormatter request;
158                         IDispatchMessageFormatter reply;
159
160                         public void DeserializeRequest (Message message, object [] parameters)
161                         {
162                                 request.DeserializeRequest (message, parameters);
163                         }
164
165                         public Message SerializeReply (MessageVersion messageVersion, object [] parameters, object result)
166                         {
167                                 return reply.SerializeReply (messageVersion, parameters, result);
168                         }
169                 }
170
171                 protected virtual WebHttpDispatchOperationSelector GetOperationSelector (ServiceEndpoint endpoint)
172                 {
173                         return new WebHttpDispatchOperationSelector (endpoint);
174                 }
175 #endif
176
177                 protected virtual QueryStringConverter GetQueryStringConverter (OperationDescription operationDescription)
178                 {
179                         return new QueryStringConverter ();
180                 }
181
182                 protected virtual IClientMessageFormatter GetReplyClientFormatter (OperationDescription operationDescription, ServiceEndpoint endpoint)
183                 {
184                         return new WebMessageFormatter.ReplyClientFormatter (operationDescription, endpoint, GetQueryStringConverter (operationDescription), this);
185                 }
186
187 #if !MOBILE
188                 protected virtual IDispatchMessageFormatter GetReplyDispatchFormatter (OperationDescription operationDescription, ServiceEndpoint endpoint)
189                 {
190                         return new WebMessageFormatter.ReplyDispatchFormatter (operationDescription, endpoint, GetQueryStringConverter (operationDescription), this);
191                 }
192 #endif
193
194                 protected virtual IClientMessageFormatter GetRequestClientFormatter (OperationDescription operationDescription, ServiceEndpoint endpoint)
195                 {
196                         return new WebMessageFormatter.RequestClientFormatter (operationDescription, endpoint, GetQueryStringConverter (operationDescription), this);
197                 }
198
199 #if !MOBILE
200                 protected virtual IDispatchMessageFormatter GetRequestDispatchFormatter (OperationDescription operationDescription, ServiceEndpoint endpoint)
201                 {
202                         return new WebMessageFormatter.RequestDispatchFormatter (operationDescription, endpoint, GetQueryStringConverter (operationDescription), this);
203                 }
204 #endif
205
206                 WebMessageBodyStyle GetBodyStyle (WebAttributeInfo wai)
207                 {
208                         return wai != null && wai.IsBodyStyleSetExplicitly ? wai.BodyStyle : DefaultBodyStyle;
209                 }
210
211                 protected void ValidateOperation (OperationDescription operation)
212                 {
213                         var wai = operation.GetWebAttributeInfo ();
214                         if (wai.Method == "GET")
215                                 return;
216                         var style = GetBodyStyle (wai);
217
218                         // if the style is wrapped there won't be problems
219                         if (style == WebMessageBodyStyle.Wrapped)
220                                 return;
221
222                         string [] parameters;
223                         if (wai.UriTemplate != null) {
224                                 // find all variables in the URI
225                                 var uri = new UriTemplate (wai.UriTemplate);
226                                 parameters = new string [uri.PathSegmentVariableNames.Count + uri.QueryValueVariableNames.Count];
227                                 uri.PathSegmentVariableNames.CopyTo (parameters, 0);
228                                 uri.QueryValueVariableNames.CopyTo (parameters, uri.PathSegmentVariableNames.Count);
229
230                                 // sort because Array.BinarySearch is the easiest way for case-insensitive search
231                                 Array.Sort (parameters, StringComparer.InvariantCultureIgnoreCase);
232                         } else
233                                 parameters = new string [0];
234
235                         bool hasBody = false;
236
237                         foreach (var msg in operation.Messages) {
238                                 if (msg.Direction == MessageDirection.Input) {
239                                         // the message is for a request
240                                         // if requests are wrapped there is nothing to check
241                                         if (style == WebMessageBodyStyle.WrappedRequest)
242                                                 continue;
243
244                                         foreach (var part in msg.Body.Parts) {
245                                                 if (Array.BinarySearch (parameters, part.Name, StringComparer.InvariantCultureIgnoreCase) < 0) {
246                                                         // this part of the message is not covered by a variable in the URI
247                                                         // so it must be passed in the body
248                                                         if (hasBody)
249                                                                 throw new InvalidOperationException (String.Format ("Operation '{0}' has multiple message body parts. Add parameters to the UriTemplate or change the BodyStyle to 'Wrapped' or 'WrappedRequest' on the WebInvoke/WebGet attribute.", operation.Name));
250                                                         hasBody = true;
251                                                 }
252                                         }
253                                 } else {
254                                         // the message is for a response
255                                         if (style != WebMessageBodyStyle.WrappedResponse && msg.Body.Parts.Count > 0)
256                                                 throw new InvalidOperationException (String.Format ("Operation '{0}' has output parameters. BodyStyle must be 'Wrapped' or 'WrappedResponse' on the operation WebInvoke/WebGet attribute.", operation.Name));
257                                 }
258                         }
259                 }
260                 
261                 [MonoTODO ("check UriTemplate validity")]
262                 public virtual void Validate (ServiceEndpoint endpoint)
263                 {
264                         if (endpoint == null)
265                                 throw new ArgumentNullException ("endpoint");
266
267                         foreach (var oper in endpoint.Contract.Operations) {
268                                 ValidateOperation (oper);
269                         }
270
271                         ValidateBinding (endpoint);
272                 }
273
274                 protected virtual void ValidateBinding (ServiceEndpoint endpoint)
275                 {
276                         switch (endpoint.Binding.Scheme) {
277                         case "http":
278                         case "https":
279                                 break;
280                         default:
281                                 throw new InvalidOperationException ("Only http and https are allowed for WebHttpBehavior");
282                         }
283                         if (!endpoint.Binding.MessageVersion.Equals (MessageVersion.None))
284                                 throw new InvalidOperationException ("Only MessageVersion.None is allowed for WebHttpBehavior");
285                         if (!endpoint.Binding.CreateBindingElements ().Find<TransportBindingElement> ().ManualAddressing)
286                                 throw new InvalidOperationException ("ManualAddressing in the transport binding element in the binding must be true for WebHttpBehavior");
287                 }
288
289 #if !MOBILE
290                 internal class WebHttpErrorHandler : IErrorHandler
291                 {
292                         public void ProvideFault (Exception error, MessageVersion version, ref Message fault)
293                         {
294                                 if (!(error is EndpointNotFoundException))
295                                         return;
296                                 fault = Message.CreateMessage (version, null);
297                                 var prop = new HttpResponseMessageProperty ();
298                                 prop.StatusCode = HttpStatusCode.NotFound;
299                                 fault.Properties.Add (HttpResponseMessageProperty.Name, prop);
300                         }
301                         
302                         public bool HandleError (Exception error)
303                         {
304                                 return false;
305                         }
306                 }
307
308                 class EndpointNotFoundOperationInvoker : IOperationInvoker
309                 {
310                         public bool IsSynchronous {
311                                 get { return true; }
312                         }
313
314                         public object [] AllocateInputs ()
315                         {
316                                 return new object [1];
317                         }
318                         
319                         public object Invoke (object instance, object [] inputs, out object [] outputs)
320                         {
321                                 throw new EndpointNotFoundException ();
322                         }
323                         
324                         public IAsyncResult InvokeBegin (object instance, object [] inputs, AsyncCallback callback, object state)
325                         {
326                                 throw new EndpointNotFoundException ();
327                         }
328
329                         public object InvokeEnd (object instance, out object [] outputs, IAsyncResult result)
330                         {
331                                 throw new InvalidOperationException ();
332                         }
333                 }
334 #endif
335         }
336 }