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