1919import com .google .api .client .util .escape .CharEscapers ;
2020import com .google .cloud .bigquery .BigQueryOptions ;
2121import com .google .cloud .bigquery .exception .BigQueryJdbcRuntimeException ;
22+ import com .google .common .collect .ImmutableList ;
23+ import com .google .common .net .UrlEscapers ;
24+ import java .io .UnsupportedEncodingException ;
25+ import java .net .URLDecoder ;
26+ import java .nio .charset .StandardCharsets ;
2227import java .util .Arrays ;
2328import java .util .Collections ;
2429import java .util .HashMap ;
2530import java .util .HashSet ;
31+ import java .util .LinkedHashMap ;
2632import java .util .List ;
2733import java .util .Map ;
2834import java .util .Map .Entry ;
3945 */
4046final class BigQueryJdbcUrlUtility {
4147
48+ private static final Map <String , Map <String , String >> PARSE_CACHE =
49+ Collections .synchronizedMap (
50+ new LinkedHashMap <String , Map <String , String >>(50 , 0.75f , true ) {
51+ protected boolean removeEldestEntry (Map .Entry <String , Map <String , String >> eldest ) {
52+ return size () > 50 ; // bound cache size
53+ }
54+ });
55+
4256 // TODO: Add all Connection options
4357 static final String ALLOW_LARGE_RESULTS_PROPERTY_NAME = "AllowLargeResults" ;
4458 static final String LARGE_RESULTS_TABLE_PROPERTY_NAME = "LargeResultTable" ;
@@ -122,6 +136,10 @@ final class BigQueryJdbcUrlUtility {
122136 static final String BYOID_SUBJECT_TOKEN_TYPE_PROPERTY_NAME = "BYOID_SubjectTokenType" ;
123137 static final String BYOID_TOKEN_URI_PROPERTY_NAME = "BYOID_TokenUri" ;
124138 static final String PARTNER_TOKEN_PROPERTY_NAME = "PartnerToken" ;
139+ private static final Pattern PARTNER_TOKEN_PATTERN =
140+ Pattern .compile (
141+ "(?:^|(?<=;))" + PARTNER_TOKEN_PROPERTY_NAME + "=\\ s*((?:\\ ([^)]*\\ )|[^;])*?)(?=(?:;|$))" ,
142+ Pattern .CASE_INSENSITIVE );
125143 static final String METADATA_FETCH_THREAD_COUNT_PROPERTY_NAME = "MetaDataFetchThreadCount" ;
126144 static final int DEFAULT_METADATA_FETCH_THREAD_COUNT_VALUE = 32 ;
127145 static final String RETRY_TIMEOUT_IN_SECS_PROPERTY_NAME = "Timeout" ;
@@ -591,6 +609,37 @@ final class BigQueryJdbcUrlUtility {
591609 + " header." )
592610 .build ())));
593611
612+ private static final List <String > NETWORK_PROPERTIES =
613+ ImmutableList .of (
614+ PARTNER_TOKEN_PROPERTY_NAME ,
615+ ENDPOINT_OVERRIDES_PROPERTY_NAME ,
616+ PRIVATE_SERVICE_CONNECT_PROPERTY_NAME );
617+
618+ private static final Map <String , String > PROPERTY_NAME_MAP ;
619+
620+ static {
621+ Map <String , String > map = new HashMap <>();
622+ for (BigQueryConnectionProperty p : VALID_PROPERTIES ) {
623+ map .put (p .getName ().toUpperCase (), p .getName ());
624+ }
625+ for (BigQueryConnectionProperty p : AUTH_PROPERTIES ) {
626+ map .put (p .getName ().toUpperCase (), p .getName ());
627+ }
628+ for (BigQueryConnectionProperty p : PROXY_PROPERTIES ) {
629+ map .put (p .getName ().toUpperCase (), p .getName ());
630+ }
631+ for (String p : OVERRIDE_PROPERTIES ) {
632+ map .put (p .toUpperCase (), p );
633+ }
634+ for (String p : BYOID_PROPERTIES ) {
635+ map .put (p .toUpperCase (), p );
636+ }
637+ for (String p : NETWORK_PROPERTIES ) {
638+ map .put (p .toUpperCase (), p );
639+ }
640+ PROPERTY_NAME_MAP = Collections .unmodifiableMap (map );
641+ }
642+
594643 private BigQueryJdbcUrlUtility () {}
595644
596645 /**
@@ -601,12 +650,69 @@ private BigQueryJdbcUrlUtility() {}
601650 * @return The String value of the property, or the default value if the property is not found.
602651 */
603652 static String parseUriProperty (String uri , String property ) {
604- Pattern pattern = Pattern .compile (String .format ("(?is)(?:;|\\ ?)%s=(.*?)(?:;|$)" , property ));
605- Matcher matcher = pattern .matcher (uri );
606- if (matcher .find () && matcher .groupCount () == 1 ) {
607- return CharEscapers .decodeUriPath (matcher .group (1 ));
653+ Map <String , String > map = parseUrl (uri );
654+ if (PROPERTY_NAME_MAP .containsKey (property .toUpperCase ())) {
655+ return map .get (PROPERTY_NAME_MAP .get (property .toUpperCase ()));
608656 }
609- return null ;
657+ return map .get (property );
658+ }
659+
660+ /**
661+ * Parses the URL into a map of key-value pairs, validating that all keys are known properties.
662+ *
663+ * @param url The URL to parse.
664+ * @return A map of property names to values.
665+ * @throws BigQueryJdbcRuntimeException if an unknown property is found or the URL is malformed.
666+ */
667+ static Map <String , String > parseUrl (String url ) {
668+ return PARSE_CACHE .computeIfAbsent (url , BigQueryJdbcUrlUtility ::parseUrlInternal );
669+ }
670+
671+ private static Map <String , String > parseUrlInternal (String url ) {
672+ Map <String , String > map = new HashMap <>();
673+ if (url == null ) {
674+ return map ;
675+ }
676+
677+ String [] urlParts = url .split (";" , 2 );
678+ if (urlParts .length < 2 ) {
679+ return map ;
680+ }
681+
682+ String urlToParse = urlParts [1 ];
683+
684+ // Parse PartnerToken separately as it contains ';'
685+ Matcher matcher = PARTNER_TOKEN_PATTERN .matcher (urlToParse );
686+ if (matcher .find ()) {
687+ String rawToken = matcher .group (1 ).trim ();
688+ String token =
689+ (rawToken .startsWith ("(" ) && rawToken .endsWith (")" ))
690+ ? rawToken .substring (1 , rawToken .length () - 1 ).trim ()
691+ : rawToken ;
692+
693+ if (token .toUpperCase ().startsWith ("GPN:" )) {
694+ map .put (PARTNER_TOKEN_PROPERTY_NAME , " (" + token + ")" );
695+ }
696+ urlToParse = matcher .replaceFirst ("" );
697+ }
698+
699+ String [] parts = urlToParse .split (";" );
700+ for (String part : parts ) {
701+ if (part .trim ().isEmpty ()) {
702+ continue ;
703+ }
704+ String [] kv = part .split ("=" , 2 );
705+ String key = kv [0 ].trim ().toUpperCase ();
706+ if (kv .length != 2 || !PROPERTY_NAME_MAP .containsKey (key )) {
707+ String ref = (kv .length == 2 ) ? key : part ;
708+ String safeRef = ref .length () > 32 ? ref .substring (0 , 32 ) + "..." : ref ;
709+ throw new BigQueryJdbcRuntimeException (
710+ String .format ("Wrong value or unknown setting: %s" , safeRef ));
711+ }
712+
713+ map .put (PROPERTY_NAME_MAP .get (key ), CharEscapers .decodeUriPath (kv [1 ]));
714+ }
715+ return Collections .unmodifiableMap (map );
610716 }
611717
612718 /**
@@ -622,7 +728,11 @@ static String appendPropertiesToURL(String url, String callerClassName, Properti
622728 for (Entry <Object , Object > entry : properties .entrySet ()) {
623729 if (entry .getValue () != null && !"" .equals (entry .getValue ())) {
624730 LOG .finest ("Appending %s with value %s to URL" , entry .getKey (), entry .getValue ());
625- urlBuilder .append (";" ).append (entry .getKey ()).append ("=" ).append (entry .getValue ());
731+ String encodedValue =
732+ UrlEscapers .urlFormParameterEscaper ()
733+ .escape ((String ) entry .getValue ())
734+ .replace ("+" , "%20" );
735+ urlBuilder .append (";" ).append (entry .getKey ()).append ("=" ).append (encodedValue );
626736 }
627737 }
628738 return urlBuilder .toString ();
@@ -697,22 +807,17 @@ public static String parsePartnerTokenProperty(String url, String callerClassNam
697807 LOG .finest ("++enter++\t " + callerClassName );
698808 // This property is expected to be set by partners only. For more details on exact format
699809 // supported, refer b/396086960
700- String regex =
701- PARTNER_TOKEN_PROPERTY_NAME + "=\\ s*\\ (\\ s*(GPN:[^;]*?)\\ s*(?:;\\ s*([^)]*?))?\\ s*\\ )" ;
702- Pattern pattern = Pattern .compile (regex );
703- Matcher matcher = pattern .matcher (url );
704-
810+ Matcher matcher = PARTNER_TOKEN_PATTERN .matcher (url );
705811 if (matcher .find ()) {
706- String gpnPart = matcher .group (1 );
707- String environmentPart = matcher .group (2 );
708- StringBuilder partnerToken = new StringBuilder (" (" );
709- partnerToken .append (gpnPart );
710- if (environmentPart != null && !environmentPart .trim ().isEmpty ()) {
711- partnerToken .append ("; " );
712- partnerToken .append (environmentPart );
812+ String rawToken = matcher .group (1 ).trim ();
813+ String token =
814+ (rawToken .startsWith ("(" ) && rawToken .endsWith (")" ))
815+ ? rawToken .substring (1 , rawToken .length () - 1 ).trim ()
816+ : rawToken ;
817+
818+ if (token .toUpperCase ().startsWith ("GPN:" )) {
819+ return " (" + token + ")" ;
713820 }
714- partnerToken .append (")" );
715- return partnerToken .toString ();
716821 }
717822 return null ;
718823 }
@@ -793,7 +898,12 @@ static Map<String, String> parseOverrideProperties(String url, String callerClas
793898 Matcher matcher = pattern .matcher (url );
794899 String overridePropertiesString ;
795900 if (matcher .find () && matcher .groupCount () >= 1 ) {
796- overridePropertiesString = matcher .group (2 );
901+ try {
902+ overridePropertiesString =
903+ URLDecoder .decode (matcher .group (2 ), StandardCharsets .UTF_8 .name ());
904+ } catch (UnsupportedEncodingException e ) {
905+ throw new BigQueryJdbcRuntimeException (e );
906+ }
797907 } else {
798908 return overrideProps ;
799909 }
0 commit comments