Skip to content

Commit 64ec5b2

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 62ac909 commit 64ec5b2

File tree

2 files changed

+47
-27
lines changed

2 files changed

+47
-27
lines changed

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

Lines changed: 34 additions & 27 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;
@@ -474,18 +475,21 @@ private static String uriEncode(String source, Charset charset) {
474475
return source;
475476
}
476477

477-
StringBuilder builder = new StringBuilder();
478-
for (byte b : source.getBytes(charset)) {
478+
boolean changed = false;
479+
StringBuilder builder = new StringBuilder(source.length());
480+
byte[] bytes = source.getBytes(charset);
481+
482+
for (byte b : bytes) {
479483
if (isUnreserved(b)) {
480484
builder.append((char) b);
481-
}
482-
else {
483-
// Substitution: A '%' followed by the hexadecimal representation of the ASCII value of the replaced character
485+
} else {
484486
builder.append('%');
485-
builder.append(Integer.toHexString(b).toUpperCase());
487+
builder.append(Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16)));
488+
builder.append(Character.toUpperCase(Character.forDigit(b & 0xF, 16)));
489+
changed = true;
486490
}
487491
}
488-
return builder.toString();
492+
return changed ? builder.toString() : source;
489493
}
490494

491495
private static boolean isUnreserved(int c) {
@@ -555,34 +559,37 @@ private static String toLowerCase(String s) {
555559
* @return a decoded String
556560
*/
557561
private String percentDecode(final String input) {
558-
if (input == null) {
559-
return null;
560-
}
561-
final String decoded = uriDecode(input);
562-
if (!decoded.equals(input)) {
563-
return decoded;
564-
}
565-
return input;
562+
return uriDecode(input);
566563
}
567564

568565
public static String uriDecode(String source) {
569-
if (source == null) {
566+
if (source == null || source.isEmpty()) {
570567
return source;
571568
}
572-
int length = source.length();
573-
StringBuilder builder = new StringBuilder();
569+
570+
boolean changed = false;
571+
byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
572+
int length = bytes.length;
573+
ByteArrayOutputStream buffer = new ByteArrayOutputStream(length);
574+
574575
for (int i = 0; i < length; i++) {
575-
if (source.charAt(i) == '%') {
576-
String str = source.substring(i + 1, i + 3);
577-
char c = (char) Integer.parseInt(str, 16);
578-
builder.append(c);
579-
i += 2;
580-
}
581-
else {
582-
builder.append(source.charAt(i));
576+
int b = bytes[i];
577+
578+
if (b == '%') {
579+
if (i + 2 >= length) {
580+
return null;
581+
}
582+
583+
int b1 = Character.digit(bytes[++i], 16);
584+
int b2 = Character.digit(bytes[++i], 16);
585+
buffer.write((char) ((b1 << 4) + b2));
586+
changed = true;
587+
} else {
588+
buffer.write(b);
583589
}
584590
}
585-
return builder.toString();
591+
592+
return changed ? new String(buffer.toByteArray(), StandardCharsets.UTF_8) : source;
586593
}
587594

588595
/**

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,19 @@ public static void resetLocale() {
7070
Locale.setDefault(defaultLocale);
7171
}
7272

73+
@Test
74+
public void testEncoding1() throws MalformedPackageURLException {
75+
PackageURL purl = new PackageURL("maven", "com.google.summit", "summit-ast", "2.2.0\n", null, null);
76+
Assert.assertEquals("pkg:maven/com.google.summit/summit-ast@2.2.0%0A", purl.toString());
77+
}
78+
79+
@Test
80+
public void testEncoding2() throws MalformedPackageURLException {
81+
PackageURL purl = new PackageURL("pkg:nuget/%D0%9Cicros%D0%BEft.%D0%95ntit%D1%83Fram%D0%B5work%D0%A1%D0%BEr%D0%B5");
82+
Assert.assertEquals("Мicrosоft.ЕntitуFramеworkСоrе", purl.getName());
83+
Assert.assertEquals("pkg:nuget/%D0%9Cicros%D0%BEft.%D0%95ntit%D1%83Fram%D0%B5work%D0%A1%D0%BEr%D0%B5", purl.toString());
84+
}
85+
7386
@Test
7487
public void testConstructorParsing() throws Exception {
7588
exception = ExpectedException.none();

0 commit comments

Comments
 (0)