-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
IOHelper.cs
335 lines (284 loc) · 13.6 KB
/
IOHelper.cs
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
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.IO;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Hosting;
using System.IO.Compression;
namespace Umbraco.Core.IO
{
public static class IOHelper
{
/// <summary>
/// Gets or sets a value forcing Umbraco to consider it is non-hosted.
/// </summary>
/// <remarks>This should always be false, unless unit testing.</remarks>
public static bool ForceNotHosted { get; set; }
private static string _rootDir = "";
// static compiled regex for faster performance
//private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
/// <summary>
/// Gets a value indicating whether Umbraco is hosted.
/// </summary>
public static bool IsHosted => !ForceNotHosted && (HttpContext.Current != null || HostingEnvironment.IsHosted);
public static char DirSepChar => Path.DirectorySeparatorChar;
//helper to try and match the old path to a new virtual one
public static string FindFile(string virtualPath)
{
string retval = virtualPath;
if (virtualPath.StartsWith("~"))
retval = virtualPath.Replace("~", SystemDirectories.Root);
if (virtualPath.StartsWith("/") && virtualPath.StartsWith(SystemDirectories.Root) == false)
retval = SystemDirectories.Root + "/" + virtualPath.TrimStart(Constants.CharArrays.ForwardSlash);
return retval;
}
public static string ResolveVirtualUrl(string path)
{
if (string.IsNullOrWhiteSpace(path)) return path;
return path.StartsWith("~/") ? ResolveUrl(path) : path;
}
//Replaces tildes with the root dir
public static string ResolveUrl(string virtualPath)
{
if (virtualPath.StartsWith("~"))
return virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/");
else if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute))
return virtualPath;
else
return VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root);
}
public static Attempt<string> TryResolveUrl(string virtualPath)
{
try
{
if (virtualPath.StartsWith("~"))
return Attempt.Succeed(virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/"));
if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute))
return Attempt.Succeed(virtualPath);
return Attempt.Succeed(VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root));
}
catch (Exception ex)
{
return Attempt.Fail(virtualPath, ex);
}
}
public static string MapPath(string path, bool useHttpContext)
{
if (path == null) throw new ArgumentNullException("path");
useHttpContext = useHttpContext && IsHosted;
// Check if the path is already mapped
if ((path.Length >= 2 && path[1] == Path.VolumeSeparatorChar)
|| path.StartsWith(@"\\")) //UNC Paths start with "\\". If the site is running off a network drive mapped paths will look like "\\Whatever\Boo\Bar"
{
return path;
}
// Check that we even have an HttpContext! otherwise things will fail anyways
// http://umbraco.codeplex.com/workitem/30946
if (useHttpContext && HttpContext.Current != null)
{
//string retval;
if (String.IsNullOrEmpty(path) == false && (path.StartsWith("~") || path.StartsWith(SystemDirectories.Root)))
return HostingEnvironment.MapPath(path);
else
return HostingEnvironment.MapPath("~/" + path.TrimStart(Constants.CharArrays.ForwardSlash));
}
var root = GetRootDirectorySafe();
var newPath = path.TrimStart(Constants.CharArrays.TildeForwardSlash).Replace('/', IOHelper.DirSepChar);
var retval = root + IOHelper.DirSepChar.ToString(CultureInfo.InvariantCulture) + newPath;
return retval;
}
public static string MapPath(string path)
{
return MapPath(path, true);
}
//use a tilde character instead of the complete path
internal static string ReturnPath(string settingsKey, string standardPath, bool useTilde)
{
var retval = ConfigurationManager.AppSettings[settingsKey];
if (string.IsNullOrEmpty(retval))
retval = standardPath;
return retval.TrimEnd(Constants.CharArrays.ForwardSlash);
}
internal static string ReturnPath(string settingsKey, string standardPath)
{
return ReturnPath(settingsKey, standardPath, false);
}
/// <summary>
/// Verifies that the current filepath matches a directory where the user is allowed to edit a file.
/// </summary>
/// <param name="filePath">The filepath to validate.</param>
/// <param name="validDir">The valid directory.</param>
/// <returns>A value indicating whether the filepath is valid.</returns>
internal static bool VerifyEditPath(string filePath, string validDir)
{
return VerifyEditPath(filePath, new[] { validDir });
}
/// <summary>
/// Verifies that the current filepath matches one of several directories where the user is allowed to edit a file.
/// </summary>
/// <param name="filePath">The filepath to validate.</param>
/// <param name="validDirs">The valid directories.</param>
/// <returns>A value indicating whether the filepath is valid.</returns>
internal static bool VerifyEditPath(string filePath, IEnumerable<string> validDirs)
{
// this is called from ScriptRepository, PartialViewRepository, etc.
// filePath is the fullPath (rooted, filesystem path, can be trusted)
// validDirs are virtual paths (eg ~/Views)
//
// except that for templates, filePath actually is a virtual path
// TODO: what's below is dirty, there are too many ways to get the root dir, etc.
// not going to fix everything today
var mappedRoot = MapPath(SystemDirectories.Root);
if (filePath.StartsWith(mappedRoot) == false)
filePath = MapPath(filePath);
// yes we can (see above)
//// don't trust what we get, it may contain relative segments
//filePath = Path.GetFullPath(filePath);
foreach (var dir in validDirs)
{
var validDir = dir;
if (validDir.StartsWith(mappedRoot) == false)
validDir = MapPath(validDir);
if (PathStartsWith(filePath, validDir, Path.DirectorySeparatorChar))
return true;
}
return false;
}
/// <summary>
/// Verifies that the current filepath has one of several authorized extensions.
/// </summary>
/// <param name="filePath">The filepath to validate.</param>
/// <param name="validFileExtensions">The valid extensions.</param>
/// <returns>A value indicating whether the filepath is valid.</returns>
internal static bool VerifyFileExtension(string filePath, IEnumerable<string> validFileExtensions)
{
var ext = Path.GetExtension(filePath);
return ext != null && validFileExtensions.Contains(ext.TrimStart(Constants.CharArrays.Period));
}
public static bool PathStartsWith(string path, string root, char separator)
{
// either it is identical to root,
// or it is root + separator + anything
if (path.StartsWith(root, StringComparison.OrdinalIgnoreCase) == false) return false;
if (path.Length == root.Length) return true;
if (path.Length < root.Length) return false;
return path[root.Length] == separator;
}
/// <summary>
/// Returns the path to the root of the application, by getting the path to where the assembly where this
/// method is included is present, then traversing until it's past the /bin directory. Ie. this makes it work
/// even if the assembly is in a /bin/debug or /bin/release folder
/// </summary>
/// <returns></returns>
internal static string GetRootDirectorySafe()
{
if (String.IsNullOrEmpty(_rootDir) == false)
{
return _rootDir;
}
var codeBase = Assembly.GetExecutingAssembly().CodeBase;
var uri = new Uri(codeBase);
var path = uri.LocalPath;
var baseDirectory = Path.GetDirectoryName(path);
if (String.IsNullOrEmpty(baseDirectory))
throw new Exception("No root directory could be resolved. Please ensure that your Umbraco solution is correctly configured.");
_rootDir = baseDirectory.Contains("bin")
? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1)
: baseDirectory;
return _rootDir;
}
internal static string GetRootDirectoryBinFolder()
{
string binFolder = String.Empty;
if (String.IsNullOrEmpty(_rootDir))
{
binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory.FullName;
return binFolder;
}
binFolder = Path.Combine(GetRootDirectorySafe(), "bin");
// do this all the time (no #if DEBUG) because Umbraco release
// can be used in tests by an app (eg Deploy) being debugged
var debugFolder = Path.Combine(binFolder, "debug");
if (Directory.Exists(debugFolder))
return debugFolder;
var releaseFolder = Path.Combine(binFolder, "release");
if (Directory.Exists(releaseFolder))
return releaseFolder;
if (Directory.Exists(binFolder))
return binFolder;
return _rootDir;
}
/// <summary>
/// Allows you to overwrite RootDirectory, which would otherwise be resolved
/// automatically upon application start.
/// </summary>
/// <remarks>The supplied path should be the absolute path to the root of the umbraco site.</remarks>
/// <param name="rootPath"></param>
internal static void SetRootDirectory(string rootPath)
{
_rootDir = rootPath;
}
/// <summary>
/// Check to see if filename passed has any special chars in it and strips them to create a safe filename. Used to overcome an issue when Umbraco is used in IE in an intranet environment.
/// </summary>
/// <param name="filePath">The filename passed to the file handler from the upload field.</param>
/// <returns>A safe filename without any path specific chars.</returns>
internal static string SafeFileName(string filePath)
{
// use string extensions
return filePath.ToSafeFileName();
}
public static void EnsurePathExists(string path)
{
var absolutePath = MapPath(path);
if (Directory.Exists(absolutePath) == false)
Directory.CreateDirectory(absolutePath);
}
/// <summary>
/// Checks if a given path is a full path including drive letter
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
// From: http://stackoverflow.com/a/35046453/5018
internal static bool IsFullPath(this string path)
{
return string.IsNullOrWhiteSpace(path) == false
&& path.IndexOfAny(Path.GetInvalidPathChars().ToArray()) == -1
&& Path.IsPathRooted(path)
&& Path.GetPathRoot(path).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false;
}
/// <summary>
/// Get properly formatted relative path from an existing absolute or relative path
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
internal static string GetRelativePath(this string path)
{
if (path.IsFullPath())
{
var rootDirectory = GetRootDirectorySafe();
var relativePath = path.ToLowerInvariant().Replace(rootDirectory.ToLowerInvariant(), string.Empty);
path = relativePath;
}
return path.EnsurePathIsApplicationRootPrefixed();
}
/// <summary>
/// Ensures that a path has `~/` as prefix
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
internal static string EnsurePathIsApplicationRootPrefixed(this string path)
{
if (path.StartsWith("~/"))
return path;
if (path.StartsWith("/") == false && path.StartsWith("\\") == false)
path = string.Format("/{0}", path);
if (path.StartsWith("~") == false)
path = string.Format("~{0}", path);
return path;
}
}
}