summaryrefslogtreecommitdiff
path: root/external/aspnetwebstack/src/System.Web.WebPages/WebPageRoute.cs
blob: b5c2d963970ec62a10cd8e438cb5421aa7faeb27 (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
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.IO;
using System.Web.WebPages.Deployment;
using System.Web.WebPages.Resources;

namespace System.Web.WebPages
{
    internal sealed class WebPageRoute
    {
        private static readonly Lazy<bool> _isRootExplicitlyDisabled = new Lazy<bool>(() => WebPagesDeployment.IsExplicitlyDisabled("~/"));
        private IVirtualPathFactory _virtualPathFactory;
        private bool? _isExplicitlyDisabled;

        internal IVirtualPathFactory VirtualPathFactory
        {
            get { return _virtualPathFactory ?? VirtualPathFactoryManager.Instance; }
            set { _virtualPathFactory = value; }
        }

        internal bool IsExplicitlyDisabled
        {
            get { return _isExplicitlyDisabled ?? _isRootExplicitlyDisabled.Value; }
            set { _isExplicitlyDisabled = value; }
        }

        internal void DoPostResolveRequestCache(HttpContextBase context)
        {
            if (IsExplicitlyDisabled)
            {
                // If the root config is explicitly disabled, do not process the request.
                return;
            }

            // Parse incoming URL (we trim off the first two chars since they're always "~/")
            string requestPath = context.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + context.Request.PathInfo;
            var registeredExtensions = WebPageHttpHandler.GetRegisteredExtensions();

            // Check if this request matches a file in the app
            WebPageMatch webpageRouteMatch = MatchRequest(requestPath, registeredExtensions, VirtualPathFactory, context, DisplayModeProvider.Instance);
            if (webpageRouteMatch != null)
            {
                // If it matches then save some data for the WebPage's UrlData
                context.Items[typeof(WebPageMatch)] = webpageRouteMatch;

                string virtualPath = "~/" + webpageRouteMatch.MatchedPath;

                // Verify that this path is enabled before remapping
                if (!WebPagesDeployment.IsExplicitlyDisabled(virtualPath))
                {
                    IHttpHandler handler = WebPageHttpHandler.CreateFromVirtualPath(virtualPath);
                    if (handler != null)
                    {
                        SessionStateUtil.SetUpSessionState(context, handler);
                        // Remap to our handler
                        context.RemapHandler(handler);
                    }
                }
            }
            else
            {
                // Bug:904704 If its not a match, but to a supported extension, we want to return a 404 instead of a 403
                string extension = PathUtil.GetExtension(requestPath);
                foreach (string supportedExt in registeredExtensions)
                {
                    if (String.Equals("." + supportedExt, extension, StringComparison.OrdinalIgnoreCase))
                    {
                        throw new HttpException(404, null);
                    }
                }
            }
        }

        private static bool FileExists(string virtualPath, IVirtualPathFactory virtualPathFactory)
        {
            var path = "~/" + virtualPath;
            return virtualPathFactory.Exists(path);
        }

        internal static WebPageMatch GetWebPageMatch(HttpContextBase context)
        {
            WebPageMatch webPageMatch = (WebPageMatch)context.Items[typeof(WebPageMatch)];
            return webPageMatch;
        }

        private static string GetRouteLevelMatch(string pathValue, IEnumerable<string> supportedExtensions, IVirtualPathFactory virtualPathFactory, HttpContextBase context, DisplayModeProvider displayModeProvider)
        {
            foreach (string supportedExtension in supportedExtensions)
            {
                string virtualPath = "~/" + pathValue;

                // Only add the extension if it's not already there
                if (!virtualPath.EndsWith("." + supportedExtension, StringComparison.OrdinalIgnoreCase))
                {
                    virtualPath += "." + supportedExtension;
                }
                DisplayInfo virtualPathDisplayInfo = displayModeProvider.GetDisplayInfoForVirtualPath(virtualPath, context, virtualPathFactory.Exists, currentDisplayMode: null);

                if (virtualPathDisplayInfo != null)
                {
                    // If there's an exact match on disk, return it unless it starts with an underscore
                    if (Path.GetFileName(virtualPathDisplayInfo.FilePath).StartsWith("_", StringComparison.OrdinalIgnoreCase))
                    {
                        throw new HttpException(404, WebPageResources.WebPageRoute_UnderscoreBlocked);
                    }

                    string resolvedVirtualPath = virtualPathDisplayInfo.FilePath;

                    // Matches are not expected to be virtual paths so remove the ~/ from the match
                    if (resolvedVirtualPath.StartsWith("~/", StringComparison.OrdinalIgnoreCase))
                    {
                        resolvedVirtualPath = resolvedVirtualPath.Remove(0, 2);
                    }

                    DisplayModeProvider.SetDisplayMode(context, virtualPathDisplayInfo.DisplayMode);

                    return resolvedVirtualPath;
                }
            }

            return null;
        }

        internal static WebPageMatch MatchRequest(string pathValue, IEnumerable<string> supportedExtensions, IVirtualPathFactory virtualPathFactory, HttpContextBase context, DisplayModeProvider displayModes)
        {
            string currentLevel = String.Empty;
            string currentPathInfo = pathValue;

            // We can skip the file exists check and normal lookup for empty paths, but we still need to look for default pages
            if (!String.IsNullOrEmpty(pathValue))
            {
                // If the file exists and its not a supported extension, let the request go through
                if (FileExists(pathValue, virtualPathFactory))
                {
                    // TODO: Look into switching to RawURL to eliminate the need for this issue
                    bool foundSupportedExtension = false;
                    foreach (string supportedExtension in supportedExtensions)
                    {
                        if (pathValue.EndsWith("." + supportedExtension, StringComparison.OrdinalIgnoreCase))
                        {
                            foundSupportedExtension = true;
                            break;
                        }
                    }

                    if (!foundSupportedExtension)
                    {
                        return null;
                    }
                }

                // For each trimmed part of the path try to add a known extension and
                // check if it matches a file in the application.
                currentLevel = pathValue;
                currentPathInfo = String.Empty;
                while (true)
                {
                    // Does the current route level patch any supported extension?
                    string routeLevelMatch = GetRouteLevelMatch(currentLevel, supportedExtensions, virtualPathFactory, context, displayModes);
                    if (routeLevelMatch != null)
                    {
                        return new WebPageMatch(routeLevelMatch, currentPathInfo);
                    }

                    // Try to remove the last path segment (e.g. go from /foo/bar to /foo)
                    int indexOfLastSlash = currentLevel.LastIndexOf('/');
                    if (indexOfLastSlash == -1)
                    {
                        // If there are no more slashes, we're done
                        break;
                    }
                    else
                    {
                        // Chop off the last path segment to get to the next one
                        currentLevel = currentLevel.Substring(0, indexOfLastSlash);

                        // And save the path info in case there is a match
                        currentPathInfo = pathValue.Substring(indexOfLastSlash + 1);
                    }
                }
            }

            return MatchDefaultFiles(pathValue, supportedExtensions, virtualPathFactory, context, displayModes, currentLevel);
        }

        private static WebPageMatch MatchDefaultFiles(string pathValue, IEnumerable<string> supportedExtensions, IVirtualPathFactory virtualPathFactory, HttpContextBase context, DisplayModeProvider displayModes, string currentLevel)
        {
            // If we haven't found anything yet, now try looking for default.* or index.* at the current url
            currentLevel = pathValue;
            string currentLevelDefault;
            string currentLevelIndex;
            if (String.IsNullOrEmpty(currentLevel))
            {
                currentLevelDefault = "default";
                currentLevelIndex = "index";
            }
            else
            {
                if (currentLevel[currentLevel.Length - 1] != '/')
                {
                    currentLevel += "/";
                }
                currentLevelDefault = currentLevel + "default";
                currentLevelIndex = currentLevel + "index";
            }

            // Does the current route level match any supported extension?
            string defaultMatch = GetRouteLevelMatch(currentLevelDefault, supportedExtensions, virtualPathFactory, context, displayModes);
            if (defaultMatch != null)
            {
                return new WebPageMatch(defaultMatch, String.Empty);
            }

            string indexMatch = GetRouteLevelMatch(currentLevelIndex, supportedExtensions, virtualPathFactory, context, displayModes);
            if (indexMatch != null)
            {
                return new WebPageMatch(indexMatch, String.Empty);
            }

            return null;
        }
    }
}