1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.vfs.provider;
18
19 import org.apache.commons.vfs.FileName;
20 import org.apache.commons.vfs.FileSystemException;
21 import org.apache.commons.vfs.FileType;
22 import org.apache.commons.vfs.VFS;
23
24 /***
25 * Utilities for dealing with URIs. See RFC 2396 for details.
26 *
27 * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
28 * @version $Revision: 480428 $ $Date: 2005-10-13 21:11:33 +0200 (Do, 13 Okt
29 * 2005) $
30 */
31 public final class UriParser
32 {
33 /***
34 * The normalised separator to use.
35 */
36 private static final char SEPARATOR_CHAR = FileName.SEPARATOR_CHAR;
37
38 /***
39 * The set of valid separators. These are all converted to the normalised
40 * one. Does <i>not</i> contain the normalised separator
41 */
42
43 public static final char TRANS_SEPARATOR = '//';
44
45 private UriParser()
46 {
47 }
48
49 /***
50 * Extracts the first element of a path.
51 */
52 public static String extractFirstElement(final StringBuffer name)
53 {
54 final int len = name.length();
55 if (len < 1)
56 {
57 return null;
58 }
59 int startPos = 0;
60 if (name.charAt(0) == SEPARATOR_CHAR)
61 {
62 startPos = 1;
63 }
64 for (int pos = startPos; pos < len; pos++)
65 {
66 if (name.charAt(pos) == SEPARATOR_CHAR)
67 {
68
69 final String elem = name.substring(startPos, pos);
70 name.delete(startPos, pos + 1);
71 return elem;
72 }
73 }
74
75
76 final String elem = name.substring(startPos);
77 name.setLength(0);
78 return elem;
79 }
80
81 /***
82 * Normalises a path. Does the following:
83 * <ul>
84 * <li>Removes empty path elements.
85 * <li>Handles '.' and '..' elements.
86 * <li>Removes trailing separator.
87 * </ul>
88 *
89 * Its assumed that the separators are already fixed.
90 *
91 * @see #fixSeparators
92 */
93 public static FileType normalisePath(final StringBuffer path)
94 throws FileSystemException
95 {
96 FileType fileType = FileType.FOLDER;
97 if (path.length() == 0)
98 {
99 return fileType;
100 }
101
102 if (path.charAt(path.length() - 1) != '/')
103 {
104 fileType = FileType.FILE;
105 }
106
107
108
109
110
111 int startFirstElem = 0;
112 if (path.charAt(0) == SEPARATOR_CHAR)
113 {
114 if (path.length() == 1)
115 {
116 return fileType;
117 }
118 startFirstElem = 1;
119 }
120
121
122 int startElem = startFirstElem;
123 int maxlen = path.length();
124 while (startElem < maxlen)
125 {
126
127 int endElem = startElem;
128 for (; endElem < maxlen && path.charAt(endElem) != SEPARATOR_CHAR; endElem++)
129 {
130 }
131
132 final int elemLen = endElem - startElem;
133 if (elemLen == 0)
134 {
135
136 path.delete(endElem, endElem + 1);
137 maxlen = path.length();
138 continue;
139 }
140 if (elemLen == 1 && path.charAt(startElem) == '.')
141 {
142
143 path.delete(startElem, endElem + 1);
144 maxlen = path.length();
145 continue;
146 }
147 if (elemLen == 2 && path.charAt(startElem) == '.'
148 && path.charAt(startElem + 1) == '.')
149 {
150
151 if (startElem == startFirstElem)
152 {
153
154 throw new FileSystemException(
155 "vfs.provider/invalid-relative-path.error");
156 }
157
158
159 int pos = startElem - 2;
160 for (; pos >= 0 && path.charAt(pos) != SEPARATOR_CHAR; pos--)
161 {
162 }
163 startElem = pos + 1;
164
165 path.delete(startElem, endElem + 1);
166 maxlen = path.length();
167 continue;
168 }
169
170
171 startElem = endElem + 1;
172 }
173
174
175 if (!VFS.isUriStyle())
176 {
177 if (maxlen > 0 && path.charAt(maxlen - 1) == SEPARATOR_CHAR
178 && maxlen > 1)
179 {
180 path.delete(maxlen - 1, maxlen);
181 }
182 }
183
184 return fileType;
185 }
186
187 /***
188 * Normalises the separators in a name.
189 */
190 public static boolean fixSeparators(final StringBuffer name)
191 {
192 boolean changed = false;
193 final int maxlen = name.length();
194 for (int i = 0; i < maxlen; i++)
195 {
196 final char ch = name.charAt(i);
197 if (ch == TRANS_SEPARATOR)
198 {
199 name.setCharAt(i, SEPARATOR_CHAR);
200 changed = true;
201 }
202 }
203 return changed;
204 }
205
206 /***
207 * Extracts the scheme from a URI.
208 *
209 * @param uri
210 * The URI.
211 * @return The scheme name. Returns null if there is no scheme.
212 */
213 public static String extractScheme(final String uri)
214 {
215 return extractScheme(uri, null);
216 }
217
218 /***
219 * Extracts the scheme from a URI. Removes the scheme and ':' delimiter from
220 * the front of the URI.
221 *
222 * @param uri
223 * The URI.
224 * @param buffer
225 * Returns the remainder of the URI.
226 * @return The scheme name. Returns null if there is no scheme.
227 */
228 public static String extractScheme(final String uri,
229 final StringBuffer buffer)
230 {
231 if (buffer != null)
232 {
233 buffer.setLength(0);
234 buffer.append(uri);
235 }
236
237 final int maxPos = uri.length();
238 for (int pos = 0; pos < maxPos; pos++)
239 {
240 final char ch = uri.charAt(pos);
241
242 if (ch == ':')
243 {
244
245 final String scheme = uri.substring(0, pos);
246 if (buffer != null)
247 {
248 buffer.delete(0, pos + 1);
249 }
250 return scheme.intern();
251 }
252
253 if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
254 {
255
256 continue;
257 }
258 if (pos > 0
259 && ((ch >= '0' && ch <= '9') || ch == '+' || ch == '-' || ch == '.'))
260 {
261
262
263
264 continue;
265 }
266
267
268 break;
269 }
270
271
272 return null;
273 }
274
275 /***
276 * Removes %nn encodings from a string.
277 */
278 public static String decode(final String encodedStr)
279 throws FileSystemException
280 {
281 if (encodedStr == null)
282 {
283 return null;
284 }
285 if (encodedStr.indexOf('%') < 0)
286 {
287 return encodedStr;
288 }
289 final StringBuffer buffer = new StringBuffer(encodedStr);
290 decode(buffer, 0, buffer.length());
291 return buffer.toString();
292 }
293
294 /***
295 * Removes %nn encodings from a string.
296 */
297 public static void decode(final StringBuffer buffer, final int offset,
298 final int length) throws FileSystemException
299 {
300 int index = offset;
301 int count = length;
302 for (; count > 0; count--, index++)
303 {
304 final char ch = buffer.charAt(index);
305 if (ch != '%')
306 {
307 continue;
308 }
309 if (count < 3)
310 {
311 throw new FileSystemException(
312 "vfs.provider/invalid-escape-sequence.error", buffer
313 .substring(index, index + count));
314 }
315
316
317 int dig1 = Character.digit(buffer.charAt(index + 1), 16);
318 int dig2 = Character.digit(buffer.charAt(index + 2), 16);
319 if (dig1 == -1 || dig2 == -1)
320 {
321 throw new FileSystemException(
322 "vfs.provider/invalid-escape-sequence.error", buffer
323 .substring(index, index + 3));
324 }
325 char value = (char) (dig1 << 4 | dig2);
326
327
328 buffer.setCharAt(index, value);
329 buffer.delete(index + 1, index + 3);
330 count -= 2;
331 }
332 }
333
334 /***
335 * Encodes and appends a string to a StringBuffer.
336 */
337 public static void appendEncoded(final StringBuffer buffer,
338 final String unencodedValue, final char[] reserved)
339 {
340 final int offset = buffer.length();
341 buffer.append(unencodedValue);
342 encode(buffer, offset, unencodedValue.length(), reserved);
343 }
344
345 /***
346 * Encodes a set of reserved characters in a StringBuffer, using the URI %nn
347 * encoding. Always encodes % characters.
348 */
349 public static void encode(final StringBuffer buffer, final int offset,
350 final int length, final char[] reserved)
351 {
352 int index = offset;
353 int count = length;
354 for (; count > 0; index++, count--)
355 {
356 final char ch = buffer.charAt(index);
357 boolean match = (ch == '%');
358 if (reserved != null)
359 {
360 for (int i = 0; !match && i < reserved.length; i++)
361 {
362 if (ch == reserved[i])
363 {
364 match = true;
365 }
366 }
367 }
368 if (match)
369 {
370
371 char[] digits =
372 { Character.forDigit(((ch >> 4) & 0xF), 16),
373 Character.forDigit((ch & 0xF), 16) };
374 buffer.setCharAt(index, '%');
375 buffer.insert(index + 1, digits);
376 index += 2;
377 }
378 }
379 }
380
381 /***
382 * Removes %nn encodings from a string.
383 */
384 public static String encode(final String decodedStr)
385 {
386 return encode(decodedStr, null);
387 }
388
389 public static String encode(final String decodedStr, final char[] reserved)
390 {
391 if (decodedStr == null)
392 {
393 return null;
394 }
395 final StringBuffer buffer = new StringBuffer(decodedStr);
396 encode(buffer, 0, buffer.length(), reserved);
397 return buffer.toString();
398 }
399
400 public static String[] encode(String[] strings)
401 {
402 if (strings == null)
403 {
404 return null;
405 }
406 for (int i = 0; i < strings.length; i++)
407 {
408 strings[i] = encode(strings[i]);
409 }
410 return strings;
411 }
412
413 public static void checkUriEncoding(String uri) throws FileSystemException
414 {
415 decode(uri);
416 }
417
418 public static void canonicalizePath(StringBuffer buffer, int offset,
419 int length, FileNameParser fileNameParser)
420 throws FileSystemException
421 {
422 int index = offset;
423 int count = length;
424 for (; count > 0; count--, index++)
425 {
426 final char ch = buffer.charAt(index);
427 if (ch == '%')
428 {
429 if (count < 3)
430 {
431 throw new FileSystemException(
432 "vfs.provider/invalid-escape-sequence.error",
433 buffer.substring(index, index + count));
434 }
435
436
437 int dig1 = Character.digit(buffer.charAt(index + 1), 16);
438 int dig2 = Character.digit(buffer.charAt(index + 2), 16);
439 if (dig1 == -1 || dig2 == -1)
440 {
441 throw new FileSystemException(
442 "vfs.provider/invalid-escape-sequence.error",
443 buffer.substring(index, index + 3));
444 }
445 char value = (char) (dig1 << 4 | dig2);
446
447 boolean match = (value == '%')
448 || (fileNameParser != null && fileNameParser
449 .encodeCharacter(value));
450
451 if (match)
452 {
453
454 index += 2;
455 count -= 2;
456 continue;
457 }
458
459
460 buffer.setCharAt(index, value);
461 buffer.delete(index + 1, index + 3);
462 count -= 2;
463 }
464 else if (fileNameParser.encodeCharacter(ch))
465 {
466
467 char[] digits =
468 { Character.forDigit(((ch >> 4) & 0xF), 16),
469 Character.forDigit((ch & 0xF), 16) };
470 buffer.setCharAt(index, '%');
471 buffer.insert(index + 1, digits);
472 index += 2;
473 }
474 }
475 }
476
477 public static String extractQueryString(StringBuffer name)
478 {
479 for (int pos = 0; pos < name.length(); pos++)
480 {
481 if (name.charAt(pos) == '?')
482 {
483 String queryString = name.substring(pos + 1);
484 name.delete(pos, name.length());
485 return queryString;
486 }
487 }
488
489 return null;
490 }
491 }