VirtualPathUtility.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. //
  2. // System.Web.VirtualPathUtility.cs
  3. //
  4. // Author:
  5. // Chris Toshok ([email protected])
  6. // Gonzalo Paniagua Javier ([email protected])
  7. //
  8. //
  9. // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
  10. //
  11. // Permission is hereby granted, free of charge, to any person obtaining
  12. // a copy of this software and associated documentation files (the
  13. // "Software"), to deal in the Software without restriction, including
  14. // without limitation the rights to use, copy, modify, merge, publish,
  15. // distribute, sublicense, and/or sell copies of the Software, and to
  16. // permit persons to whom the Software is furnished to do so, subject to
  17. // the following conditions:
  18. //
  19. // The above copyright notice and this permission notice shall be
  20. // included in all copies or substantial portions of the Software.
  21. //
  22. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  23. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  24. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  25. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  26. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  27. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  28. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29. //
  30. using System.Collections.Specialized;
  31. using System.Web.Configuration;
  32. using System.Web.Util;
  33. using System.Text;
  34. using Microsoft.Win32;
  35. namespace System.Web {
  36. public static class VirtualPathUtility
  37. {
  38. static bool monoSettingsVerifyCompatibility;
  39. static bool runningOnWindows;
  40. static VirtualPathUtility ()
  41. {
  42. try {
  43. runningOnWindows = HttpRuntime.RunningOnWindows;
  44. var monoSettings = WebConfigurationManager.GetWebApplicationSection ("system.web/monoSettings") as MonoSettingsSection;
  45. if (monoSettings != null)
  46. monoSettingsVerifyCompatibility = monoSettings.VerificationCompatibility != 1;
  47. } catch {
  48. // ignore
  49. }
  50. }
  51. public static string AppendTrailingSlash (string virtualPath)
  52. {
  53. if (virtualPath == null)
  54. return virtualPath;
  55. int length = virtualPath.Length;
  56. if (length == 0 || virtualPath [length - 1] == '/')
  57. return virtualPath;
  58. return virtualPath + "/";
  59. }
  60. public static string Combine (string basePath, string relativePath)
  61. {
  62. basePath = Normalize (basePath);
  63. if (IsRooted (relativePath))
  64. return Normalize (relativePath);
  65. int basePathLen = basePath.Length;
  66. if (basePath [basePathLen - 1] != '/') {
  67. if (basePathLen > 1) {
  68. int lastSlash = basePath.LastIndexOf ('/');
  69. if (lastSlash >= 0)
  70. basePath = basePath.Substring (0, lastSlash + 1);
  71. } else { // "~" only
  72. basePath += "/";
  73. }
  74. }
  75. return Normalize (basePath + relativePath);
  76. }
  77. public static string GetDirectory (string virtualPath)
  78. {
  79. return GetDirectory (virtualPath, true);
  80. }
  81. internal static string GetDirectory (string virtualPath, bool normalize)
  82. {
  83. if (normalize)
  84. virtualPath = Normalize (virtualPath);
  85. if (IsAppRelative (virtualPath) && virtualPath.Length < 3) { // "~" or "~/"
  86. virtualPath = ToAbsolute (virtualPath);
  87. }
  88. if (virtualPath.Length == 1 && virtualPath [0] == '/') { // "/"
  89. return null;
  90. }
  91. int last = virtualPath.LastIndexOf ('/', virtualPath.Length - 2, virtualPath.Length - 2);
  92. if (last > 0)
  93. return virtualPath.Substring (0, last + 1);
  94. else
  95. return "/";
  96. }
  97. public static string GetExtension (string virtualPath)
  98. {
  99. if (StrUtils.IsNullOrEmpty (virtualPath))
  100. throw new ArgumentNullException ("virtualPath");
  101. virtualPath = Canonize (virtualPath);
  102. int dot = virtualPath.LastIndexOf ('.');
  103. if (dot == -1 || dot == virtualPath.Length - 1 || dot < virtualPath.LastIndexOf ('/'))
  104. return String.Empty;
  105. return virtualPath.Substring (dot);
  106. }
  107. public static string GetFileName (string virtualPath)
  108. {
  109. virtualPath = Normalize (virtualPath);
  110. if (IsAppRelative (virtualPath) && virtualPath.Length < 3) { // "~" or "~/"
  111. virtualPath = ToAbsolute (virtualPath);
  112. }
  113. if (virtualPath.Length == 1 && virtualPath [0] == '/') { // "/"
  114. return String.Empty;
  115. }
  116. virtualPath = RemoveTrailingSlash (virtualPath);
  117. int last = virtualPath.LastIndexOf ('/');
  118. return virtualPath.Substring (last + 1);
  119. }
  120. internal static bool IsRooted (string virtualPath)
  121. {
  122. return IsAbsolute (virtualPath) || IsAppRelative (virtualPath);
  123. }
  124. public static bool IsAbsolute (string virtualPath)
  125. {
  126. if (StrUtils.IsNullOrEmpty (virtualPath))
  127. throw new ArgumentNullException ("virtualPath");
  128. return (virtualPath [0] == '/' || virtualPath [0] == '\\');
  129. }
  130. public static bool IsAppRelative (string virtualPath)
  131. {
  132. if (StrUtils.IsNullOrEmpty (virtualPath))
  133. throw new ArgumentNullException ("virtualPath");
  134. if (virtualPath.Length == 1 && virtualPath [0] == '~')
  135. return true;
  136. if (virtualPath [0] == '~' && (virtualPath [1] == '/' || virtualPath [1] == '\\'))
  137. return true;
  138. return false;
  139. }
  140. // MSDN: If the fromPath and toPath parameters are not rooted; that is,
  141. // they do not equal the root operator (the tilde [~]), do not start with a tilde (~),
  142. // such as a tilde and a slash mark (~/) or a tilde and a double backslash (~//),
  143. // or do not start with a slash mark (/), an ArgumentException exception is thrown.
  144. public static string MakeRelative (string fromPath, string toPath)
  145. {
  146. if (fromPath == null || toPath == null)
  147. throw new NullReferenceException (); // yeah!
  148. if (toPath == "")
  149. return toPath;
  150. toPath = ToAbsoluteInternal (toPath);
  151. fromPath = ToAbsoluteInternal (fromPath);
  152. if (String.CompareOrdinal (fromPath, toPath) == 0 && fromPath [fromPath.Length - 1] == '/')
  153. return "./";
  154. string [] toPath_parts = toPath.Split ('/');
  155. string [] fromPath_parts = fromPath.Split ('/');
  156. int dest = 1;
  157. while (toPath_parts [dest] == fromPath_parts [dest]) {
  158. if (toPath_parts.Length == (dest + 1) || fromPath_parts.Length == (dest + 1)) {
  159. break;
  160. }
  161. dest++;
  162. }
  163. StringBuilder res = new StringBuilder();
  164. for (int i = 1; i < fromPath_parts.Length - dest; i++) {
  165. res.Append ("../");
  166. }
  167. for (int i = dest; i < toPath_parts.Length; i++) {
  168. res.Append (toPath_parts [i]);
  169. if (i < toPath_parts.Length - 1)
  170. res.Append ('/');
  171. }
  172. return res.ToString ();
  173. }
  174. static string ToAbsoluteInternal (string virtualPath)
  175. {
  176. if (IsAppRelative (virtualPath))
  177. return ToAbsolute (virtualPath, HttpRuntime.AppDomainAppVirtualPath);
  178. else if (IsAbsolute (virtualPath))
  179. return Normalize (virtualPath);
  180. throw new ArgumentOutOfRangeException ("Specified argument was out of the range of valid values.");
  181. }
  182. public static string RemoveTrailingSlash (string virtualPath)
  183. {
  184. if (virtualPath == null || virtualPath == "")
  185. return null;
  186. int last = virtualPath.Length - 1;
  187. if (last == 0 || virtualPath [last] != '/')
  188. return virtualPath;
  189. return virtualPath.Substring (0, last);
  190. }
  191. public static string ToAbsolute (string virtualPath)
  192. {
  193. return ToAbsolute (virtualPath, true);
  194. }
  195. internal static string ToAbsolute (string virtualPath, bool normalize)
  196. {
  197. if (IsAbsolute (virtualPath)) {
  198. if (normalize)
  199. return Normalize (virtualPath);
  200. else
  201. return virtualPath;
  202. }
  203. string apppath = HttpRuntime.AppDomainAppVirtualPath;
  204. if (apppath == null)
  205. throw new HttpException ("The path to the application is not known");
  206. if (virtualPath.Length == 1 && virtualPath [0] == '~')
  207. return apppath;
  208. return ToAbsolute (virtualPath,apppath);
  209. }
  210. // If virtualPath is:
  211. // Absolute, the ToAbsolute method returns the virtual path with no changes.
  212. // Application relative, the ToAbsolute method adds applicationPath to the beginning of the virtual path.
  213. // Not rooted, the ToAbsolute method raises an ArgumentOutOfRangeException exception.
  214. public static string ToAbsolute (string virtualPath, string applicationPath)
  215. {
  216. return ToAbsolute (virtualPath, applicationPath, true);
  217. }
  218. internal static string ToAbsolute (string virtualPath, string applicationPath, bool normalize)
  219. {
  220. if (StrUtils.IsNullOrEmpty (applicationPath))
  221. throw new ArgumentNullException ("applicationPath");
  222. if (StrUtils.IsNullOrEmpty (virtualPath))
  223. throw new ArgumentNullException ("virtualPath");
  224. if (IsAppRelative(virtualPath)) {
  225. if (applicationPath [0] != '/')
  226. throw new ArgumentException ("appPath is not rooted", "applicationPath");
  227. string path = applicationPath + (virtualPath.Length == 1 ? "/" : virtualPath.Substring (1));
  228. if (normalize)
  229. return Normalize (path);
  230. else
  231. return path;
  232. }
  233. if (virtualPath [0] != '/')
  234. throw new ArgumentException (String.Format ("Relative path not allowed: '{0}'", virtualPath));
  235. if (normalize)
  236. return Normalize (virtualPath);
  237. else
  238. return virtualPath;
  239. }
  240. public static string ToAppRelative (string virtualPath)
  241. {
  242. string apppath = HttpRuntime.AppDomainAppVirtualPath;
  243. if (apppath == null)
  244. throw new HttpException ("The path to the application is not known");
  245. return ToAppRelative (virtualPath, apppath);
  246. }
  247. public static string ToAppRelative (string virtualPath, string applicationPath)
  248. {
  249. virtualPath = Normalize (virtualPath);
  250. if (IsAppRelative (virtualPath))
  251. return virtualPath;
  252. if (!IsAbsolute (applicationPath))
  253. throw new ArgumentException ("appPath is not absolute", "applicationPath");
  254. applicationPath = Normalize (applicationPath);
  255. if (applicationPath.Length == 1)
  256. return "~" + virtualPath;
  257. int appPath_lenght = applicationPath.Length;
  258. if (String.CompareOrdinal (virtualPath, applicationPath) == 0)
  259. return "~/";
  260. if (String.CompareOrdinal (virtualPath, 0, applicationPath, 0, appPath_lenght) == 0)
  261. return "~" + virtualPath.Substring (appPath_lenght);
  262. return virtualPath;
  263. }
  264. static char [] path_sep = { '/' };
  265. static string Normalize (string path)
  266. {
  267. if (!IsRooted (path))
  268. throw new ArgumentException (String.Format ("The relative virtual path '{0}' is not allowed here.", path));
  269. if (path.Length == 1) // '/' or '~'
  270. return path;
  271. path = Canonize (path);
  272. int dotPos = path.IndexOf ('.');
  273. while (dotPos >= 0) {
  274. if (++dotPos == path.Length)
  275. break;
  276. char nextChar = path [dotPos];
  277. if ((nextChar == '/') || (nextChar == '.'))
  278. break;
  279. dotPos = path.IndexOf ('.', dotPos);
  280. }
  281. if (dotPos < 0)
  282. return path;
  283. bool starts_with_tilda = false;
  284. bool ends_with_slash = false;
  285. string [] apppath_parts= null;
  286. if (path [0] == '~') {
  287. if (path.Length == 2) // "~/"
  288. return "~/";
  289. starts_with_tilda = true;
  290. path = path.Substring (1);
  291. }
  292. else if (path.Length == 1) { // "/"
  293. return "/";
  294. }
  295. if (path [path.Length - 1] == '/')
  296. ends_with_slash = true;
  297. string [] parts = StrUtils.SplitRemoveEmptyEntries (path, path_sep);
  298. int end = parts.Length;
  299. int dest = 0;
  300. for (int i = 0; i < end; i++) {
  301. string current = parts [i];
  302. if (current == ".")
  303. continue;
  304. if (current == "..") {
  305. dest--;
  306. if(dest >= 0)
  307. continue;
  308. if (starts_with_tilda) {
  309. if (apppath_parts == null) {
  310. string apppath = HttpRuntime.AppDomainAppVirtualPath;
  311. apppath_parts = StrUtils.SplitRemoveEmptyEntries (apppath, path_sep);
  312. }
  313. if ((apppath_parts.Length + dest) >= 0)
  314. continue;
  315. }
  316. throw new HttpException ("Cannot use a leading .. to exit above the top directory.");
  317. }
  318. if (dest >= 0)
  319. parts [dest] = current;
  320. else
  321. apppath_parts [apppath_parts.Length + dest] = current;
  322. dest++;
  323. }
  324. StringBuilder str = new StringBuilder();
  325. if (apppath_parts != null) {
  326. starts_with_tilda = false;
  327. int count = apppath_parts.Length;
  328. if (dest < 0)
  329. count += dest;
  330. for (int i = 0; i < count; i++) {
  331. str.Append ('/');
  332. str.Append (apppath_parts [i]);
  333. }
  334. }
  335. else if (starts_with_tilda) {
  336. str.Append ('~');
  337. }
  338. for (int i = 0; i < dest; i++) {
  339. str.Append ('/');
  340. str.Append (parts [i]);
  341. }
  342. if (str.Length > 0) {
  343. if (ends_with_slash)
  344. str.Append ('/');
  345. }
  346. else {
  347. return "/";
  348. }
  349. return str.ToString ();
  350. }
  351. internal static string Canonize (string path)
  352. {
  353. int index = -1;
  354. for (int i=0; i < path.Length; i++) {
  355. if ((path [i] == '\\') || (path [i] == '/' && (i + 1) < path.Length && (path [i + 1] == '/' || path [i + 1] == '\\'))) {
  356. index = i;
  357. break;
  358. }
  359. }
  360. if (index < 0)
  361. return path;
  362. StringBuilder sb = new StringBuilder (path.Length);
  363. sb.Append (path, 0, index);
  364. for (int i = index; i < path.Length; i++) {
  365. if (path [i] == '\\' || path [i] == '/') {
  366. int next = i + 1;
  367. if (next < path.Length && (path [next] == '\\' || path [next] == '/'))
  368. continue;
  369. sb.Append ('/');
  370. }
  371. else {
  372. sb.Append (path [i]);
  373. }
  374. }
  375. return sb.ToString ();
  376. }
  377. // See: http://support.microsoft.com/kb/932552
  378. // See: https://bugzilla.novell.com/show_bug.cgi?id=509163
  379. static readonly char[] invalidVirtualPathChars = {':', '*'};
  380. static readonly string aspNetVerificationKey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\ASP.NET";
  381. internal static bool IsValidVirtualPath (string path)
  382. {
  383. if (path == null)
  384. return false;
  385. bool doValidate = true;
  386. if (runningOnWindows) {
  387. try {
  388. object v = Registry.GetValue (aspNetVerificationKey, "VerificationCompatibility", null);
  389. if (v != null && v is int)
  390. doValidate = (int)v != 1;
  391. } catch {
  392. // ignore
  393. }
  394. }
  395. if (doValidate)
  396. doValidate = monoSettingsVerifyCompatibility;
  397. if (!doValidate)
  398. return true;
  399. return path.IndexOfAny (invalidVirtualPathChars) == -1;
  400. }
  401. }
  402. }