Welcome to mirror list, hosted at ThFree Co, Russian Federation.

RestHandler.cs « Services « Script « System.Web.Extensions « referencesource « class « mcs - github.com/mono/mono.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 29d3f6088b26ba91045cfd505439c196b3f48a42 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
//------------------------------------------------------------------------------
// <copyright file="RestHandler.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Web.Script.Services {
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;
    using System.Net;
    using System.Reflection;
    using System.Security;
    using System.Text;
    using System.Web;
    using System.Web.Resources;
    using System.Web.Script.Serialization;
    using System.Web.SessionState;

    internal class RestHandler : IHttpHandler {
        private WebServiceMethodData _webServiceMethodData;

        internal static IHttpHandler CreateHandler(HttpContext context) {
            // Expectation is that we got a PathInfo of form /MethodName
            if (context.Request.PathInfo.Length < 2 || context.Request.PathInfo[0] != '/') {
                throw new InvalidOperationException(AtlasWeb.WebService_InvalidWebServiceCall);
            }

            // Get the data about the web service being invoked
            WebServiceData webServiceData = WebServiceData.GetWebServiceData(context, context.Request.FilePath);
            string methodName = context.Request.PathInfo.Substring(1);
            return CreateHandler(webServiceData, methodName);
        }

        private static IHttpHandler CreateHandler(WebServiceData webServiceData, string methodName) {

            // Get the data about the method being called
            WebServiceMethodData methodData = webServiceData.GetMethodData(methodName);

            // Create the proper handler, depending on whether we need session state
            RestHandler handler;
            if (methodData.RequiresSession)
                handler = new RestHandlerWithSession();
            else
                handler = new RestHandler();

            // Save the method data in the handler
            handler._webServiceMethodData = methodData;
            return handler;
        }

        // This is very similar to WebService caching, the differences are
        // 1) Here we explicitely SetValidUntilExpires(true) because in an XmlHttp there is
        //    "pragma:no-cache" in header which would result in cache miss on the server.
        // 2) Here we don't vary on header "Content-type" or "SOAPAction" because the former
        //    is specific to soap 1.2, which puts action in the content-type param; and the
        //    later is used by soap calls.
        private static void InitializeCachePolicy(WebServiceMethodData methodData, HttpContext context) {
            int cacheDuration = methodData.CacheDuration;
            if (cacheDuration > 0) {
                context.Response.Cache.SetCacheability(HttpCacheability.Server);
                context.Response.Cache.SetExpires(DateTime.Now.AddSeconds(cacheDuration));
                context.Response.Cache.SetSlidingExpiration(false);
                context.Response.Cache.SetValidUntilExpires(true);

                // DevDiv 23596: Don't set VaryBy* if the method takes no parameters
                if (methodData.ParameterDatas.Count > 0) {
                    context.Response.Cache.VaryByParams["*"] = true;
                }
                else {
                    context.Response.Cache.VaryByParams.IgnoreParams = true;
                }
            }
            else {
                context.Response.Cache.SetNoServerCaching();
                context.Response.Cache.SetMaxAge(TimeSpan.Zero);
            }
        }

        private static IDictionary<string, object> GetRawParamsFromGetRequest(HttpContext context, JavaScriptSerializer serializer, WebServiceMethodData methodData) {
            // Get all the parameters from the query string
            NameValueCollection queryString = context.Request.QueryString;
            Dictionary<string, object> rawParams = new Dictionary<string, object>();
            foreach (WebServiceParameterData param in methodData.ParameterDatas) {
                string name = param.ParameterInfo.Name;
                string val = queryString[name];
                if (val != null) {
                    rawParams.Add(name, serializer.DeserializeObject(val));
                }
            }
            return rawParams;
        }

        private static IDictionary<string, object> GetRawParamsFromPostRequest(HttpContext context, JavaScriptSerializer serializer) {
            // Read the entire body as a string
            TextReader reader = new StreamReader(context.Request.InputStream);
            string bodyString = reader.ReadToEnd();

            // If there is no body, treat it as an empty object
            if (String.IsNullOrEmpty(bodyString)) {
                return new Dictionary<string, object>();
            }

            // Deserialize the javascript request body
            return serializer.Deserialize<IDictionary<string, object>>(bodyString);
        }

        private static IDictionary<string, object> GetRawParams(WebServiceMethodData methodData, HttpContext context) {
            if (methodData.UseGet) {
                if (context.Request.HttpMethod == "GET") {
                    return GetRawParamsFromGetRequest(context, methodData.Owner.Serializer, methodData);
                }
                else {
                    throw new InvalidOperationException(
                        String.Format(CultureInfo.CurrentCulture, AtlasWeb.WebService_InvalidVerbRequest,
                            methodData.MethodName, "POST"));
                }
            }
            else if (context.Request.HttpMethod == "POST") {
                return GetRawParamsFromPostRequest(context, methodData.Owner.Serializer);
            } else {
                throw new InvalidOperationException(
                    String.Format(CultureInfo.CurrentCulture, AtlasWeb.WebService_InvalidVerbRequest,
                        methodData.MethodName, "GET"));
            }
        }

        private static void InvokeMethod(HttpContext context, WebServiceMethodData methodData, IDictionary<string, object> rawParams) {
            // Initialize HttpCachePolicy
            InitializeCachePolicy(methodData, context);

            // Create an new instance of the class
            object target = null;
            if (!methodData.IsStatic) target = Activator.CreateInstance(methodData.Owner.TypeData.Type);

            // Make the actual method call on it
            object retVal = methodData.CallMethodFromRawParams(target, rawParams);

            string contentType;
            string responseString = null;
            if (methodData.UseXmlResponse) {
                responseString = retVal as string;

                // If it's a string, output it as is unless XmlSerializeString is set
                if (responseString == null || methodData.XmlSerializeString) {
                    // Use the Xml Serializer
                    try {
                        responseString = ServicesUtilities.XmlSerializeObjectToString(retVal);
                    }
                    catch (Exception e) {
                        // Throw a better error if Xml serialization fails
                        throw new InvalidOperationException(
                            String.Format(CultureInfo.CurrentCulture, AtlasWeb.WebService_InvalidXmlReturnType,
                                methodData.MethodName, retVal.GetType().FullName, e.Message));
                    }
                }

                contentType = "text/xml";
            }
            else {

                // Convert the result to a JSON string
                // DevDiv 88409:Change JSON wire format to prevent CSRF attack 
                // We wrap the returned value inside an object , and assign the returned value
                // to member "d" of the object. We do so as JSOM for object will never be parsed
                // as valid Javascript , unlike arrays.
                responseString =@"{""d"":" + methodData.Owner.Serializer.Serialize(retVal) + "}";
                contentType = "application/json";
            }

            // Set the response content-type
            context.Response.ContentType = contentType;

            // Write the string to the response
            if (responseString != null)
                context.Response.Write(responseString);
        }

        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification="All exceptions need to be reported to the client")]
        [SuppressMessage("Microsoft.Security", "CA2107:ReviewDenyAndPermitOnlyUsage",
           Justification = "Fix for DevDiv 39162 GAC'd non-APTCA types can instantiate in networking stack in Medium trust")]
        internal static void ExecuteWebServiceCall(HttpContext context, WebServiceMethodData methodData) {
            try {
                NamedPermissionSet s_permissionSet = HttpRuntime.NamedPermissionSet;
                if (s_permissionSet != null) {
                    s_permissionSet.PermitOnly();
                }

                // Deserialize the javascript request body
                IDictionary<string, object> rawParams = GetRawParams(methodData, context);
                InvokeMethod(context, methodData, rawParams);
            }
            catch (Exception ex) {
                WriteExceptionJsonString(context, ex);
            }
        }

        private static object BuildWebServiceError(string msg, string stack, string type) {
            var result = new OrderedDictionary();
            result["Message"] = msg;
            result["StackTrace"] = stack;
            result["ExceptionType"] = type;
            return result;
        }

        internal static void WriteExceptionJsonString(HttpContext context, Exception ex) {
            WriteExceptionJsonString(context, ex, (int)HttpStatusCode.InternalServerError);
        }

        internal static void WriteExceptionJsonString(HttpContext context, Exception ex, int statusCode) {
            // Record the charset before we call ClearHeaders(). (DevDiv Bugs 158401)
            string charset = context.Response.Charset;
            context.Response.ClearHeaders();
            context.Response.ClearContent();
            context.Response.Clear();
            context.Response.StatusCode = statusCode;
            context.Response.StatusDescription = HttpWorkerRequest.GetStatusDescription(statusCode);
            context.Response.ContentType = "application/json";
            context.Response.AddHeader("jsonerror", "true");
            // Maintain the Charset from before. (DevDiv Bugs 158401)
            context.Response.Charset = charset;
            //Devdiv 


            context.Response.TrySkipIisCustomErrors = true;
            using (StreamWriter writer = new StreamWriter(context.Response.OutputStream, new UTF8Encoding(false))) {
                if (ex is TargetInvocationException) {
                    ex = ex.InnerException;
                }

                // Don't show any error stack or sensitive info when custom error is enabled.
                if (context.IsCustomErrorEnabled) {
                    writer.Write(JavaScriptSerializer.SerializeInternal(BuildWebServiceError(AtlasWeb.WebService_Error, String.Empty, String.Empty)));
                }
                else {
                    writer.Write(JavaScriptSerializer.SerializeInternal(BuildWebServiceError(ex.Message, ex.StackTrace, ex.GetType().FullName)));
                }
                writer.Flush();
            }
        }

        public void ProcessRequest(HttpContext context) {
            ExecuteWebServiceCall(context, _webServiceMethodData);
        }

        public bool IsReusable {
            get {
                return false;
            }
        }
    }

    // Same handler, but implementing IRequiresSessionState to allow session state use
    internal class RestHandlerWithSession: RestHandler, IRequiresSessionState {
    }
}