8913c7090b59a2e53ce257eec69ac0e865e3d02d
[mono.git] / mcs / class / referencesource / System.ServiceModel.Web / System / ServiceModel / Description / WebScriptEnablingBehavior.cs
1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4 #pragma warning disable 1634, 1691
5 namespace System.ServiceModel.Description
6 {
7     using System;
8     using System.Diagnostics;
9     using System.Globalization;
10     using System.Net;
11     using System.Runtime.Serialization;
12     using System.Runtime.Serialization.Json;
13     using System.Security;
14     using System.ServiceModel;
15     using System.ServiceModel.Activation;
16     using System.ServiceModel.Channels;
17     using System.ServiceModel.Dispatcher;
18     using System.ServiceModel.Web;
19     using System.Xml;
20
21     public sealed class WebScriptEnablingBehavior : WebHttpBehavior
22     {
23         static readonly DataContractJsonSerializer jsonFaultSerializer = new DataContractJsonSerializer(typeof(JsonFaultDetail));
24         static readonly WebMessageBodyStyle webScriptBodyStyle = WebMessageBodyStyle.WrappedRequest;
25         static readonly WebMessageFormat webScriptDefaultMessageFormat = WebMessageFormat.Json;
26         const int MaxMetadataEndpointBufferSize = 2048;
27         WebMessageFormat requestMessageFormat = webScriptDefaultMessageFormat;
28         WebMessageFormat responseMessageFormat = webScriptDefaultMessageFormat;
29
30         public WebScriptEnablingBehavior()
31         {
32         }
33
34         public override WebMessageBodyStyle DefaultBodyStyle
35         {
36             get
37             {
38                 return webScriptBodyStyle;
39             }
40             set
41             {
42                 if (value != webScriptBodyStyle)
43                 {
44                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR2.GetString(SR2.BodyStyleNotSupportedByWebScript, value, this.GetType().Name, webScriptBodyStyle)));
45                 }
46             }
47         }
48
49         public override WebMessageFormat DefaultOutgoingRequestFormat
50         {
51             get
52             {
53                 return this.requestMessageFormat;
54             }
55             set
56             {
57                 if (!WebMessageFormatHelper.IsDefined(value))
58                 {
59                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value"));
60                 }
61                 this.requestMessageFormat = value;
62             }
63         }
64
65         public override WebMessageFormat DefaultOutgoingResponseFormat
66         {
67             get
68             {
69                 return this.responseMessageFormat;
70             }
71             set
72             {
73                 if (!WebMessageFormatHelper.IsDefined(value))
74                 {
75                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value"));
76                 }
77                 this.responseMessageFormat = value;
78             }
79         }
80
81         public override bool HelpEnabled
82         {
83             get
84             {
85                 return false;
86             }
87             set
88             {
89                 if (value)
90                 {
91                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR2.GetString(SR2.HelpPageNotSupportedInScripts)));
92                 }
93             }
94         }
95
96         public override bool AutomaticFormatSelectionEnabled
97         {
98             get
99             {
100                 return false;
101             }
102             set
103             {
104                 if (value)
105                 {
106                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR2.GetString(SR2.AutomaticFormatSelectionNotSupportedInScripts)));
107                 }
108             }
109         }
110
111         public override bool FaultExceptionEnabled
112         {
113             get
114             {
115                 return false;
116             }
117             set
118             {
119                 if (value)
120                 {
121                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR2.GetString(SR2.FaultExceptionEnabledNotSupportedInScripts)));
122                 }
123             }
124         }
125
126         public override void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
127         {
128             base.ApplyClientBehavior(endpoint, clientRuntime);
129 #pragma warning disable 56506 // Microsoft, clientRuntime.MessageInspectors is never null
130             clientRuntime.MessageInspectors.Add(new JsonClientMessageInspector());
131 #pragma warning restore 56506
132         }
133
134         public override void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
135         {
136             base.ApplyDispatchBehavior(endpoint, endpointDispatcher);
137
138             try
139             {
140                 AddMetadataEndpoint(endpoint, endpointDispatcher, false); //  debugMode 
141                 AddMetadataEndpoint(endpoint, endpointDispatcher, true); //  debugMode 
142             }
143             catch (XmlException exception)
144             {
145                 // Microsoft, need to reference this resource string although fix for 13332 was removed
146                 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR2.GetString(SR2.InvalidXmlCharactersInNameUsedWithPOSTMethod, string.Empty, string.Empty, string.Empty), exception));
147             }
148         }
149
150         public override void Validate(ServiceEndpoint endpoint)
151         {
152             base.Validate(endpoint);
153
154 #pragma warning disable 56506 // Microsoft, endpoint.Contract is never null
155             foreach (OperationDescription operation in endpoint.Contract.Operations)
156 #pragma warning restore 56506
157             {
158                 if (operation.Behaviors.Find<XmlSerializerOperationBehavior>() != null)
159                 {
160                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
161                         SR2.GetString(SR2.WebScriptNotSupportedForXmlSerializerFormat, typeof(XmlSerializerFormatAttribute).Name, this.GetType().ToString())));
162                 }
163                 string method = WebHttpBehavior.GetWebMethod(operation);
164                 if (method != WebHttpBehavior.GET
165                     && method != WebHttpBehavior.POST)
166                 {
167                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
168                         SR2.GetString(SR2.WebScriptInvalidHttpRequestMethod, operation.Name,
169                         endpoint.Contract.Name, method, this.GetType().ToString())));
170                 }
171                 WebGetAttribute webGetAttribute = operation.Behaviors.Find<WebGetAttribute>();
172                 if (webGetAttribute != null && webGetAttribute.UriTemplate != null)
173                 {
174                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
175                         SR2.GetString(SR2.WebScriptNotSupportedForXmlSerializerFormat, typeof(UriTemplate).Name, this.GetType().ToString())));
176                 }
177                 WebInvokeAttribute webInvokeAttribute = operation.Behaviors.Find<WebInvokeAttribute>();
178                 if (webInvokeAttribute != null && webInvokeAttribute.UriTemplate != null)
179                 {
180                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
181                         SR2.GetString(SR2.WebScriptNotSupportedForXmlSerializerFormat, typeof(UriTemplate).Name, this.GetType().ToString())));
182                 }
183                 WebMessageBodyStyle bodyStyle = GetBodyStyle(operation);
184                 if (bodyStyle != webScriptBodyStyle)
185                 {
186                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR2.GetString(SR2.BodyStyleNotSupportedByWebScript, bodyStyle, this.GetType().Name, webScriptBodyStyle)));
187                 }
188
189                 foreach (MessageDescription messageDescription in operation.Messages)
190                 {
191                     if (!messageDescription.IsTypedMessage &&
192                         (messageDescription.Direction == MessageDirection.Output) &&
193                         (messageDescription.Body.Parts.Count > 0))
194                     {
195                         throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
196                             SR2.GetString(SR2.WebScriptOutRefOperationsNotSupported, operation.Name,
197                             endpoint.Contract.Name)));
198                     }
199                 }
200             }
201         }
202
203         internal override DataContractJsonSerializerOperationFormatter CreateDataContractJsonSerializerOperationFormatter(OperationDescription od, DataContractSerializerOperationBehavior dcsob, bool isWrapped)
204         {
205             return new DataContractJsonSerializerOperationFormatter(od, dcsob.MaxItemsInObjectGraph, dcsob.IgnoreExtensionDataObject, dcsob.DataContractSurrogate, isWrapped, true, this.JavascriptCallbackParameterName);
206         }
207
208         internal override string GetWmiTypeName()
209         {
210             return "WebScriptEnablingBehavior";
211         }
212
213         internal override bool UseBareReplyFormatter(WebMessageBodyStyle style, OperationDescription operationDescription, WebMessageFormat responseFormat, out Type parameterType)
214         {
215             if (responseFormat == WebMessageFormat.Json)
216             {
217                 parameterType = null;
218                 return false;
219             }
220             return base.UseBareReplyFormatter(style, operationDescription, responseFormat, out parameterType);
221         }
222
223         protected override void AddClientErrorInspector(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
224         {
225             clientRuntime.MessageInspectors.Add(new JsonClientMessageInspector());
226         }
227
228         protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
229         {
230             if (endpointDispatcher.ChannelDispatcher == null)
231             {
232                 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(
233                     "endpointDispatcher", SR2.GetString(SR2.ChannelDispatcherMustBePresent));
234             }
235 #pragma warning disable 56506 // Microsoft, endpointDispatcher.ChannelDispatcher.ErrorHandlers never null
236             endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new JsonErrorHandler(endpoint, endpointDispatcher.ChannelDispatcher.IncludeExceptionDetailInFaults));
237 #pragma warning restore 56506
238         }
239
240         protected override QueryStringConverter GetQueryStringConverter(OperationDescription operationDescription)
241         {
242             return new JsonQueryStringConverter(operationDescription);
243         }
244
245         void AddMetadataEndpoint(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher, bool debugMode)
246         {
247             Uri baseAddress = endpoint.Address.Uri;
248             if (baseAddress == null)
249             {
250                 return;
251             }
252
253             ServiceHostBase host = endpointDispatcher.ChannelDispatcher.Host;
254
255             UriBuilder builder = new UriBuilder(baseAddress);
256             builder.Path += builder.Path.EndsWith("/", StringComparison.OrdinalIgnoreCase)
257                 ? (WebScriptClientGenerator.GetMetadataEndpointSuffix(debugMode))
258                 : ("/" + WebScriptClientGenerator.GetMetadataEndpointSuffix(debugMode));
259             EndpointAddress metadataAddress = new EndpointAddress(builder.Uri);
260
261             foreach (ServiceEndpoint serviceEndpoint in host.Description.Endpoints)
262             {
263                 if (EndpointAddress.UriEquals(serviceEndpoint.Address.Uri, metadataAddress.Uri, true, false))//  ignoreCase //  includeHostNameInComparison 
264                 {
265                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(
266                         new InvalidOperationException(SR2.GetString(SR2.JsonNoEndpointAtMetadataAddress, this.GetType().ToString(), serviceEndpoint.Address, serviceEndpoint.Name, host.Description.Name)));
267                 }
268             }
269
270             HttpTransportBindingElement transportBindingElement;
271             HttpTransportBindingElement existingTransportBindingElement = endpoint.Binding.CreateBindingElements().Find<HttpTransportBindingElement>();
272
273             if (existingTransportBindingElement != null)
274             {
275                 transportBindingElement = (HttpTransportBindingElement)existingTransportBindingElement.Clone();
276             }
277             else
278             {
279                 if (baseAddress.Scheme == "https")
280                 {
281                     transportBindingElement = new HttpsTransportBindingElement();
282                 }
283                 else
284                 {
285                     transportBindingElement = new HttpTransportBindingElement();
286                 }
287             }
288
289             transportBindingElement.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
290             transportBindingElement.TransferMode = TransferMode.Buffered;
291             transportBindingElement.MaxBufferSize = MaxMetadataEndpointBufferSize;
292             transportBindingElement.MaxReceivedMessageSize = MaxMetadataEndpointBufferSize;
293             Binding metadataBinding = new CustomBinding(
294                 new WebScriptMetadataMessageEncodingBindingElement(),
295                 transportBindingElement);
296             BindingParameterCollection parameters = host.GetBindingParameters(endpoint);
297
298             // build endpoint dispatcher
299             ContractDescription metadataContract = ContractDescription.GetContract(typeof(ServiceMetadataExtension.IHttpGetMetadata));
300             OperationDescription metadataOperation = metadataContract.Operations[0];
301             EndpointDispatcher metadataEndpointDispatcher = new EndpointDispatcher(metadataAddress, metadataContract.Name, metadataContract.Namespace);
302             DispatchOperation dispatchOperation = new DispatchOperation(metadataEndpointDispatcher.DispatchRuntime, metadataOperation.Name, metadataOperation.Messages[0].Action, metadataOperation.Messages[1].Action);
303             dispatchOperation.Formatter = new WebScriptMetadataFormatter();
304             dispatchOperation.Invoker = new SyncMethodInvoker(metadataOperation.SyncMethod);
305             metadataEndpointDispatcher.DispatchRuntime.Operations.Add(dispatchOperation);
306             metadataEndpointDispatcher.DispatchRuntime.SingletonInstanceContext = new InstanceContext(host, new WebScriptClientGenerator(endpoint, debugMode, !String.IsNullOrEmpty(this.JavascriptCallbackParameterName)));
307             metadataEndpointDispatcher.DispatchRuntime.InstanceContextProvider = new SingletonInstanceContextProvider(metadataEndpointDispatcher.DispatchRuntime);
308
309             // build channel dispatcher
310             IChannelListener<IReplyChannel> listener = null;
311             if (metadataBinding.CanBuildChannelListener<IReplyChannel>(parameters))
312             {
313                 listener = metadataBinding.BuildChannelListener<IReplyChannel>(metadataAddress.Uri, parameters);
314             }
315             ChannelDispatcher metadataChannelDispatcher = new ChannelDispatcher(listener);
316             metadataChannelDispatcher.MessageVersion = MessageVersion.None;
317             metadataChannelDispatcher.Endpoints.Add(metadataEndpointDispatcher);
318
319             host.ChannelDispatchers.Add(metadataChannelDispatcher);
320         }
321
322         class JsonClientMessageInspector : WebFaultClientMessageInspector
323         {
324             public override void AfterReceiveReply(ref Message reply, object correlationState)
325             {
326                 bool callBase = true;
327                 if (reply != null)
328                 {
329                     object responseProperty = reply.Properties[HttpResponseMessageProperty.Name];
330                     if (responseProperty != null)
331                     {
332                         if (((HttpResponseMessageProperty)responseProperty).Headers[JsonGlobals.jsonerrorString] == JsonGlobals.trueString)
333                         {
334                             callBase = false;
335                             XmlDictionaryReader reader = reply.GetReaderAtBodyContents();
336                             JsonFaultDetail faultDetail = jsonFaultSerializer.ReadObject(reader) as JsonFaultDetail;
337                             FaultCode faultCode = new FaultCode(FaultCodeConstants.Codes.InternalServiceFault, FaultCodeConstants.Namespaces.NetDispatch);
338                             faultCode = FaultCode.CreateReceiverFaultCode(faultCode);
339                             if (faultDetail != null)
340                             {
341                                 if (faultDetail.ExceptionDetail != null)
342                                 {
343                                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(
344                                         new FaultException<ExceptionDetail>(faultDetail.ExceptionDetail, faultDetail.Message, faultCode));
345                                 }
346                                 else
347                                 {
348                                     throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(
349                                         new FaultException(MessageFault.CreateFault(faultCode, faultDetail.Message)));
350                                 }
351                             }
352                             else
353                             {
354                                 throw System.ServiceModel.DiagnosticUtility.ExceptionUtility.ThrowHelperError(
355                                     new FaultException(MessageFault.CreateFault(faultCode,
356                                     System.ServiceModel.SR.GetString(System.ServiceModel.SR.SFxInternalServerError))));
357                             }
358                         }
359                     }
360                 }
361                 if (callBase)
362                 {
363                     base.AfterReceiveReply(ref reply, correlationState);
364                 }
365             }
366         }
367
368         class JsonErrorHandler : IErrorHandler
369         {
370             bool includeExceptionDetailInFaults;
371             string outgoingContentType;
372
373             public JsonErrorHandler(ServiceEndpoint endpoint, bool includeExceptionDetailInFaults)
374             {
375                 WebMessageEncodingBindingElement webMEBE = endpoint.Binding.CreateBindingElements().Find<WebMessageEncodingBindingElement>();
376                 outgoingContentType = JsonMessageEncoderFactory.GetContentType(webMEBE);
377                 this.includeExceptionDetailInFaults = includeExceptionDetailInFaults;
378             }
379
380             public bool HandleError(Exception error)
381             {
382                 return false;
383             }
384
385             public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
386             {
387                 HttpResponseMessageProperty responseProperty;
388                 if (fault == null)
389                 {
390                     FaultCode code = new FaultCode(FaultCodeConstants.Codes.InternalServiceFault, FaultCodeConstants.Namespaces.NetDispatch);
391                     code = FaultCode.CreateReceiverFaultCode(code);
392                     string action = FaultCodeConstants.Actions.NetDispatcher;
393
394                     MessageFault innerFault;
395                     innerFault = MessageFault.CreateFault(code, new FaultReason(error.Message, CultureInfo.CurrentCulture), new ExceptionDetail(error));
396                     fault = Message.CreateMessage(version, action, new JsonFaultBodyWriter(innerFault, this.includeExceptionDetailInFaults));
397
398                     responseProperty = new HttpResponseMessageProperty();
399                     fault.Properties.Add(HttpResponseMessageProperty.Name, responseProperty);
400                 }
401                 else
402                 {
403                     MessageFault innerFault = MessageFault.CreateFault(fault, TransportDefaults.MaxFaultSize);
404                     Message newMessage = Message.CreateMessage(version, fault.Headers.Action, new JsonFaultBodyWriter(innerFault, this.includeExceptionDetailInFaults));
405                     newMessage.Headers.To = fault.Headers.To;
406                     newMessage.Properties.CopyProperties(fault.Properties);
407
408                     object property = null;
409                     if (newMessage.Properties.TryGetValue(HttpResponseMessageProperty.Name, out property))
410                     {
411                         responseProperty = (HttpResponseMessageProperty)property;
412                     }
413                     else
414                     {
415                         responseProperty = new HttpResponseMessageProperty();
416                         newMessage.Properties.Add(HttpResponseMessageProperty.Name, responseProperty);
417                     }
418
419                     fault.Close();
420                     fault = newMessage;
421                 }
422                 responseProperty.Headers.Add(HttpResponseHeader.ContentType, outgoingContentType);
423                 responseProperty.Headers.Add(JsonGlobals.jsonerrorString, JsonGlobals.trueString);
424                 responseProperty.StatusCode = System.Net.HttpStatusCode.InternalServerError;
425
426                 object bodyFormatPropertyObject;
427                 if (fault.Properties.TryGetValue(WebBodyFormatMessageProperty.Name, out bodyFormatPropertyObject))
428                 {
429                     WebBodyFormatMessageProperty bodyFormatProperty = bodyFormatPropertyObject as WebBodyFormatMessageProperty;
430                     if ((bodyFormatProperty == null) ||
431                         (bodyFormatProperty.Format != WebContentFormat.Json))
432                     {
433                         fault.Properties[WebBodyFormatMessageProperty.Name] = WebBodyFormatMessageProperty.JsonProperty;
434                     }
435                 }
436                 else
437                 {
438                     fault.Properties.Add(WebBodyFormatMessageProperty.Name, WebBodyFormatMessageProperty.JsonProperty);
439                 }
440             }
441
442             class JsonFaultBodyWriter : BodyWriter
443             {
444                 JsonFaultDetail faultDetail;
445
446                 public JsonFaultBodyWriter(MessageFault fault, bool includeExceptionDetailInFaults)
447                     : base(false)
448                 {
449                     faultDetail = new JsonFaultDetail();
450                     if (includeExceptionDetailInFaults)
451                     {
452                         faultDetail.Message = fault.Reason.ToString();
453                         if (fault.HasDetail)
454                         {
455                             try
456                             {
457                                 ExceptionDetail originalFaultDetail = fault.GetDetail<ExceptionDetail>();
458                                 faultDetail.StackTrace = originalFaultDetail.StackTrace;
459                                 faultDetail.ExceptionType = originalFaultDetail.Type;
460                                 faultDetail.ExceptionDetail = originalFaultDetail;
461                             }
462                             catch (SerializationException exception)
463                             {
464                                 System.ServiceModel.DiagnosticUtility.TraceHandledException(exception, TraceEventType.Information);
465                                 // A SerializationException will be thrown if the detail isn't of type ExceptionDetail
466                                 // In that case, we want to just move on.
467                             }
468                             catch (SecurityException exception)
469                             {
470                                 System.ServiceModel.DiagnosticUtility.TraceHandledException(exception, TraceEventType.Information);
471                                 // A SecurityException will be thrown if the detail can't be obtained in partial trust
472                                 // (This is guaranteed to happen unless there's an Assert for MemberAccessPermission, since ExceptionDetail
473                                 //     has DataMembers that have private setters.)
474                                 // In that case, we want to just move on.
475                             }
476                         }
477                     }
478                     else
479                     {
480                         faultDetail.Message = System.ServiceModel.SR.GetString(System.ServiceModel.SR.SFxInternalServerError);
481                     }
482                 }
483
484                 protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
485                 {
486                     jsonFaultSerializer.WriteObject(writer, faultDetail);
487                 }
488             }
489         }
490     }
491 }