@@ -80,6 +80,13 @@ public class GenericUrl extends GenericData {
80
80
/** Fragment component or {@code null} for none. */
81
81
private String fragment ;
82
82
83
+ /**
84
+ * If true, the URL string originally given is used as is (without encoding, decoding and
85
+ * escaping) whenever referenced; otherwise, part of the URL string may be encoded or decoded as
86
+ * deemed appropriate or necessary.
87
+ */
88
+ private boolean verbatim ;
89
+
83
90
public GenericUrl () {}
84
91
85
92
/**
@@ -99,24 +106,52 @@ public GenericUrl() {}
99
106
* @throws IllegalArgumentException if URL has a syntax error
100
107
*/
101
108
public GenericUrl (String encodedUrl ) {
102
- this (parseURL (encodedUrl ));
109
+ this (encodedUrl , false );
110
+ }
111
+
112
+ /**
113
+ * Constructs from an encoded URL.
114
+ *
115
+ * <p>Any known query parameters with pre-defined fields as data keys are parsed based on
116
+ * their data type. Any unrecognized query parameter are always parsed as a string.
117
+ *
118
+ * <p>Any {@link MalformedURLException} is wrapped in an {@link IllegalArgumentException}.
119
+ *
120
+ * @param encodedUrl encoded URL, including any existing query parameters that should be parsed
121
+ * @param verbatim flag, to specify if URL should be used as is (without encoding, decoding and escaping)
122
+ * @throws IllegalArgumentException if URL has a syntax error
123
+ */
124
+ public GenericUrl (String encodedUrl , boolean verbatim ) {
125
+ this (parseURL (encodedUrl ), verbatim );
103
126
}
104
127
128
+
105
129
/**
106
130
* Constructs from a URI.
107
131
*
108
132
* @param uri URI
109
133
* @since 1.14
110
134
*/
111
135
public GenericUrl (URI uri ) {
136
+ this (uri , false );
137
+ }
138
+
139
+ /**
140
+ * Constructs from a URI.
141
+ *
142
+ * @param uri URI
143
+ * @param verbatim flag, to specify if URL should be used as is (without encoding, decoding and escaping)
144
+ */
145
+ public GenericUrl (URI uri , boolean verbatim ) {
112
146
this (
113
147
uri .getScheme (),
114
148
uri .getHost (),
115
149
uri .getPort (),
116
150
uri .getRawPath (),
117
151
uri .getRawFragment (),
118
152
uri .getRawQuery (),
119
- uri .getRawUserInfo ());
153
+ uri .getRawUserInfo (),
154
+ verbatim );
120
155
}
121
156
122
157
/**
@@ -126,14 +161,26 @@ public GenericUrl(URI uri) {
126
161
* @since 1.14
127
162
*/
128
163
public GenericUrl (URL url ) {
164
+ this (url , false );
165
+ }
166
+
167
+ /**
168
+ * Constructs from a URL.
169
+ *
170
+ * @param url URL
171
+ * @param verbatim flag, to specify if URL should be used as is (without encoding, decoding and escaping)
172
+ * @since 1.14
173
+ */
174
+ public GenericUrl (URL url , boolean verbatim ) {
129
175
this (
130
176
url .getProtocol (),
131
177
url .getHost (),
132
178
url .getPort (),
133
179
url .getPath (),
134
180
url .getRef (),
135
181
url .getQuery (),
136
- url .getUserInfo ());
182
+ url .getUserInfo (),
183
+ verbatim );
137
184
}
138
185
139
186
private GenericUrl (
@@ -143,16 +190,26 @@ private GenericUrl(
143
190
String path ,
144
191
String fragment ,
145
192
String query ,
146
- String userInfo ) {
193
+ String userInfo ,
194
+ boolean verbatim ) {
147
195
this .scheme = scheme .toLowerCase (Locale .US );
148
196
this .host = host ;
149
197
this .port = port ;
150
- this .pathParts = toPathParts (path );
151
- this .fragment = fragment != null ? CharEscapers .decodeUri (fragment ) : null ;
152
- if (query != null ) {
153
- UrlEncodedParser .parse (query , this );
154
- }
155
- this .userInfo = userInfo != null ? CharEscapers .decodeUri (userInfo ) : null ;
198
+ this .pathParts = toPathParts (path , verbatim );
199
+ this .verbatim = verbatim ;
200
+ if (verbatim ) {
201
+ this .fragment = fragment ;
202
+ if (query != null ) {
203
+ UrlEncodedParser .parse (query , this , false );
204
+ }
205
+ this .userInfo = userInfo ;
206
+ } else {
207
+ this .fragment = fragment != null ? CharEscapers .decodeUri (fragment ) : null ;
208
+ if (query != null ) {
209
+ UrlEncodedParser .parse (query , this );
210
+ }
211
+ this .userInfo = userInfo != null ? CharEscapers .decodeUri (userInfo ) : null ;
212
+ }
156
213
}
157
214
158
215
@ Override
@@ -333,7 +390,7 @@ public final String buildAuthority() {
333
390
buf .append (Preconditions .checkNotNull (scheme ));
334
391
buf .append ("://" );
335
392
if (userInfo != null ) {
336
- buf .append (CharEscapers .escapeUriUserInfo (userInfo )).append ('@' );
393
+ buf .append (verbatim ? userInfo : CharEscapers .escapeUriUserInfo (userInfo )).append ('@' );
337
394
}
338
395
buf .append (Preconditions .checkNotNull (host ));
339
396
int port = this .port ;
@@ -357,12 +414,12 @@ public final String buildRelativeUrl() {
357
414
if (pathParts != null ) {
358
415
appendRawPathFromParts (buf );
359
416
}
360
- addQueryParams (entrySet (), buf );
417
+ addQueryParams (entrySet (), buf , verbatim );
361
418
362
419
// URL fragment
363
420
String fragment = this .fragment ;
364
421
if (fragment != null ) {
365
- buf .append ('#' ).append (URI_FRAGMENT_ESCAPER .escape (fragment ));
422
+ buf .append ('#' ).append (verbatim ? fragment : URI_FRAGMENT_ESCAPER .escape (fragment ));
366
423
}
367
424
return buf .toString ();
368
425
}
@@ -467,7 +524,7 @@ public String getRawPath() {
467
524
* @param encodedPath raw encoded path or {@code null} to set {@link #pathParts} to {@code null}
468
525
*/
469
526
public void setRawPath (String encodedPath ) {
470
- pathParts = toPathParts (encodedPath );
527
+ pathParts = toPathParts (encodedPath , verbatim );
471
528
}
472
529
473
530
/**
@@ -482,7 +539,7 @@ public void setRawPath(String encodedPath) {
482
539
*/
483
540
public void appendRawPath (String encodedPath ) {
484
541
if (encodedPath != null && encodedPath .length () != 0 ) {
485
- List <String > appendedPathParts = toPathParts (encodedPath );
542
+ List <String > appendedPathParts = toPathParts (encodedPath , verbatim );
486
543
if (pathParts == null || pathParts .isEmpty ()) {
487
544
this .pathParts = appendedPathParts ;
488
545
} else {
@@ -492,7 +549,6 @@ public void appendRawPath(String encodedPath) {
492
549
}
493
550
}
494
551
}
495
-
496
552
/**
497
553
* Returns the decoded path parts for the given encoded path.
498
554
*
@@ -503,6 +559,20 @@ public void appendRawPath(String encodedPath) {
503
559
* or {@code ""} input
504
560
*/
505
561
public static List <String > toPathParts (String encodedPath ) {
562
+ return toPathParts (encodedPath , false );
563
+ }
564
+
565
+ /**
566
+ * Returns the path parts (decoded if not {@code verbatim}).
567
+ *
568
+ * @param encodedPath slash-prefixed encoded path, for example {@code
569
+ * "/m8/feeds/contacts/default/full"}
570
+ * @param verbatim flag, to specify if URL should be used as is (without encoding, decoding and escaping)
571
+ * @return path parts (decoded if not {@code verbatim}), with each part assumed to be preceded by a {@code '/'}, for example
572
+ * {@code "", "m8", "feeds", "contacts", "default", "full"}, or {@code null} for {@code null}
573
+ * or {@code ""} input
574
+ */
575
+ public static List <String > toPathParts (String encodedPath , boolean verbatim ) {
506
576
if (encodedPath == null || encodedPath .length () == 0 ) {
507
577
return null ;
508
578
}
@@ -518,7 +588,7 @@ public static List<String> toPathParts(String encodedPath) {
518
588
} else {
519
589
sub = encodedPath .substring (cur );
520
590
}
521
- result .add (CharEscapers .decodeUri (sub ));
591
+ result .add (verbatim ? sub : CharEscapers .decodeUri (sub ));
522
592
cur = slash + 1 ;
523
593
}
524
594
return result ;
@@ -532,40 +602,40 @@ private void appendRawPathFromParts(StringBuilder buf) {
532
602
buf .append ('/' );
533
603
}
534
604
if (pathPart .length () != 0 ) {
535
- buf .append (CharEscapers .escapeUriPath (pathPart ));
605
+ buf .append (verbatim ? pathPart : CharEscapers .escapeUriPath (pathPart ));
536
606
}
537
607
}
538
608
}
539
609
540
610
/** Adds query parameters from the provided entrySet into the buffer. */
541
- static void addQueryParams (Set <Entry <String , Object >> entrySet , StringBuilder buf ) {
611
+ static void addQueryParams (Set <Entry <String , Object >> entrySet , StringBuilder buf , boolean verbatim ) {
542
612
// (similar to UrlEncodedContent)
543
613
boolean first = true ;
544
614
for (Map .Entry <String , Object > nameValueEntry : entrySet ) {
545
615
Object value = nameValueEntry .getValue ();
546
616
if (value != null ) {
547
- String name = CharEscapers .escapeUriQuery (nameValueEntry .getKey ());
617
+ String name = verbatim ? nameValueEntry . getKey () : CharEscapers .escapeUriQuery (nameValueEntry .getKey ());
548
618
if (value instanceof Collection <?>) {
549
619
Collection <?> collectionValue = (Collection <?>) value ;
550
620
for (Object repeatedValue : collectionValue ) {
551
- first = appendParam (first , buf , name , repeatedValue );
621
+ first = appendParam (first , buf , name , repeatedValue , verbatim );
552
622
}
553
623
} else {
554
- first = appendParam (first , buf , name , value );
624
+ first = appendParam (first , buf , name , value , verbatim );
555
625
}
556
626
}
557
627
}
558
628
}
559
629
560
- private static boolean appendParam (boolean first , StringBuilder buf , String name , Object value ) {
630
+ private static boolean appendParam (boolean first , StringBuilder buf , String name , Object value , boolean verbatim ) {
561
631
if (first ) {
562
632
first = false ;
563
633
buf .append ('?' );
564
634
} else {
565
635
buf .append ('&' );
566
636
}
567
637
buf .append (name );
568
- String stringValue = CharEscapers .escapeUriQuery (value .toString ());
638
+ String stringValue = verbatim ? value . toString () : CharEscapers .escapeUriQuery (value .toString ());
569
639
if (stringValue .length () != 0 ) {
570
640
buf .append ('=' ).append (stringValue );
571
641
}
0 commit comments