Skip to content

Commit 15a0e6f

Browse files
committed
Fix URL encoding and decoding
The methods `uriEncode` and `uriDecode` did not properly handle percent-encoding. In particular, `uriEncode` didn't properly output two uppercase hex digits and `urlDecode` did not properly handle non-ASCII characters. Aditionally, if no percent-encoding was performed, these methods will now return the original string. Fixes package-url#150 Closes package-url#153 Fixes package-url#154
1 parent dd8309b commit 15a0e6f

File tree

2 files changed

+48
-28
lines changed

2 files changed

+48
-28
lines changed

src/main/java/com/github/packageurl/PackageURL.java

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
*/
2222
package com.github.packageurl;
2323

24+
import java.io.ByteArrayOutputStream;
2425
import java.io.Serializable;
2526
import java.net.URI;
2627
import java.net.URISyntaxException;
@@ -444,22 +445,25 @@ private String percentEncode(final String input) {
444445
}
445446

446447
private static String uriEncode(String source, Charset charset) {
447-
if (source == null || source.length() == 0) {
448+
if (source == null || source.isEmpty()) {
448449
return source;
449450
}
450451

451-
StringBuilder builder = new StringBuilder();
452-
for (byte b : source.getBytes(charset)) {
452+
boolean changed = false;
453+
StringBuilder builder = new StringBuilder(source.length());
454+
byte[] bytes = source.getBytes(charset);
455+
456+
for (byte b : bytes) {
453457
if (isUnreserved(b)) {
454458
builder.append((char) b);
455-
}
456-
else {
457-
// Substitution: A '%' followed by the hexadecimal representation of the ASCII value of the replaced character
459+
} else {
458460
builder.append('%');
459-
builder.append(Integer.toHexString(b).toUpperCase());
461+
builder.append(Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16)));
462+
builder.append(Character.toUpperCase(Character.forDigit(b & 0xF, 16)));
463+
changed = true;
460464
}
461465
}
462-
return builder.toString();
466+
return changed ? builder.toString() : source;
463467
}
464468

465469
private static boolean isUnreserved(int c) {
@@ -525,34 +529,37 @@ private static String toLowerCase(String s) {
525529
* @return a decoded String
526530
*/
527531
private String percentDecode(final String input) {
528-
if (input == null) {
529-
return null;
530-
}
531-
final String decoded = uriDecode(input);
532-
if (!decoded.equals(input)) {
533-
return decoded;
534-
}
535-
return input;
532+
return uriDecode(input);
536533
}
537534

538535
public static String uriDecode(String source) {
539-
if (source == null) {
536+
if (source == null || source.isEmpty()) {
540537
return source;
541538
}
542-
int length = source.length();
543-
StringBuilder builder = new StringBuilder();
539+
540+
boolean changed = false;
541+
byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
542+
int length = bytes.length;
543+
ByteArrayOutputStream buffer = new ByteArrayOutputStream(length);
544+
544545
for (int i = 0; i < length; i++) {
545-
if (source.charAt(i) == '%') {
546-
String str = source.substring(i + 1, i + 3);
547-
char c = (char) Integer.parseInt(str, 16);
548-
builder.append(c);
549-
i += 2;
550-
}
551-
else {
552-
builder.append(source.charAt(i));
546+
int b = bytes[i];
547+
548+
if (b == '%') {
549+
if (i + 2 >= length) {
550+
return null;
551+
}
552+
553+
int b1 = Character.digit(bytes[++i], 16);
554+
int b2 = Character.digit(bytes[++i], 16);
555+
buffer.write((char) ((b1 << 4) + b2));
556+
changed = true;
557+
} else {
558+
buffer.write(b);
553559
}
554560
}
555-
return builder.toString();
561+
562+
return changed ? new String(buffer.toByteArray(), StandardCharsets.UTF_8) : source;
556563
}
557564

558565
/**

src/test/java/com/github/packageurl/PackageURLTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,19 @@ public static void setup() throws IOException {
5555
json = new JSONArray(jsonTxt);
5656
}
5757

58+
@Test
59+
public void testEncoding1() throws MalformedPackageURLException {
60+
PackageURL purl = new PackageURL("maven", "com.google.summit", "summit-ast", "2.2.0\n", null, null);
61+
Assert.assertEquals("pkg:maven/com.google.summit/[email protected]%0A", purl.toString());
62+
}
63+
64+
@Test
65+
public void testEncoding2() throws MalformedPackageURLException {
66+
PackageURL purl = new PackageURL("pkg:nuget/%D0%9Cicros%D0%BEft.%D0%95ntit%D1%83Fram%D0%B5work%D0%A1%D0%BEr%D0%B5");
67+
Assert.assertEquals("Мicrosоft.ЕntitуFramеworkСоrе", purl.getName());
68+
Assert.assertEquals("pkg:nuget/%D0%9Cicros%D0%BEft.%D0%95ntit%D1%83Fram%D0%B5work%D0%A1%D0%BEr%D0%B5", purl.toString());
69+
}
70+
5871
@Test
5972
public void testConstructorParsing() throws Exception {
6073
exception = ExpectedException.none();

0 commit comments

Comments
 (0)