1 //
2 // System.Web.UrlUtils.cs
3 //
4 // Authors:
5 //	Gonzalo Paniagua (gonzalo@ximian.com)
6 //      Jackson Harper   (jackson@ximian.com)
7 //
8 
9 //
10 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 //
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 //
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31 
32 using System.Web.SessionState;
33 using System.Text;
34 namespace System.Web.Util {
35 
36 #if VISUAL_STUDIO
37 	public
38 #else
39 	internal
40 #endif
41 	class UrlUtils {
42 
43 		// appRoot + SessionID + vpath
InsertSessionId(string id, string path)44 		public static string InsertSessionId (string id, string path)
45 		{
46 			string dir = GetDirectory (path);
47 			if (!dir.EndsWith ("/"))
48 				dir += "/";
49 
50 			string appvpath = HttpRuntime.AppDomainAppVirtualPath;
51 			if (!appvpath.EndsWith ("/"))
52 				appvpath += "/";
53 
54 			if (path.StartsWith (appvpath))
55 				path = path.Substring (appvpath.Length);
56 
57 			if (path.StartsWith("/"))
58 				path = path.Length > 1 ? path.Substring (1) : "";
59 
60 			return Canonic (appvpath + "(" + id + ")/" + path);
61 		}
62 
GetSessionId(string path)63 		public static string GetSessionId (string path)
64 		{
65 #if TARGET_DOTNET
66 			return null;
67 #else
68 			if (path == null)
69 				return null;
70 
71 			string appvpath = HttpRuntime.AppDomainAppVirtualPath;
72 			int appvpathlen = appvpath.Length;
73 
74 			if (path.Length <= appvpathlen)
75 				return null;
76 
77 			path = path.Substring (appvpathlen);
78 
79 			int len = path.Length;
80 			if (len == 0 || path [0] != '/') {
81 				path = '/' + path;
82 				len++;
83 			}
84 
85 			if ((len < SessionId.IdLength + 3) || (path [1] != '(') ||
86 			    (path [SessionId.IdLength + 2] != ')'))
87 				return null;
88 
89 			return path.Substring (2, SessionId.IdLength);
90 #endif
91 		}
92 
HasSessionId(string path)93 		public static bool HasSessionId (string path)
94 		{
95 			if (path == null || path.Length < 5)
96 				return false;
97 
98 			return (StrUtils.StartsWith (path, "/(") && path.IndexOf (")/") > 2);
99 		}
100 
RemoveSessionId(string base_path, string file_path)101 		public static string RemoveSessionId (string base_path, string file_path)
102 		{
103 			// Caller did a GetSessionId first
104 			int idx = base_path.IndexOf ("/(");
105 			string dir = base_path.Substring (0, idx + 1);
106 			if (!dir.EndsWith ("/"))
107 				dir += "/";
108 
109 			idx = base_path.IndexOf (")/");
110 			if (idx != -1 && base_path.Length > idx + 2) {
111 				string dir2 = base_path.Substring (idx + 2);
112 				if (!dir2.EndsWith ("/"))
113 					dir2 += "/";
114 
115 				dir += dir2;
116 			}
117 
118 			return Canonic (dir + GetFile (file_path));
119 		}
120 
Combine(string basePath, string relPath)121 		public static string Combine (string basePath, string relPath)
122 		{
123 			if (relPath == null)
124 				throw new ArgumentNullException ("relPath");
125 
126 			int rlength = relPath.Length;
127 			if (rlength == 0)
128 				return "";
129 
130 			relPath = relPath.Replace ('\\', '/');
131 			if (IsRooted (relPath))
132 				return Canonic (relPath);
133 
134 			char first = relPath [0];
135 			if (rlength < 3 || first == '~' || first == '/' || first == '\\') {
136 				if (basePath == null || (basePath.Length == 1 && basePath [0] == '/'))
137 					basePath = String.Empty;
138 
139 				string slash = (first == '/') ? "" : "/";
140 				if (first == '~') {
141 					if (rlength == 1) {
142 						relPath = "";
143 					} else if (rlength > 1 && relPath [1] == '/') {
144 						relPath = relPath.Substring (2);
145 						slash = "/";
146 					}
147 
148 					string appvpath = HttpRuntime.AppDomainAppVirtualPath;
149 					if (appvpath.EndsWith ("/"))
150 						slash = "";
151 
152 					return Canonic (appvpath + slash + relPath);
153 				}
154 
155 				return Canonic (basePath + slash + relPath);
156 			}
157 
158 			if (basePath == null || basePath.Length == 0 || basePath [0] == '~')
159 				basePath = HttpRuntime.AppDomainAppVirtualPath;
160 
161 			if (basePath.Length <= 1)
162 				basePath = String.Empty;
163 
164 			return Canonic (basePath + "/" + relPath);
165 		}
166 
167 		static char [] path_sep = {'\\', '/'};
168 
Canonic(string path)169 		public static string Canonic (string path)
170 		{
171 			bool isRooted = IsRooted(path);
172 			bool endsWithSlash = path.EndsWith("/");
173 			string [] parts = path.Split (path_sep);
174 			int end = parts.Length;
175 
176 			int dest = 0;
177 
178 			for (int i = 0; i < end; i++) {
179 				string current = parts [i];
180 
181 				if (current.Length == 0)
182 					continue;
183 
184 				if (current == "." )
185 					continue;
186 
187 				if (current == "..") {
188 					dest --;
189 					continue;
190 				}
191 				if (dest < 0)
192 					if (!isRooted)
193 						throw new HttpException ("Invalid path.");
194 					else
195 						dest = 0;
196 
197 				parts [dest++] = current;
198 			}
199 			if (dest < 0)
200 				throw new HttpException ("Invalid path.");
201 
202 			if (dest == 0)
203 				return "/";
204 
205 			string str = String.Join ("/", parts, 0, dest);
206 			str = RemoveDoubleSlashes (str);
207 			if (isRooted)
208 				str = "/" + str;
209 			if (endsWithSlash)
210 				str = str + "/";
211 
212 			return str;
213 		}
214 
GetDirectory(string url)215 		public static string GetDirectory (string url)
216 		{
217 			url = url.Replace('\\','/');
218 			int last = url.LastIndexOf ('/');
219 
220 			if (last > 0) {
221 				if (last < url.Length)
222 					last++;
223 				return RemoveDoubleSlashes (url.Substring (0, last));
224 			}
225 
226 			return "/";
227 		}
228 
RemoveDoubleSlashes(string input)229 		public static string RemoveDoubleSlashes (string input)
230 		{
231 			// MS VirtualPathUtility removes duplicate '/'
232 
233 			int index = -1;
234 			for (int i = 1; i < input.Length; i++)
235 				if (input [i] == '/' && input [i - 1] == '/') {
236 					index = i - 1;
237 					break;
238 				}
239 
240 			if (index == -1) // common case optimization
241 				return input;
242 
243 			StringBuilder sb = new StringBuilder (input.Length);
244 			sb.Append (input, 0, index);
245 
246 			for (int i = index; i < input.Length; i++) {
247 				if (input [i] == '/') {
248 					int next = i + 1;
249 					if (next < input.Length && input [next] == '/')
250 						continue;
251 					sb.Append ('/');
252 				}
253 				else {
254 					sb.Append (input [i]);
255 				}
256 			}
257 
258 			return sb.ToString ();
259 		}
260 
GetFile(string url)261 		public static string GetFile (string url)
262 		{
263 			url = url.Replace('\\','/');
264 			int last = url.LastIndexOf ('/');
265 			if (last >= 0) {
266 				if (url.Length == 1) // Empty file name instead of ArgumentOutOfRange
267 					return "";
268 				return url.Substring (last+1);
269 			}
270 
271 			throw new ArgumentException (String.Format ("GetFile: `{0}' does not contain a /", url));
272 		}
273 
IsRooted(string path)274 		public static bool IsRooted (string path)
275 		{
276 			if (path == null || path.Length == 0)
277 				return true;
278 
279 			char c = path [0];
280 			if (c == '/' || c == '\\')
281 				return true;
282 
283 			return false;
284 		}
285 
IsRelativeUrl(string path)286 		public static bool IsRelativeUrl (string path)
287 		{
288 			return (path [0] != '/' && path.IndexOf (':') == -1);
289 		}
290 
ResolveVirtualPathFromAppAbsolute(string path)291                 public static string ResolveVirtualPathFromAppAbsolute (string path)
292                 {
293                         if (path [0] != '~') return path;
294 
295                         if (path.Length == 1)
296                                 return HttpRuntime.AppDomainAppVirtualPath;
297 
298                         if (path [1] == '/' || path [1] == '\\') {
299                                 string appPath = HttpRuntime.AppDomainAppVirtualPath;
300                                 if (appPath.Length > 1)
301                                         return appPath + "/" + path.Substring (2);
302                                 return "/" + path.Substring (2);
303                         }
304                         return path;
305                 }
306 
ResolvePhysicalPathFromAppAbsolute(string path)307                 public static string ResolvePhysicalPathFromAppAbsolute (string path)
308                 {
309                         if (path [0] != '~') return path;
310 
311                         if (path.Length == 1)
312                                 return HttpRuntime.AppDomainAppPath;
313 
314                         if (path [1] == '/' || path [1] == '\\') {
315                                 string appPath = HttpRuntime.AppDomainAppPath;
316                                 if (appPath.Length > 1)
317                                         return appPath + "/" + path.Substring (2);
318                                 return "/" + path.Substring (2);
319                         }
320                         return path;
321                 }
322 	}
323 }
324