forked from aspnet/AspNetWebStack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathUrlUtil.cs
More file actions
252 lines (225 loc) · 10.1 KB
/
Copy pathUrlUtil.cs
File metadata and controls
252 lines (225 loc) · 10.1 KB
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
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Web.Routing;
namespace System.Web.WebPages
{
internal static class UrlUtil
{
private static UrlRewriterHelper _urlRewriterHelper = new UrlRewriterHelper();
// this method can accept an app-relative path or an absolute path for contentPath
public static string GenerateClientUrl(HttpContextBase httpContext, string contentPath)
{
if (String.IsNullOrEmpty(contentPath))
{
return contentPath;
}
// many of the methods we call internally can't handle query strings properly, so just strip it out for
// the time being
string query;
contentPath = StripQuery(contentPath, out query);
// many of the methods we call internally can't handle query strings properly, so tack it on after processing
// the virtual app path and url rewrites
if (String.IsNullOrEmpty(query))
{
return GenerateClientUrlInternal(httpContext, contentPath);
}
else
{
return GenerateClientUrlInternal(httpContext, contentPath) + query;
}
}
public static string GenerateClientUrl(HttpContextBase httpContext, string basePath, string path, params object[] pathParts)
{
if (String.IsNullOrEmpty(path))
{
return path;
}
if (pathParts != null)
{
for (int i = 0; i < pathParts.Length; i++)
{
if (pathParts[i] == null)
{
throw new ArgumentNullException("pathParts");
}
}
}
if (basePath != null)
{
path = VirtualPathUtility.Combine(basePath, path);
}
string query;
string processedPath = BuildUrl(path, out query, pathParts);
// many of the methods we call internally can't handle query strings properly, so tack it on after processing
// the virtual app path and url rewrites
if (String.IsNullOrEmpty(query))
{
return GenerateClientUrlInternal(httpContext, processedPath);
}
else
{
return GenerateClientUrlInternal(httpContext, processedPath) + query;
}
}
private static string GenerateClientUrlInternal(HttpContextBase httpContext, string contentPath)
{
if (String.IsNullOrEmpty(contentPath))
{
return contentPath;
}
// can't call VirtualPathUtility.IsAppRelative since it throws on some inputs
bool isAppRelative = contentPath[0] == '~';
if (isAppRelative)
{
string absoluteContentPath = VirtualPathUtility.ToAbsolute(contentPath, httpContext.Request.ApplicationPath);
return GenerateClientUrlInternal(httpContext, absoluteContentPath);
}
// we only want to manipulate the path if URL rewriting is active for this request, else we risk breaking the generated URL
bool wasRequestRewritten = _urlRewriterHelper.WasRequestRewritten(httpContext);
if (!wasRequestRewritten)
{
return contentPath;
}
// Since the rawUrl represents what the user sees in his browser, it is what we want to use as the base
// of our absolute paths. For example, consider mysite.example.com/foo, which is internally
// rewritten to content.example.com/mysite/foo. When we want to generate a link to ~/bar, we want to
// base it from / instead of /foo, otherwise the user ends up seeing mysite.example.com/foo/bar,
// which is incorrect.
string relativeUrlToDestination = MakeRelative(httpContext.Request.Path, contentPath);
string absoluteUrlToDestination = MakeAbsolute(httpContext.Request.RawUrl, relativeUrlToDestination);
return absoluteUrlToDestination;
}
public static string MakeAbsolute(string basePath, string relativePath)
{
// The Combine() method can't handle query strings on the base path, so we trim it off.
string query;
basePath = StripQuery(basePath, out query);
return VirtualPathUtility.Combine(basePath, relativePath);
}
public static string MakeRelative(string fromPath, string toPath)
{
string relativeUrl = VirtualPathUtility.MakeRelative(fromPath, toPath);
if (String.IsNullOrEmpty(relativeUrl) || relativeUrl[0] == '?')
{
// Sometimes VirtualPathUtility.MakeRelative() will return an empty string when it meant to return '.',
// but links to {empty string} are browser dependent. We replace it with an explicit path to force
// consistency across browsers.
relativeUrl = "./" + relativeUrl;
}
return relativeUrl;
}
private static string StripQuery(string path, out string query)
{
int queryIndex = path.IndexOf('?');
if (queryIndex >= 0)
{
query = path.Substring(queryIndex);
return path.Substring(0, queryIndex);
}
else
{
query = null;
return path;
}
}
internal static void ResetUrlRewriterHelper()
{
_urlRewriterHelper = new UrlRewriterHelper();
}
internal static string BuildUrl(string path, out string query, params object[] pathParts)
{
// Performance senstive
//
// This code branches on the number of path-parts to either favor string.Concat or StringBuilder
// for performance. The most common case (for WebPages) will provide a single int value as a
// path-part - string.Concat can be more efficient when we know the number of strings to join.
if (pathParts == null || pathParts.Length == 0)
{
query = String.Empty;
return HttpUtility.UrlPathEncode(path);
}
else if (pathParts.Length == 1)
{
object pathPart = pathParts[0];
if (IsDisplayableType(pathPart.GetType()))
{
string displayablePath = Convert.ToString(pathPart, CultureInfo.InvariantCulture);
path = path + "/" + displayablePath;
query = String.Empty;
return HttpUtility.UrlPathEncode(path);
}
else
{
StringBuilder queryBuilder = new StringBuilder();
AppendToQueryString(queryBuilder, pathPart);
query = queryBuilder.ToString();
return HttpUtility.UrlPathEncode(path);
}
}
else
{
StringBuilder pathBuilder = new StringBuilder(path);
StringBuilder queryBuilder = new StringBuilder();
for (int i = 0; i < pathParts.Length; i++)
{
object pathPart = pathParts[i];
if (IsDisplayableType(pathPart.GetType()))
{
var displayablePath = Convert.ToString(pathPart, CultureInfo.InvariantCulture);
pathBuilder.Append('/');
pathBuilder.Append(displayablePath);
}
else
{
AppendToQueryString(queryBuilder, pathPart);
}
}
query = queryBuilder.ToString();
return HttpUtility.UrlPathEncode(pathBuilder.ToString());
}
}
private static void AppendToQueryString(StringBuilder queryString, object obj)
{
// If this method is called, then obj isn't a type that we can put in the path, instead
// we want to format it as key-value pairs for the query string. The mostly likely
// user scenario for this is an anonymous type.
IDictionary<string, object> dictionary = TypeHelper.ObjectToDictionary(obj);
foreach (var item in dictionary)
{
if (queryString.Length == 0)
{
queryString.Append('?');
}
else
{
queryString.Append('&');
}
string stringValue = Convert.ToString(item.Value, CultureInfo.InvariantCulture);
queryString.Append(HttpUtility.UrlEncode(item.Key))
.Append('=')
.Append(HttpUtility.UrlEncode(stringValue));
}
}
/// <summary>
/// Determines if a type is displayable as part of a Url path.
/// </summary>
/// <remarks>
/// If a type is a displayable type, then we format values of that type as part of the Url Path. If not, then
/// we attempt to create a RouteValueDictionary, and encode the value as key-value pairs in the query string.
///
/// We determine if a type is displayable by whether or not it implements any interfaces. The built-in simple
/// types like Int32 implement IFormattable, which will be used to convert it to a string.
///
/// Primarily we do this check to allow anonymous types to represent key-value pairs (anonymous types don't
/// implement any interfaces).
/// </remarks>
private static bool IsDisplayableType(Type t)
{
return t.GetInterfaces().Length > 0;
}
}
}