|
26 | 26 | import java.io.OutputStream; |
27 | 27 | import java.net.URL; |
28 | 28 | import java.net.URLConnection; |
| 29 | +import java.nio.ByteBuffer; |
29 | 30 | import java.nio.channels.FileChannel; |
| 31 | +import java.nio.charset.Charset; |
30 | 32 | import java.util.ArrayList; |
31 | 33 | import java.util.Collection; |
32 | 34 | import java.util.Date; |
@@ -113,6 +115,11 @@ public FileUtils() { |
113 | 115 | */ |
114 | 116 | public static final File[] EMPTY_FILE_ARRAY = new File[0]; |
115 | 117 |
|
| 118 | + /** |
| 119 | + * The UTF-8 character set, used to decode octets in URLs. |
| 120 | + */ |
| 121 | + private static final Charset UTF8 = Charset.forName("UTF-8"); |
| 122 | + |
116 | 123 | //----------------------------------------------------------------------- |
117 | 124 | /** |
118 | 125 | * Returns the path to the system temporary directory. |
@@ -515,28 +522,70 @@ public static boolean contentEquals(File file1, File file2) throws IOException { |
515 | 522 | * <p> |
516 | 523 | * From version 1.1 this method will decode the URL. |
517 | 524 | * Syntax such as <code>file:///my%20docs/file.txt</code> will be |
518 | | - * correctly decoded to <code>/my docs/file.txt</code>. |
| 525 | + * correctly decoded to <code>/my docs/file.txt</code>. Starting with version |
| 526 | + * 1.5, this method uses UTF-8 to decode percent-encoded octets to characters. |
| 527 | + * Additionally, malformed percent-encoded octets are handled leniently by |
| 528 | + * passing them through literally. |
519 | 529 | * |
520 | 530 | * @param url the file URL to convert, <code>null</code> returns <code>null</code> |
521 | 531 | * @return the equivalent <code>File</code> object, or <code>null</code> |
522 | 532 | * if the URL's protocol is not <code>file</code> |
523 | | - * @throws IllegalArgumentException if the file is incorrectly encoded |
524 | 533 | */ |
525 | 534 | public static File toFile(URL url) { |
526 | | - if (url == null || !url.getProtocol().equals("file")) { |
| 535 | + if (url == null || !"file".equalsIgnoreCase(url.getProtocol())) { |
527 | 536 | return null; |
528 | 537 | } else { |
529 | 538 | String filename = url.getFile().replace('/', File.separatorChar); |
530 | | - int pos =0; |
531 | | - while ((pos = filename.indexOf('%', pos)) >= 0) { |
532 | | - if (pos + 2 < filename.length()) { |
533 | | - String hexStr = filename.substring(pos + 1, pos + 3); |
534 | | - char ch = (char) Integer.parseInt(hexStr, 16); |
535 | | - filename = filename.substring(0, pos) + ch + filename.substring(pos + 3); |
| 539 | + filename = decodeUrl(filename); |
| 540 | + return new File(filename); |
| 541 | + } |
| 542 | + } |
| 543 | + |
| 544 | + /** |
| 545 | + * Decodes the specified URL as per RFC 3986, i.e. transforms |
| 546 | + * percent-encoded octets to characters by decoding with the UTF-8 character |
| 547 | + * set. This function is primarily intended for usage with |
| 548 | + * {@link java.net.URL} which unfortunately does not enforce proper URLs. As |
| 549 | + * such, this method will leniently accept invalid characters or malformed |
| 550 | + * percent-encoded octets and simply pass them literally through to the |
| 551 | + * result string. Except for rare edge cases, this will make unencoded URLs |
| 552 | + * pass through unaltered. |
| 553 | + * |
| 554 | + * @param url The URL to decode, may be <code>null</code>. |
| 555 | + * @return The decoded URL or <code>null</code> if the input was |
| 556 | + * <code>null</code>. |
| 557 | + */ |
| 558 | + static String decodeUrl(String url) { |
| 559 | + String decoded = url; |
| 560 | + if (url != null && url.indexOf('%') >= 0) { |
| 561 | + int n = url.length(); |
| 562 | + StringBuffer buffer = new StringBuffer(); |
| 563 | + ByteBuffer bytes = ByteBuffer.allocate(n); |
| 564 | + for (int i = 0; i < n;) { |
| 565 | + if (url.charAt(i) == '%') { |
| 566 | + try { |
| 567 | + do { |
| 568 | + byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16); |
| 569 | + bytes.put(octet); |
| 570 | + i += 3; |
| 571 | + } while (i < n && url.charAt(i) == '%'); |
| 572 | + continue; |
| 573 | + } catch (RuntimeException e) { |
| 574 | + // malformed percent-encoded octet, fall through and |
| 575 | + // append characters literally |
| 576 | + } finally { |
| 577 | + if (bytes.position() > 0) { |
| 578 | + bytes.flip(); |
| 579 | + buffer.append(UTF8.decode(bytes).toString()); |
| 580 | + bytes.clear(); |
| 581 | + } |
| 582 | + } |
536 | 583 | } |
| 584 | + buffer.append(url.charAt(i++)); |
537 | 585 | } |
538 | | - return new File(filename); |
| 586 | + decoded = buffer.toString(); |
539 | 587 | } |
| 588 | + return decoded; |
540 | 589 | } |
541 | 590 |
|
542 | 591 | /** |
|
0 commit comments