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 final String uriHost = uri.getHost();
338
339
340 this.host = uriHost != null && InetAddressUtils.isIPv6URLBracketedAddress(uriHost)
341 ? uriHost.substring(1, uriHost.length() - 1)
342 : uriHost;
343 this.port = uri.getPort();
344 this.encodedUserInfo = uri.getRawUserInfo();
345 this.userInfo = uri.getUserInfo();
346 if (this.encodedAuthority != null && this.host == null) {
347 try {
348 final URIAuthority uriAuthority = URIAuthority.parse(this.encodedAuthority);
349 this.encodedUserInfo = uriAuthority.getUserInfo();
350 this.userInfo = PercentCodec.decode(uriAuthority.getUserInfo(), charset);
351 this.host = PercentCodec.decode(uriAuthority.getHostName(), charset);
352 this.port = uriAuthority.getPort();
353 } catch (final URISyntaxException ignore) {
354 }
355 }
356 this.encodedPath = uri.getRawPath();
357 this.pathSegments = parsePath(uri.getRawPath(), charset);
358 this.pathRootless = uri.getRawPath() == null || !uri.getRawPath().startsWith("/");
359 this.encodedQuery = uri.getRawQuery();
360 this.queryParams = parseQuery(uri.getRawQuery(), charset, false);
361 this.encodedFragment = uri.getRawFragment();
362 this.fragment = uri.getFragment();
363 this.charset = charset;
364 }
365
366
367
368
369
370
371 public URIBuilder setScheme(final String scheme) {
372 this.scheme = !TextUtils.isBlank(scheme) ? scheme : null;
373 return this;
374 }
375
376
377
378
379
380
381
382
383 public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart) {
384 this.encodedSchemeSpecificPart = schemeSpecificPart;
385 return this;
386 }
387
388
389
390
391
392
393
394
395
396 public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart, final NameValuePair... nvps) {
397 return setSchemeSpecificPart(schemeSpecificPart, nvps != null ? Arrays.asList(nvps) : null);
398 }
399
400
401
402
403
404
405
406
407
408 public URIBuilder setSchemeSpecificPart(final String schemeSpecificPart, final List <NameValuePair> nvps) {
409 this.encodedSchemeSpecificPart = null;
410 if (!TextUtils.isBlank(schemeSpecificPart)) {
411 final StringBuilder sb = new StringBuilder(schemeSpecificPart);
412 if (nvps != null && !nvps.isEmpty()) {
413 sb.append("?");
414 formatQuery(sb, nvps, this.charset, false);
415 }
416 this.encodedSchemeSpecificPart = sb.toString();
417 }
418 return this;
419 }
420
421
422
423
424
425
426
427 public URIBuilder setUserInfo(final String userInfo) {
428 this.userInfo = !TextUtils.isBlank(userInfo) ? userInfo : null;
429 this.encodedSchemeSpecificPart = null;
430 this.encodedAuthority = null;
431 this.encodedUserInfo = null;
432 return this;
433 }
434
435
436
437
438
439
440
441
442
443
444 @Deprecated
445 public URIBuilder setUserInfo(final String username, final String password) {
446 return setUserInfo(username + ':' + password);
447 }
448
449
450
451
452
453
454 public URIBuilder setHost(final InetAddress host) {
455 this.host = host != null ? host.getHostAddress() : null;
456 this.encodedSchemeSpecificPart = null;
457 this.encodedAuthority = null;
458 return this;
459 }
460
461
462
463
464
465
466
467
468 public URIBuilder setHost(final String host) {
469 this.host = host;
470 this.encodedSchemeSpecificPart = null;
471 this.encodedAuthority = null;
472 return this;
473 }
474
475
476
477
478
479
480
481 public URIBuilder setHttpHost(final HttpHost httpHost ) {
482 setScheme(httpHost.getSchemeName());
483 setHost(httpHost.getHostName());
484 setPort(httpHost.getPort());
485 return this;
486 }
487
488
489
490
491
492
493 public URIBuilder setPort(final int port) {
494 this.port = port < 0 ? -1 : port;
495 this.encodedSchemeSpecificPart = null;
496 this.encodedAuthority = null;
497 return this;
498 }
499
500
501
502
503
504
505 public URIBuilder setPath(final String path) {
506 setPathSegments(path != null ? splitPath(path) : null);
507 this.pathRootless = path != null && !path.startsWith("/");
508 return this;
509 }
510
511
512
513
514
515
516 public URIBuilder appendPath(final String path) {
517 if (path != null) {
518 appendPathSegments(splitPath(path));
519 }
520 return this;
521 }
522
523
524
525
526
527
528 public URIBuilder setPathSegments(final String... pathSegments) {
529 return setPathSegments(Arrays.asList(pathSegments));
530 }
531
532
533
534
535
536
537 public URIBuilder appendPathSegments(final String... pathSegments) {
538 return appendPathSegments(Arrays.asList(pathSegments));
539 }
540
541
542
543
544
545
546
547
548
549 public URIBuilder setPathSegmentsRootless(final String... pathSegments) {
550 return setPathSegmentsRootless(Arrays.asList(pathSegments));
551 }
552
553
554
555
556
557
558 public URIBuilder setPathSegments(final List<String> pathSegments) {
559 this.pathSegments = pathSegments != null && !pathSegments.isEmpty() ? new ArrayList<>(pathSegments) : null;
560 this.encodedSchemeSpecificPart = null;
561 this.encodedPath = null;
562 this.pathRootless = false;
563 return this;
564 }
565
566
567
568
569
570
571 public URIBuilder appendPathSegments(final List<String> pathSegments) {
572 if (pathSegments != null && !pathSegments.isEmpty()) {
573 if (this.pathSegments == null) {
574 this.pathSegments = new ArrayList<>();
575 }
576 this.pathSegments.addAll(pathSegments);
577 this.encodedSchemeSpecificPart = null;
578 this.encodedPath = null;
579 }
580 return this;
581 }
582
583
584
585
586
587
588
589
590
591 public URIBuilder setPathSegmentsRootless(final List<String> pathSegments) {
592 this.pathSegments = pathSegments != null && !pathSegments.isEmpty() ? new ArrayList<>(pathSegments) : null;
593 this.encodedSchemeSpecificPart = null;
594 this.encodedPath = null;
595 this.pathRootless = true;
596 return this;
597 }
598
599
600
601
602
603
604 public URIBuilder removeQuery() {
605 this.queryParams = null;
606 this.query = null;
607 this.encodedQuery = null;
608 this.encodedSchemeSpecificPart = null;
609 return this;
610 }
611
612
613
614
615
616
617
618
619
620
621
622 public URIBuilder setParameters(final List <NameValuePair> nvps) {
623 if (this.queryParams == null) {
624 this.queryParams = new ArrayList<>();
625 } else {
626 this.queryParams.clear();
627 }
628 this.queryParams.addAll(nvps);
629 this.encodedQuery = null;
630 this.encodedSchemeSpecificPart = null;
631 this.query = null;
632 return this;
633 }
634
635
636
637
638
639
640
641
642
643
644
645 public URIBuilder addParameters(final List <NameValuePair> nvps) {
646 if (this.queryParams == null) {
647 this.queryParams = new ArrayList<>();
648 }
649 this.queryParams.addAll(nvps);
650 this.encodedQuery = null;
651 this.encodedSchemeSpecificPart = null;
652 this.query = null;
653 return this;
654 }
655
656
657
658
659
660
661
662
663
664
665
666 public URIBuilder setParameters(final NameValuePair... nvps) {
667 if (this.queryParams == null) {
668 this.queryParams = new ArrayList<>();
669 } else {
670 this.queryParams.clear();
671 }
672 Collections.addAll(this.queryParams, nvps);
673 this.encodedQuery = null;
674 this.encodedSchemeSpecificPart = null;
675 this.query = null;
676 return this;
677 }
678
679
680
681
682
683
684
685
686
687
688
689 public URIBuilder addParameter(final String param, final String value) {
690 if (this.queryParams == null) {
691 this.queryParams = new ArrayList<>();
692 }
693 this.queryParams.add(new BasicNameValuePair(param, value));
694 this.encodedQuery = null;
695 this.encodedSchemeSpecificPart = null;
696 this.query = null;
697 return this;
698 }
699
700
701
702
703
704
705
706
707
708
709
710 public URIBuilder setParameter(final String param, final String value) {
711 if (this.queryParams == null) {
712 this.queryParams = new ArrayList<>();
713 }
714 if (!this.queryParams.isEmpty()) {
715 for (final Iterator<NameValuePair> it = this.queryParams.iterator(); it.hasNext(); ) {
716 final NameValuePair nvp = it.next();
717 if (nvp.getName().equals(param)) {
718 it.remove();
719 }
720 }
721 }
722 this.queryParams.add(new BasicNameValuePair(param, value));
723 this.encodedQuery = null;
724 this.encodedSchemeSpecificPart = null;
725 this.query = null;
726 return this;
727 }
728
729
730
731
732
733
734 public URIBuilder clearParameters() {
735 this.queryParams = null;
736 this.encodedQuery = null;
737 this.encodedSchemeSpecificPart = null;
738 return this;
739 }
740
741
742
743
744
745
746
747
748
749
750
751 public URIBuilder setCustomQuery(final String query) {
752 this.query = !TextUtils.isBlank(query) ? query : null;
753 this.encodedQuery = null;
754 this.encodedSchemeSpecificPart = null;
755 this.queryParams = null;
756 return this;
757 }
758
759
760
761
762
763
764
765 public URIBuilder setFragment(final String fragment) {
766 this.fragment = !TextUtils.isBlank(fragment) ? fragment : null;
767 this.encodedFragment = null;
768 return this;
769 }
770
771 public boolean isAbsolute() {
772 return this.scheme != null;
773 }
774
775 public boolean isOpaque() {
776 return this.pathSegments == null && this.encodedPath == null;
777 }
778
779 public String getScheme() {
780 return this.scheme;
781 }
782
783
784
785
786
787
788
789 public String getSchemeSpecificPart() {
790 return this.encodedSchemeSpecificPart;
791 }
792
793 public String getUserInfo() {
794 return this.userInfo;
795 }
796
797
798
799
800
801
802
803 public String getHost() {
804 return this.host;
805 }
806
807 public int getPort() {
808 return this.port;
809 }
810
811 public boolean isPathEmpty() {
812 return (this.pathSegments == null || this.pathSegments.isEmpty()) &&
813 (this.encodedPath == null || this.encodedPath.isEmpty());
814 }
815
816 public List<String> getPathSegments() {
817 return this.pathSegments != null ? new ArrayList<>(this.pathSegments) : new ArrayList<String>();
818 }
819
820 public String getPath() {
821 if (this.pathSegments == null) {
822 return null;
823 }
824 final StringBuilder result = new StringBuilder();
825 for (final String segment : this.pathSegments) {
826 result.append('/').append(segment);
827 }
828 return result.toString();
829 }
830
831 public boolean isQueryEmpty() {
832 return (this.queryParams == null || this.queryParams.isEmpty()) && this.encodedQuery == null;
833 }
834
835 public List<NameValuePair> getQueryParams() {
836 return this.queryParams != null ? new ArrayList<>(this.queryParams) : new ArrayList<NameValuePair>();
837 }
838
839 public String getFragment() {
840 return this.fragment;
841 }
842
843
844
845
846
847
848
849
850
851
852
853 public URIBuilder normalizeSyntax() {
854 final String scheme = this.scheme;
855 if (scheme != null) {
856 this.scheme = scheme.toLowerCase(Locale.ROOT);
857 }
858
859 if (this.pathRootless) {
860 return this;
861 }
862
863
864 this.encodedSchemeSpecificPart = null;
865 this.encodedAuthority = null;
866 this.encodedUserInfo = null;
867 this.encodedPath = null;
868 this.encodedQuery = null;
869 this.encodedFragment = null;
870
871 final String host = this.host;
872 if (host != null) {
873 this.host = host.toLowerCase(Locale.ROOT);
874 }
875
876 if (this.pathSegments != null) {
877 final List<String> inputSegments = this.pathSegments;
878 if (!inputSegments.isEmpty()) {
879 final Stack<String> outputSegments = new Stack<>();
880 for (final String inputSegment : inputSegments) {
881 if (!inputSegment.isEmpty() && !".".equals(inputSegment)) {
882 if ("..".equals(inputSegment)) {
883 if (!outputSegments.isEmpty()) {
884 outputSegments.pop();
885 }
886 } else {
887 outputSegments.push(inputSegment);
888 }
889 }
890 }
891 if (!inputSegments.isEmpty()) {
892 final String lastSegment = inputSegments.get(inputSegments.size() - 1);
893 if (lastSegment.isEmpty()) {
894 outputSegments.push("");
895 }
896 }
897 this.pathSegments = outputSegments;
898 } else {
899 this.pathSegments = Collections.singletonList("");
900 }
901 }
902
903 return this;
904 }
905
906 @Override
907 public String toString() {
908 return buildString();
909 }
910
911 }