2121 */
2222package com .github .packageurl ;
2323
24+ import java .io .ByteArrayOutputStream ;
2425import java .io .Serializable ;
2526import java .io .UnsupportedEncodingException ;
2627import java .net .URI ;
2728import java .net .URISyntaxException ;
28- import java .net .URLDecoder ;
29- import java .net .URLEncoder ;
29+ import java .nio .charset .Charset ;
3030import java .nio .charset .StandardCharsets ;
3131import java .util .Arrays ;
3232import java .util .Collections ;
@@ -432,16 +432,38 @@ private String canonicalize(boolean coordinatesOnly) {
432432 * @return an encoded String
433433 */
434434 private String percentEncode (final String input ) {
435- try {
436- return URLEncoder .encode (input , UTF8 )
437- .replace ("+" , "%20" )
438- // "*" is a reserved character in RFC 3986.
439- .replace ("*" , "%2A" )
440- // "~" is an unreserved character in RFC 3986.
441- .replace ("%7E" , "~" );
442- } catch (UnsupportedEncodingException e ) {
443- return input ; // this should never occur
435+ return uriEncode (input , StandardCharsets .UTF_8 );
436+ }
437+
438+ private static String uriEncode (String source , Charset charset ) {
439+ if (source == null || source .length () == 0 ) {
440+ return source ;
444441 }
442+
443+ StringBuilder builder = new StringBuilder ();
444+ for (byte b : source .getBytes (charset )) {
445+ if (isUnreserved (b )) {
446+ builder .append ((char ) b );
447+ }
448+ else {
449+ // Substitution: A '%' followed by the hexadecimal representation of the ASCII value of the replaced character
450+ builder .append ('%' );
451+ builder .append (Integer .toHexString (b ).toUpperCase ());
452+ }
453+ }
454+ return builder .toString ();
455+ }
456+
457+ private static boolean isUnreserved (int c ) {
458+ return (isAlpha (c ) || isDigit (c ) || '-' == c || '.' == c || '_' == c || '~' == c );
459+ }
460+
461+ private static boolean isAlpha (int c ) {
462+ return ((c >= 'a' && c <= 'z' ) || (c >= 'A' && c <= 'Z' ));
463+ }
464+
465+ private static boolean isDigit (int c ) {
466+ return (c >= '0' && c <= '9' );
445467 }
446468
447469 /**
@@ -455,17 +477,33 @@ private String percentDecode(final String input) {
455477 if (input == null ) {
456478 return null ;
457479 }
458- try {
459- final String decoded = URLDecoder .decode (input , UTF8 );
460- if (!decoded .equals (input )) {
461- return decoded ;
462- }
463- } catch (UnsupportedEncodingException e ) {
464- return input ; // this should never occur
480+ final String decoded = uriDecode (input );
481+ if (!decoded .equals (input )) {
482+ return decoded ;
465483 }
466484 return input ;
467485 }
468486
487+ public static String uriDecode (String source ) {
488+ if (source == null ) {
489+ return source ;
490+ }
491+ int length = source .length ();
492+ StringBuilder builder = new StringBuilder ();
493+ for (int i = 0 ; i < length ; i ++) {
494+ if (source .charAt (i ) == '%' ) {
495+ String str = source .substring (i + 1 , i + 3 );
496+ char c = (char ) Integer .parseInt (str , 16 );
497+ builder .append (c );
498+ i += 2 ;
499+ }
500+ else {
501+ builder .append (source .charAt (i ));
502+ }
503+ }
504+ return builder .toString ();
505+ }
506+
469507 /**
470508 * Given a specified PackageURL, this method will parse the purl and populate this classes
471509 * instance fields so that the corresponding getters may be called to retrieve the individual
0 commit comments