Skip to content

Commit dd8309b

Browse files
dwalluckjeremylong
andauthored
Fix case of various components (#164)
* Fix case of various components There were places in the code where code was duplicated or could return different results based on the locale. Duplicate code was moved to new methods. The locale-specic lowercase method were replaced by an ASCII-specific lowecase method. * fix: resolve merge conflict * fix: code readability --------- Co-authored-by: Jeremy Long <jeremy.long@gmail.com>
1 parent af34fc4 commit dd8309b

File tree

1 file changed

+67
-21
lines changed

1 file changed

+67
-21
lines changed

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

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public PackageURL(final String type, final String name) throws MalformedPackageU
9494
public PackageURL(final String type, final String namespace, final String name, final String version,
9595
final TreeMap<String, String> qualifiers, final String subpath)
9696
throws MalformedPackageURLException {
97-
this.type = validateType(type);
97+
this.type = toLowerCase(validateType(type));
9898
this.namespace = validateNamespace(namespace);
9999
this.name = validateName(name);
100100
this.version = validateVersion(version);
@@ -246,16 +246,19 @@ private String validateType(final String value) throws MalformedPackageURLExcept
246246
if (value == null || value.isEmpty()) {
247247
throw new MalformedPackageURLException("The PackageURL type cannot be null or empty");
248248
}
249-
if (value.charAt(0) >= '0' && value.charAt(0) <= '9') {
249+
250+
if (isDigit(value.charAt(0))) {
250251
throw new MalformedPackageURLException("The PackageURL type cannot start with a number");
251252
}
252-
final String retVal = value.toLowerCase();
253-
if (retVal.chars().anyMatch(c -> !(c == '.' || c == '+' || c == '-'
254-
|| (c >= 'a' && c <= 'z')
255-
|| (c >= '0' && c <= '9')))) {
253+
254+
if (!value.chars().allMatch(c -> (c == '.' || c == '+' || c == '-'
255+
|| isUpperCase(c)
256+
|| isLowerCase(c)
257+
|| isDigit(c)))) {
256258
throw new MalformedPackageURLException("The PackageURL type contains invalid characters");
257259
}
258-
return retVal;
260+
261+
return value;
259262
}
260263

261264
private String validateNamespace(final String value) throws MalformedPackageURLException {
@@ -270,15 +273,14 @@ private String validateNamespace(final String[] values) throws MalformedPackageU
270273
return null;
271274
}
272275
final String tempNamespace = validatePath(values, false);
273-
274276
String retVal;
275277
switch (type) {
276278
case StandardTypes.BITBUCKET:
277279
case StandardTypes.DEBIAN:
278280
case StandardTypes.GITHUB:
279281
case StandardTypes.GOLANG:
280282
case StandardTypes.RPM:
281-
retVal = tempNamespace.toLowerCase();
283+
retVal = tempNamespace != null ? toLowerCase(tempNamespace) : null;
282284
break;
283285
default:
284286
retVal = tempNamespace;
@@ -297,10 +299,10 @@ private String validateName(final String value) throws MalformedPackageURLExcept
297299
case StandardTypes.DEBIAN:
298300
case StandardTypes.GITHUB:
299301
case StandardTypes.GOLANG:
300-
temp = value.toLowerCase();
302+
temp = toLowerCase(value);
301303
break;
302304
case StandardTypes.PYPI:
303-
temp = value.replaceAll("_", "-").toLowerCase();
305+
temp = toLowerCase(value).replace('_', '-');
304306
break;
305307
default:
306308
temp = value;
@@ -330,16 +332,15 @@ private Map<String, String> validateQualifiers(final Map<String, String> values)
330332
return values;
331333
}
332334

333-
private String validateKey(final String value) throws MalformedPackageURLException {
335+
private void validateKey(final String value) throws MalformedPackageURLException {
334336
if (value == null || value.isEmpty()) {
335337
throw new MalformedPackageURLException("Qualifier key is invalid: " + value);
336338
}
337-
final String retValue = value.toLowerCase();
338-
if ((value.charAt(0) >= '0' && value.charAt(0) <= '9')
339-
|| !value.chars().allMatch(c -> (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '-' || c == '_')) {
339+
340+
if (isDigit(value.charAt(0))
341+
|| !value.chars().allMatch(c -> isLowerCase(c) || (isDigit(c)) || c == '.' || c == '-' || c == '_')) {
340342
throw new MalformedPackageURLException("Qualifier key is invalid: " + value);
341343
}
342-
return retValue;
343344
}
344345

345346
private String validatePath(final String value, final boolean isSubpath) throws MalformedPackageURLException {
@@ -418,7 +419,7 @@ private String canonicalize(boolean coordinatesOnly) {
418419
if (qualifiers != null && qualifiers.size() > 0) {
419420
purl.append("?");
420421
qualifiers.entrySet().stream().forEachOrdered((entry) -> {
421-
purl.append(entry.getKey().toLowerCase());
422+
purl.append(toLowerCase(entry.getKey()));
422423
purl.append("=");
423424
purl.append(percentEncode(entry.getValue()));
424425
purl.append("&");
@@ -466,13 +467,56 @@ private static boolean isUnreserved(int c) {
466467
}
467468

468469
private static boolean isAlpha(int c) {
469-
return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));
470+
return (isLowerCase(c) || isUpperCase(c));
470471
}
471472

472473
private static boolean isDigit(int c) {
473474
return (c >= '0' && c <= '9');
474475
}
475476

477+
private static boolean isUpperCase(int c) {
478+
return (c >= 'A' && c <= 'Z');
479+
}
480+
481+
private static int indexOfFirstUpperCaseChar(String s) {
482+
int length = s.length();
483+
484+
for (int i = 0; i < length; i++) {
485+
if (isUpperCase(s.charAt(i))) {
486+
return i;
487+
}
488+
}
489+
490+
return -1;
491+
}
492+
493+
private static boolean isLowerCase(int c) {
494+
return (c >= 'a' && c <= 'z');
495+
}
496+
497+
private static int toLowerCase(int c) {
498+
return (c ^ 0x20);
499+
}
500+
501+
private static String toLowerCase(String s) {
502+
int pos = indexOfFirstUpperCaseChar(s);
503+
504+
if (pos == -1) {
505+
return s;
506+
}
507+
508+
char[] chars = s.toCharArray();
509+
int length = chars.length;
510+
511+
for (int i = pos; i < length; i++) {
512+
if (isUpperCase(chars[i])) {
513+
chars[i] = (char) toLowerCase(chars[i]);
514+
}
515+
}
516+
517+
return new String(chars);
518+
}
519+
476520
/**
477521
* Optionally decodes a String, if it's encoded. If String is not encoded,
478522
* method will return the original input value.
@@ -571,7 +615,8 @@ private void parse(final String purl) throws MalformedPackageURLException {
571615
if (index <= start) {
572616
throw new MalformedPackageURLException("Invalid purl: does not contain both a type and name");
573617
}
574-
this.type = validateType(remainder.substring(start, index).toLowerCase());
618+
this.type = toLowerCase(validateType(remainder.substring(start, index)));
619+
575620
start = index + 1;
576621

577622
// version is optional - check for existence
@@ -619,8 +664,9 @@ private Map<String, String> parseQualifiers(final String encodedString) throws M
619664
(map, value) -> {
620665
final String[] entry = value.split("=", 2);
621666
if (entry.length == 2 && !entry[1].isEmpty()) {
622-
if (map.put(entry[0].toLowerCase(), percentDecode(entry[1])) != null) {
623-
throw new ValidationException("Duplicate package qualifier encountere - more then one value was specified for " + entry[0].toLowerCase());
667+
String key = toLowerCase(entry[0]);
668+
if (map.put(key, percentDecode(entry[1])) != null) {
669+
throw new ValidationException("Duplicate package qualifier encountered. More then one value was specified for " + key);
624670
}
625671
}
626672
},

0 commit comments

Comments
 (0)