1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.core5.net;
28
29 import java.net.InetAddress;
30 import java.net.URI;
31 import java.net.URISyntaxException;
32 import java.net.UnknownHostException;
33 import java.nio.charset.Charset;
34 import java.nio.charset.StandardCharsets;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.BitSet;
38 import java.util.Collections;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.Locale;
42 import java.util.Stack;
43
44 import org.apache.hc.core5.http.HttpHost;
45 import org.apache.hc.core5.http.NameValuePair;
46 import org.apache.hc.core5.http.message.BasicNameValuePair;
47 import org.apache.hc.core5.http.message.ParserCursor;
48 import org.apache.hc.core5.util.TextUtils;
49 import org.apache.hc.core5.util.Tokenizer;
50
51
52
53
54
55
56 public class URIBuilder {
57
58
59
60
61
62
63
64 public static URIBuilder localhost() throws UnknownHostException {
65 return new URIBuilder().setHost(InetAddress.getLocalHost());
66 }
67
68
69
70
71 public static URIBuilder loopbackAddress() {
72 return new URIBuilder().setHost(InetAddress.getLoopbackAddress());
73 }
74
75 private String scheme;
76 private String encodedSchemeSpecificPart;
77 private String encodedAuthority;
78 private String userInfo;
79 private String encodedUserInfo;
80 private String host;
81 private int port;
82 private String encodedPath;
83 private boolean pathRootless;
84 private List<String> pathSegments;
85 private String encodedQuery;
86 private List<NameValuePair> queryParams;
87 private String query;
88 private Charset charset;
89 private String fragment;
90 private String encodedFragment;
91
92
93
94
95 public URIBuilder() {
96 super();
97 this.port = -1;
98 }
99
100
101
102
103
104
105
106 public URIBuilder(final String string) throws URISyntaxException {
107 this(new URI(string), StandardCharsets.UTF_8);
108 }
109
110
111
112
113
114 public URIBuilder(final URI uri) {
115 this(uri, StandardCharsets.UTF_8);
116 }
117
118
119
120
121
122
123
124 public URIBuilder(final String string, final Charset charset) throws URISyntaxException {
125 this(new URI(string), charset);
126 }
127
128
129
130
131
132 public URIBuilder(final URI uri, final Charset charset) {
133 super();
134 digestURI(uri, charset);
135 }
136
137 public URIBuilder setCharset(final Charset charset) {
138 this.charset = charset;
139 return this;
140 }
141
142 public Charset getCharset() {
143 return charset;
144 }
145
146 private static final char QUERY_PARAM_SEPARATOR = '&';
147 private static final char PARAM_VALUE_SEPARATOR = '=';
148 private static final char PATH_SEPARATOR = '/';
149
150 private static final BitSet QUERY_PARAM_SEPARATORS = new BitSet(256);
151 private static final BitSet QUERY_VALUE_SEPARATORS = new BitSet(256);
152 private static final BitSet PATH_SEPARATORS = new BitSet(256);
153
154 static {
155 QUERY_PARAM_SEPARATORS.set(QUERY_PARAM_SEPARATOR);
156 QUERY_PARAM_SEPARATORS.set(PARAM_VALUE_SEPARATOR);
157 QUERY_VALUE_SEPARATORS.set(QUERY_PARAM_SEPARATOR);
158 PATH_SEPARATORS.set(PATH_SEPARATOR);
159 }
160
161 static List<NameValuePair> parseQuery(final CharSequence s, final Charset charset, final boolean plusAsBlank) {
162 if (s == null) {
163 return null;
164 }
165 final Tokenizer tokenParser = Tokenizer.INSTANCE;
166 final ParserCursore/ParserCursor.html#ParserCursor">ParserCursor cursor = new ParserCursor(0, s.length());
167 final List<NameValuePair> list = new ArrayList<>();
168 while (!cursor.atEnd()) {
169 final String name = tokenParser.parseToken(s, cursor, QUERY_PARAM_SEPARATORS);
170 String value = null;
171 if (!cursor.atEnd()) {
172 final int delim = s.charAt(cursor.getPos());
173 cursor.updatePos(cursor.getPos() + 1);
174 if (delim == PARAM_VALUE_SEPARATOR) {
175 value = tokenParser.parseToken(s, cursor, QUERY_VALUE_SEPARATORS);
176 if (!cursor.atEnd()) {
177 cursor.updatePos(cursor.getPos() + 1);
178 }
179 }
180 }
181 if (!name.isEmpty()) {
182 list.add(new BasicNameValuePair(
183 PercentCodec.decode(name, charset, plusAsBlank),
184 PercentCodec.decode(value, charset, plusAsBlank)));
185 }
186 }
187 return list;
188 }
189
190 static List<String> splitPath(final CharSequence s) {
191 if (s == null) {
192 return null;
193 }
194 final ParserCursore/ParserCursor.html#ParserCursor">ParserCursor cursor = new ParserCursor(0, s.length());
195
196 if (cursor.atEnd()) {
197 return new ArrayList<>(0);
198 }
199 if (PATH_SEPARATORS.get(s.charAt(cursor.getPos()))) {
200 cursor.updatePos(cursor.getPos() + 1);
201 }
202 final List<String> list = new ArrayList<>();
203 final StringBuilder buf = new StringBuilder();
204 for (;;) {
205 if (cursor.atEnd()) {
206 list.add(buf.toString());
207 break;
208 }
209 final char current = s.charAt(cursor.getPos());
210 if (PATH_SEPARATORS.get(current)) {
211 list.add(buf.toString());
212 buf.setLength(0);
213 } else {
214 buf.append(current);
215 }
216 cursor.updatePos(cursor.getPos() + 1);
217 }
218 return list;
219 }
220
221 static List<String> parsePath(final CharSequence s, final Charset charset) {
222 if (s == null) {
223 return null;
224 }
225 final List<String> segments = splitPath(s);
226 final List<String> list = new ArrayList<>(segments.size());
227 for (final String segment: segments) {
228 list.add(PercentCodec.decode(segment, charset));
229 }
230 return list;
231 }
232
233 static void formatPath(final StringBuilder buf, final Iterable<String> segments, final boolean rootless, final Charset charset) {
234 int i = 0;
235 for (final String segment : segments) {
236 if (i > 0 || !rootless) {
237 buf.append(PATH_SEPARATOR);
238 }
239 PercentCodec.encode(buf, segment, charset);
240 i++;
241 }
242 }
243
244 static void formatQuery(final StringBuilder buf, final Iterable<? extends NameValuePair> params, final Charset charset,
245 final boolean blankAsPlus) {
246 int i = 0;
247 for (final NameValuePair parameter : params) {
248 if (i > 0) {
249 buf.append(QUERY_PARAM_SEPARATOR);
250 }
251 PercentCodec.encode(buf, parameter.getName(), charset, blankAsPlus);
252 if (parameter.getValue() != null) {
253 buf.append(PARAM_VALUE_SEPARATOR);
254 PercentCodec.encode(buf, parameter.getValue(), charset, blankAsPlus);
255 }
256 i++;
257 }
258 }
259
260
261
262
263 public URI build() throws URISyntaxException {
264 return new URI(buildString());
265 }
266
267 private String buildString() {
268 final StringBuilder sb = new StringBuilder();
269 if (this.scheme != null) {
270 sb.append(this.scheme).append(':');
271 }
272 if (this.encodedSchemeSpecificPart != null) {
273 sb.append(this.encodedSchemeSpecificPart);
274 } else {
275 final boolean authoritySpecified;
276 if (this.encodedAuthority != null) {
277 sb.append("//").append(this.encodedAuthority);
278 authoritySpecified = true;
279 } else if (this.host != null) {
280 sb.append("//");
281 if (this.encodedUserInfo != null) {
282 sb.append(this.encodedUserInfo).append("@");
283 } else if (this.userInfo != null) {
284 final int idx = this.userInfo.indexOf(':');
285 if (idx != -1) {
286 PercentCodec.encode(sb, this.userInfo.substring(0, idx), this.charset);
287 sb.append(':');
288 PercentCodec.encode(sb, this.userInfo.substring(idx + 1), this.charset);
289 } else {
290 PercentCodec.encode(sb, this.userInfo, this.charset);
291 }
292 sb.append("@");
293 }
294 if (InetAddressUtils.isIPv6Address(this.host)) {
295 sb.append("[").append(this.host).append("]");
296 } else {
297 sb.append(PercentCodec.encode(this.host, this.charset));
298 }
299 if (this.port >= 0) {
300 sb.append(":").append(this.port);
301 }
302 authoritySpecified = true;
303 } else {
304 authoritySpecified = false;
305 }
306 if (this.encodedPath != null) {
307 if (authoritySpecified && !TextUtils.isEmpty(this.encodedPath) && !this.encodedPath.startsWith("/")) {
308 sb.append('/');
309 }
310 sb.append(this.encodedPath);
311 } else if (this.pathSegments != null) {
312 formatPath(sb, this.pathSegments, !authoritySpecified && this.pathRootless, this.charset);
313 }
314 if (this.encodedQuery != null) {
315 sb.append("?").append(this.encodedQuery);
316 } else if (this.queryParams != null && !this.queryParams.isEmpty()) {
317 sb.append("?");
318 formatQuery(sb, this.queryParams, this.charset, false);
319 } else if (this.query != null) {
320 sb.append("?");
321 PercentCodec.encode(sb, this.query, this.charset, PercentCodec.URIC, false);
322 }
323 }
324 if (this.encodedFragment != null) {
325 sb.append("#").append(this.encodedFragment);
326 } else if (this.fragment != null) {
327 sb.append("#");
328 PercentCodec.encode(sb, this.fragment, this.charset);
329 }
330 return sb.toString();
331 }
332
333 private void digestURI(final URI uri, final Charset charset) {
334 this.scheme = uri.getScheme();
335 this.encodedSchemeSpecificPart = uri.getRawSchemeSpecificPart();
336 this.encodedAuthority = uri.getRawAuthority();
337 this.host = uri.getHost();
338 this.port = uri.getPort();
339 this.encodedUserInfo = uri.getRawUserInfo();
340 this.userInfo = uri.getUserInfo();
341 if (this.encodedAuthority != null && this.host == null) {
342 try {
343 final URIAuthority uriAuthority = URIAuthority.parse(this.encodedAuthority);
344 this.encodedUserInfo = uriAuthority.getUserInfo();
345 this.userInfo = PercentCodec.decode(uriAuthority.getUserInfo(), charset);
346 this.host = PercentCodec.decode(uriAuthority.getHostName(), charset);
347 this.port = uriAuthority.getPort();
348 } catch (final URISyntaxException ignore) {
349 }
350 }
351 this.encodedPath = uri.getRawPath();
352 this.pathSegments = parsePath(uri.getRawPath(), charset);
353 this.pathRootless = uri.getRawPath() == null || !uri.getRawPath().startsWith("/");
354 this.encodedQuery = uri.getRawQuery();
355 this.queryParams = parseQuery(uri.getRawQuery(), charset, false);
356 this.encodedFragment = uri.getRawFragment();
357 this.fragment = uri.getFragment();
358 this.charset = charset;
359 }
360
361
362
363
364
365
366 public URIBuilder setScheme(final String scheme) {
367 this.scheme = !TextUtils.isBlank(scheme) ? scheme : null;
368 return this;
369 }
370
371
372
373
374
375
376
377
378 public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart) {
379 this.encodedSchemeSpecificPart = schemeSpecificPart;
380 return this;
381 }
382
383
384
385
386
387
388
389
390
391 public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart, final NameValuePair... nvps) {
392 return setSchemeSpecificPart(schemeSpecificPart, nvps != null ? Arrays.asList(nvps) : null);
393 }
394
395
396
397
398
399
400
401
402
403 public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart, final List <NameValuePair> nvps) {
404 this.encodedSchemeSpecificPart = null;
405 if (!TextUtils.isBlank(schemeSpecificPart)) {
406 final StringBuilder sb = new StringBuilder(schemeSpecificPart);
407 if (nvps != null && !nvps.isEmpty()) {
408 sb.append("?");
409 formatQuery(sb, nvps, this.charset, false);
410 }
411 this.encodedSchemeSpecificPart = sb.toString();
412 }
413 return this;
414 }
415
416
417
418
419
420
421
422 public URIBuilder setUserInfo(final String userInfo) {
423 this.userInfo = !TextUtils.isBlank(userInfo) ? userInfo : null;
424 this.encodedSchemeSpecificPart = null;
425 this.encodedAuthority = null;
426 this.encodedUserInfo = null;
427 return this;
428 }
429
430
431
432
433
434
435
436
437
438
439 @Deprecated
440 public URIBuilder setUserInfo(final String username, final String password) {
441 return setUserInfo(username + ':' + password);
442 }
443
444
445
446
447
448
449 public URIBuilder setHost(final InetAddress host) {
450 this.host = host != null ? host.getHostAddress() : null;
451 this.encodedSchemeSpecificPart = null;
452 this.encodedAuthority = null;
453 return this;
454 }
455
456
457
458
459
460
461 public URIBuilder setHost(final String host) {
462 this.host = host;
463 this.encodedSchemeSpecificPart = null;
464 this.encodedAuthority = null;
465 return this;
466 }
467
468
469
470
471
472
473
474 public URIBuilder setHttpHost(final HttpHost httpHost ) {
475 setScheme(httpHost.getSchemeName());
476 setHost(httpHost.getHostName());
477 setPort(httpHost.getPort());
478 return this;
479 }
480
481
482
483
484
485
486 public URIBuilder setPort(final int port) {
487 this.port = port < 0 ? -1 : port;
488 this.encodedSchemeSpecificPart = null;
489 this.encodedAuthority = null;
490 return this;
491 }
492
493
494
495
496
497
498 public URIBuilder setPath(final String path) {
499 setPathSegments(path != null ? splitPath(path) : null);
500 this.pathRootless = path != null && !path.startsWith("/");
501 return this;
502 }
503
504
505
506
507
508
509 public URIBuilder appendPath(final String path) {
510 if (path != null) {
511 appendPathSegments(splitPath(path));
512 }
513 return this;
514 }
515
516
517
518
519
520
521 public URIBuilder setPathSegments(final String... pathSegments) {
522 return setPathSegments(Arrays.asList(pathSegments));
523 }
524
525
526
527
528
529
530 public URIBuilder appendPathSegments(final String... pathSegments) {
531 return appendPathSegments(Arrays.asList(pathSegments));
532 }
533
534
535
536
537
538
539
540
541
542 public URIBuilder setPathSegmentsRootless(final String... pathSegments) {
543 return setPathSegmentsRootless(Arrays.asList(pathSegments));
544 }
545
546
547
548
549
550
551 public URIBuilder setPathSegments(final List<String> pathSegments) {
552 this.pathSegments = pathSegments != null && !pathSegments.isEmpty() ? new ArrayList<>(pathSegments) : null;
553 this.encodedSchemeSpecificPart = null;
554 this.encodedPath = null;
555 this.pathRootless = false;
556 return this;
557 }
558
559
560
561
562
563
564 public URIBuilder appendPathSegments(final List<String> pathSegments) {
565 if (pathSegments != null && !pathSegments.isEmpty()) {
566 final List<String> segments = new ArrayList<>(getPathSegments());
567 segments.addAll(pathSegments);
568 setPathSegments(segments);
569 }
570 return this;
571 }
572
573
574
575
576
577
578
579
580
581 public URIBuilder setPathSegmentsRootless(final List<String> pathSegments) {
582 this.pathSegments = pathSegments != null && !pathSegments.isEmpty() ? new ArrayList<>(pathSegments) : null;
583 this.encodedSchemeSpecificPart = null;
584 this.encodedPath = null;
585 this.pathRootless = true;
586 return this;
587 }
588
589
590
591
592
593
594 public URIBuilder removeQuery() {
595 this.queryParams = null;
596 this.query = null;
597 this.encodedQuery = null;
598 this.encodedSchemeSpecificPart = null;
599 return this;
600 }
601
602
603
604
605
606
607
608
609
610
611
612 public URIBuilder setParameters(final List <NameValuePair> nvps) {
613 if (this.queryParams == null) {
614 this.queryParams = new ArrayList<>();
615 } else {
616 this.queryParams.clear();
617 }
618 this.queryParams.addAll(nvps);
619 this.encodedQuery = null;
620 this.encodedSchemeSpecificPart = null;
621 this.query = null;
622 return this;
623 }
624
625
626
627
628
629
630
631
632
633
634
635 public URIBuilder addParameters(final List <NameValuePair> nvps) {
636 if (this.queryParams == null) {
637 this.queryParams = new ArrayList<>();
638 }
639 this.queryParams.addAll(nvps);
640 this.encodedQuery = null;
641 this.encodedSchemeSpecificPart = null;
642 this.query = null;
643 return this;
644 }
645
646
647
648
649
650
651
652
653
654
655
656 public URIBuilder setParameters(final NameValuePair... nvps) {
657 if (this.queryParams == null) {
658 this.queryParams = new ArrayList<>();
659 } else {
660 this.queryParams.clear();
661 }
662 Collections.addAll(this.queryParams, nvps);
663 this.encodedQuery = null;
664 this.encodedSchemeSpecificPart = null;
665 this.query = null;
666 return this;
667 }
668
669
670
671
672
673
674
675
676
677
678
679 public URIBuilder addParameter(final String param, final String value) {
680 if (this.queryParams == null) {
681 this.queryParams = new ArrayList<>();
682 }
683 this.queryParams.add(new BasicNameValuePair(param, value));
684 this.encodedQuery = null;
685 this.encodedSchemeSpecificPart = null;
686 this.query = null;
687 return this;
688 }
689
690
691
692
693
694
695
696
697
698
699
700 public URIBuilder setParameter(final String param, final String value) {
701 if (this.queryParams == null) {
702 this.queryParams = new ArrayList<>();
703 }
704 if (!this.queryParams.isEmpty()) {
705 for (final Iterator<NameValuePair> it = this.queryParams.iterator(); it.hasNext(); ) {
706 final NameValuePair nvp = it.next();
707 if (nvp.getName().equals(param)) {
708 it.remove();
709 }
710 }
711 }
712 this.queryParams.add(new BasicNameValuePair(param, value));
713 this.encodedQuery = null;
714 this.encodedSchemeSpecificPart = null;
715 this.query = null;
716 return this;
717 }
718
719
720
721
722
723
724 public URIBuilder clearParameters() {
725 this.queryParams = null;
726 this.encodedQuery = null;
727 this.encodedSchemeSpecificPart = null;
728 return this;
729 }
730
731
732
733
734
735
736
737
738
739
740
741 public URIBuilder setCustomQuery(final String query) {
742 this.query = !TextUtils.isBlank(query) ? query : null;
743 this.encodedQuery = null;
744 this.encodedSchemeSpecificPart = null;
745 this.queryParams = null;
746 return this;
747 }
748
749
750
751
752
753
754
755 public URIBuilder setFragment(final String fragment) {
756 this.fragment = !TextUtils.isBlank(fragment) ? fragment : null;
757 this.encodedFragment = null;
758 return this;
759 }
760
761 public boolean isAbsolute() {
762 return this.scheme != null;
763 }
764
765 public boolean isOpaque() {
766 return this.pathSegments == null && this.encodedPath == null;
767 }
768
769 public String getScheme() {
770 return this.scheme;
771 }
772
773
774
775
776
777
778
779 public String getSchemeSpecificPart() {
780 return this.encodedSchemeSpecificPart;
781 }
782
783 public String getUserInfo() {
784 return this.userInfo;
785 }
786
787 public String getHost() {
788 return this.host;
789 }
790
791 public int getPort() {
792 return this.port;
793 }
794
795 public boolean isPathEmpty() {
796 return (this.pathSegments == null || this.pathSegments.isEmpty()) &&
797 (this.encodedPath == null || this.encodedPath.isEmpty());
798 }
799
800 public List<String> getPathSegments() {
801 return this.pathSegments != null ? new ArrayList<>(this.pathSegments) : Collections.<String>emptyList();
802 }
803
804 public String getPath() {
805 if (this.pathSegments == null) {
806 return null;
807 }
808 final StringBuilder result = new StringBuilder();
809 for (final String segment : this.pathSegments) {
810 result.append('/').append(segment);
811 }
812 return result.toString();
813 }
814
815 public boolean isQueryEmpty() {
816 return (this.queryParams == null || this.queryParams.isEmpty()) && this.encodedQuery == null;
817 }
818
819 public List<NameValuePair> getQueryParams() {
820 return this.queryParams != null ? new ArrayList<>(this.queryParams) : Collections.<NameValuePair>emptyList();
821 }
822
823 public String getFragment() {
824 return this.fragment;
825 }
826
827
828
829
830
831
832
833
834
835
836
837 public URIBuilder normalizeSyntax() {
838 final String scheme = this.scheme;
839 if (scheme != null) {
840 this.scheme = scheme.toLowerCase(Locale.ROOT);
841 }
842
843 if (this.pathRootless) {
844 return this;
845 }
846
847
848 this.encodedSchemeSpecificPart = null;
849 this.encodedAuthority = null;
850 this.encodedUserInfo = null;
851 this.encodedPath = null;
852 this.encodedQuery = null;
853 this.encodedFragment = null;
854
855 final String host = this.host;
856 if (host != null) {
857 this.host = host.toLowerCase(Locale.ROOT);
858 }
859
860 if (this.pathSegments != null) {
861 final List<String> inputSegments = this.pathSegments;
862 if (!inputSegments.isEmpty()) {
863 final Stack<String> outputSegments = new Stack<>();
864 for (final String inputSegment : inputSegments) {
865 if (!inputSegment.isEmpty() && !".".equals(inputSegment)) {
866 if ("..".equals(inputSegment)) {
867 if (!outputSegments.isEmpty()) {
868 outputSegments.pop();
869 }
870 } else {
871 outputSegments.push(inputSegment);
872 }
873 }
874 }
875 if (!inputSegments.isEmpty()) {
876 final String lastSegment = inputSegments.get(inputSegments.size() - 1);
877 if (lastSegment.isEmpty()) {
878 outputSegments.push("");
879 }
880 }
881 this.pathSegments = outputSegments;
882 } else {
883 this.pathSegments = Collections.singletonList("");
884 }
885 }
886
887 return this;
888 }
889
890 @Override
891 public String toString() {
892 return buildString();
893 }
894
895 }