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