File: WebHttpDispatchOperationSelector.cs

package info (click to toggle)
mono 6.12.0.199%2Bdfsg-6
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 1,296,836 kB
  • sloc: cs: 11,181,803; xml: 2,850,076; ansic: 699,709; cpp: 123,344; perl: 59,361; javascript: 30,841; asm: 21,853; makefile: 20,405; sh: 15,009; python: 4,839; pascal: 925; sql: 859; sed: 16; php: 1
file content (404 lines) | stat: -rw-r--r-- 16,717 bytes parent folder | download | duplicates (6)
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
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
#pragma warning disable 1634, 1691
namespace System.ServiceModel.Dispatcher
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Runtime;
    using System.ServiceModel;
    using System.ServiceModel.Activation;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Description;
    using System.ServiceModel.Diagnostics;
    using System.ServiceModel.Web;
    using System.Net;

    public class WebHttpDispatchOperationSelector : IDispatchOperationSelector
    {
        public const string HttpOperationSelectorUriMatchedPropertyName = "UriMatched";
        internal const string HttpOperationSelectorDataPropertyName = "HttpOperationSelectorData";

        // 
        public const string HttpOperationNamePropertyName = "HttpOperationName";
        internal const string redirectOperationName = ""; // always unhandled invoker
        internal const string RedirectPropertyName = "WebHttpRedirect";

        string catchAllOperationName = ""; // user UT=* Method=* operation, else unhandled invoker

        Dictionary<string, UriTemplateTable> methodSpecificTables; // indexed by the http method name
        UriTemplateTable wildcardTable; // this is one of the methodSpecificTables, special-cased for faster access
        Dictionary<string, UriTemplate> templates;

        UriTemplateTable helpUriTable;

        public WebHttpDispatchOperationSelector(ServiceEndpoint endpoint)
        {
            if (endpoint == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpoint");
            }
            if (endpoint.Address == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                    SR2.GetString(SR2.EndpointAddressCannotBeNull)));
            }
#pragma warning disable 56506 // Microsoft, endpoint.Address.Uri is never null
            Uri baseUri = endpoint.Address.Uri;
            this.methodSpecificTables = new Dictionary<string, UriTemplateTable>();
            this.templates = new Dictionary<string, UriTemplate>();
#pragma warning restore 56506

            WebHttpBehavior webHttpBehavior = endpoint.Behaviors.Find<WebHttpBehavior>();
            if (webHttpBehavior != null && webHttpBehavior.HelpEnabled)
            {
                this.helpUriTable = new UriTemplateTable(endpoint.ListenUri, HelpPage.GetOperationTemplatePairs());
            }

            Dictionary<WCFKey, string> alreadyHaves = new Dictionary<WCFKey, string>();

#pragma warning disable 56506 // Microsoft, endpoint.Contract is never null
            foreach (OperationDescription od in endpoint.Contract.Operations)
#pragma warning restore 56506
            {
                // ignore callback operations
                if (od.Messages[0].Direction == MessageDirection.Input)
                {
                    string method = WebHttpBehavior.GetWebMethod(od);
                    string path = UriTemplateClientFormatter.GetUTStringOrDefault(od);

                    // 

                    if (UriTemplateHelpers.IsWildcardPath(path) && (method == WebHttpBehavior.WildcardMethod))
                    {
                        if (this.catchAllOperationName != "")
                        {
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                                new InvalidOperationException(
                                SR2.GetString(SR2.MultipleOperationsInContractWithPathMethod,
                                endpoint.Contract.Name, path, method)));
                        }
                        this.catchAllOperationName = od.Name;
                    }
                    UriTemplate ut = new UriTemplate(path);
                    WCFKey wcfKey = new WCFKey(ut, method);
                    if (alreadyHaves.ContainsKey(wcfKey))
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                            new InvalidOperationException(
                            SR2.GetString(SR2.MultipleOperationsInContractWithPathMethod,
                            endpoint.Contract.Name, path, method)));
                    }
                    alreadyHaves.Add(wcfKey, od.Name);

                    UriTemplateTable methodSpecificTable;
                    if (!methodSpecificTables.TryGetValue(method, out methodSpecificTable))
                    {
                        methodSpecificTable = new UriTemplateTable(baseUri);
                        methodSpecificTables.Add(method, methodSpecificTable);
                    }

                    methodSpecificTable.KeyValuePairs.Add(new KeyValuePair<UriTemplate, object>(ut, od.Name));
                    this.templates.Add(od.Name, ut);
                }
            }

            if (this.methodSpecificTables.Count == 0)
            {
                this.methodSpecificTables = null;
            }
            else
            {
                // freeze all the tables because they should not be modified after this point
                foreach (UriTemplateTable table in this.methodSpecificTables.Values)
                {
                    table.MakeReadOnly(true /* allowDuplicateEquivalentUriTemplates */);
                }

                if (!methodSpecificTables.TryGetValue(WebHttpBehavior.WildcardMethod, out wildcardTable))
                {
                    wildcardTable = null;
                }
            }
        }

        protected WebHttpDispatchOperationSelector()
        {
        }

        public virtual UriTemplate GetUriTemplate(string operationName)
        {
            if (operationName == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("operationName");
            }
            UriTemplate result;
            if (!this.templates.TryGetValue(operationName, out result))
            {
                return null;
            }
            else
            {
                return result;
            }
        }

        [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "0#", Justification = "This method is defined by the IDispatchOperationSelector interface")]
        public string SelectOperation(ref Message message)
        {
            if (message == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("message");
            }
            bool uriMatched;
            string result = this.SelectOperation(ref message, out uriMatched);
#pragma warning disable 56506 // Microsoft, Message.Properties is never null
            message.Properties.Add(HttpOperationSelectorUriMatchedPropertyName, uriMatched);
#pragma warning restore 56506
            if (result != null)
            {
                message.Properties.Add(HttpOperationNamePropertyName, result);
                if (DiagnosticUtility.ShouldTraceInformation)
                {
#pragma warning disable 56506 // Microsoft, Message.Headers is never null
                    TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.WebRequestMatchesOperation, SR2.GetString(SR2.TraceCodeWebRequestMatchesOperation, message.Headers.To, result));
#pragma warning restore 56506
                }
            }
            return result;
        }

        [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "0#", Justification = "This method is like that defined by the IDispatchOperationSelector interface")]
        [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "This API needs to return multiple things")]
        protected virtual string SelectOperation(ref Message message, out bool uriMatched)
        {
            if (message == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("message");
            }
            uriMatched = false;
            if (this.methodSpecificTables == null)
            {
                return this.catchAllOperationName;
            }

#pragma warning disable 56506 // Microsoft, message.Properties is never null
            if (!message.Properties.ContainsKey(HttpRequestMessageProperty.Name))
            {
                return this.catchAllOperationName;
            }
            HttpRequestMessageProperty prop = message.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
            if (prop == null)
            {
                return this.catchAllOperationName;
            }
            string method = prop.Method;

            Uri to = message.Headers.To;
#pragma warning restore 56506

            if (to == null)
            {
                return this.catchAllOperationName;
            }

            if (this.helpUriTable != null)
            {
                UriTemplateMatch match = this.helpUriTable.MatchSingle(to);
                if (match != null)
                {
                    uriMatched = true;
                    AddUriTemplateMatch(match, prop, message);
                    if (method == WebHttpBehavior.GET)
                    {
                        return HelpOperationInvoker.OperationName;
                    }
                    message.Properties.Add(WebHttpDispatchOperationSelector.HttpOperationSelectorDataPropertyName,
                        new WebHttpDispatchOperationSelectorData() { AllowedMethods = new List<string>() { WebHttpBehavior.GET } });
                    return this.catchAllOperationName;
                }
            }

            UriTemplateTable methodSpecificTable;
            bool methodMatchesExactly = methodSpecificTables.TryGetValue(method, out methodSpecificTable);
            if (methodMatchesExactly)
            {
                string operationName;
                uriMatched = CanUriMatch(methodSpecificTable, to, prop, message, out operationName);
                if (uriMatched)
                {
                    return operationName;
                }
            }

            if (wildcardTable != null)
            {
                string operationName;
                uriMatched = CanUriMatch(wildcardTable, to, prop, message, out operationName);
                if (uriMatched)
                {
                    return operationName;
                }
            }

            if (ShouldRedirectToUriWithSlashAtTheEnd(methodSpecificTable, message, to))
            {
                return redirectOperationName;
            }

            // the {method, uri} pair does not match anything the service supports.
            // we know at this point that we'll return some kind of error code, but we 
            // should go through all methods for the uri to see if any method is supported
            // so that that information could be returned to the user as well

            List<string> allowedMethods = null;
            foreach (KeyValuePair<string, UriTemplateTable> pair in methodSpecificTables)
            {
                if (pair.Key == method || pair.Key == WebHttpBehavior.WildcardMethod)
                {
                    // the uri must not match the uri template
                    continue;
                }
                UriTemplateTable table = pair.Value;
                if (table.MatchSingle(to) != null)
                {
                    if (allowedMethods == null)
                    {
                        allowedMethods = new List<string>();
                    }

                    // 

                    if (!allowedMethods.Contains(pair.Key))
                    {
                        allowedMethods.Add(pair.Key);
                    }
                }
            }

            if (allowedMethods != null)
            {
                uriMatched = true;
                message.Properties.Add(WebHttpDispatchOperationSelector.HttpOperationSelectorDataPropertyName,
                    new WebHttpDispatchOperationSelectorData() { AllowedMethods = allowedMethods });
            }
            return catchAllOperationName;
        }

        bool CanUriMatch(UriTemplateTable methodSpecificTable, Uri to, HttpRequestMessageProperty prop, Message message, out string operationName)
        {
            operationName = null;
            UriTemplateMatch result = methodSpecificTable.MatchSingle(to);

            if (result != null)
            {
                operationName = result.Data as string;
                Fx.Assert(operationName != null, "bad result");
                AddUriTemplateMatch(result, prop, message);
                return true;
            }
            return false;
        }

        void AddUriTemplateMatch(UriTemplateMatch match, HttpRequestMessageProperty requestProp, Message message)
        {
            match.SetBaseUri(match.BaseUri, requestProp);
            message.Properties.Add(IncomingWebRequestContext.UriTemplateMatchResultsPropertyName, match);
        }

        bool ShouldRedirectToUriWithSlashAtTheEnd(UriTemplateTable methodSpecificTable, Message message, Uri to)
        {
            UriBuilder ub = new UriBuilder(to);
            if (ub.Path.EndsWith("/", StringComparison.Ordinal))
            {
                return false;
            }

            ub.Path = ub.Path + "/";
            Uri originalPlusSlash = ub.Uri;

            bool result = false;
            if (methodSpecificTable != null && methodSpecificTable.MatchSingle(originalPlusSlash) != null)
            {
                // as an optimization, we check the table that matched the request's method
                // first, as it is more probable that a hit happens there
                result = true;
            }
            else
            {
                // back-compat:
                // we will redirect as long as there is any method 
                // - not necessary the one the user is looking for -
                // that matches the uri with a slash at the end

                foreach (KeyValuePair<string, UriTemplateTable> pair in methodSpecificTables)
                {
                    UriTemplateTable table = pair.Value;
                    if (table != methodSpecificTable && table.MatchSingle(originalPlusSlash) != null)
                    {
                        result = true;
                        break;
                    }
                }
            }

            if (result)
            {
                string hostAndPort = GetAuthority(message);
                originalPlusSlash = UriTemplate.RewriteUri(ub.Uri, hostAndPort);
                message.Properties.Add(RedirectPropertyName, originalPlusSlash);
            }
            return result;
        }

        static string GetAuthority(Message message)
        {
            HttpRequestMessageProperty requestProperty;
            string hostName = null;
            if (message.Properties.TryGetValue(HttpRequestMessageProperty.Name, out requestProperty))
            {
                hostName = requestProperty.Headers[HttpRequestHeader.Host];
                if (!string.IsNullOrEmpty(hostName))
                {
                    return hostName;
                }
            }
            IAspNetMessageProperty aspNetMessageProperty = AspNetEnvironment.Current.GetHostingProperty(message);
            if (aspNetMessageProperty != null)
            {
                hostName = aspNetMessageProperty.OriginalRequestUri.Authority;
            }
            return hostName;
        }

        // to enforce that no two ops have same UriTemplate & Method
        class WCFKey
        {
            string method;
            UriTemplate uriTemplate;
            public WCFKey(UriTemplate uriTemplate, string method)
            {
                this.uriTemplate = uriTemplate;
                this.method = method;
            }
            public override bool Equals(object obj)
            {
                WCFKey other = obj as WCFKey;
                if (other == null)
                {
                    return false;
                }
                return this.uriTemplate.IsEquivalentTo(other.uriTemplate) && this.method == other.method;
            }
            public override int GetHashCode()
            {
                return UriTemplateEquivalenceComparer.Instance.GetHashCode(this.uriTemplate);
            }
        }
    }
}