@@ -197,7 +197,19 @@ public static UriComponentsBuilder fromUri(URI uri) {
197197 }
198198
199199 /**
200- * Create a builder that is initialized with the given URI string.
200+ * Variant of {@link #fromUriString(String, ParserType)} that defaults to
201+ * the {@link ParserType#RFC} parsing.
202+ */
203+ public static UriComponentsBuilder fromUriString (String uri ) throws InvalidUrlException {
204+ Assert .notNull (uri , "URI must not be null" );
205+ if (uri .isEmpty ()) {
206+ return new UriComponentsBuilder ();
207+ }
208+ return fromUriString (uri , ParserType .RFC );
209+ }
210+
211+ /**
212+ * Create a builder that is initialized by parsing the given URI string.
201213 * <p><strong>Note:</strong> The presence of reserved characters can prevent
202214 * correct parsing of the URI string. For example if a query parameter
203215 * contains {@code '='} or {@code '&'} characters, the query string cannot
@@ -208,47 +220,27 @@ public static UriComponentsBuilder fromUri(URI uri) {
208220 * UriComponentsBuilder.fromUriString(uriString).buildAndExpand("hot&cold");
209221 * </pre>
210222 * @param uri the URI string to initialize with
223+ * @param parserType the parsing algorithm to use
211224 * @return the new {@code UriComponentsBuilder}
212225 * @throws InvalidUrlException if {@code uri} cannot be parsed
226+ * @since 6.2
213227 */
214- public static UriComponentsBuilder fromUriString (String uri ) throws InvalidUrlException {
228+ public static UriComponentsBuilder fromUriString (String uri , ParserType parserType ) throws InvalidUrlException {
215229 Assert .notNull (uri , "URI must not be null" );
216-
230+ if (uri .isEmpty ()) {
231+ return new UriComponentsBuilder ();
232+ }
217233 UriComponentsBuilder builder = new UriComponentsBuilder ();
218- if (!uri .isEmpty ()) {
219- WhatWgUrlParser .UrlRecord urlRecord = WhatWgUrlParser .parse (uri , EMPTY_URL_RECORD , null , null );
220- if (!urlRecord .scheme ().isEmpty ()) {
221- builder .scheme (urlRecord .scheme ());
222- }
223- if (urlRecord .includesCredentials ()) {
224- StringBuilder userInfo = new StringBuilder (urlRecord .username ());
225- if (!urlRecord .password ().isEmpty ()) {
226- userInfo .append (':' );
227- userInfo .append (urlRecord .password ());
228- }
229- builder .userInfo (userInfo .toString ());
234+ return switch (parserType ) {
235+ case RFC -> {
236+ RfcUriParser .UriRecord record = RfcUriParser .parse (uri );
237+ yield builder .rfcUriRecord (record );
230238 }
231- if (urlRecord .host () != null && !(urlRecord .host () instanceof WhatWgUrlParser .EmptyHost )) {
232- builder .host (urlRecord .host ().toString ());
239+ case WHAT_WG -> {
240+ WhatWgUrlParser .UrlRecord record = WhatWgUrlParser .parse (uri , EMPTY_URL_RECORD , null , null );
241+ yield builder .whatWgUrlRecord (record );
233242 }
234- if (urlRecord .port () != null ) {
235- builder .port (urlRecord .port ().toString ());
236- }
237- if (urlRecord .path ().isOpaque ()) {
238- String ssp = urlRecord .path () + urlRecord .search ();
239- builder .schemeSpecificPart (ssp );
240- }
241- else {
242- builder .path (urlRecord .path ().toString ());
243- if (StringUtils .hasLength (urlRecord .query ())) {
244- builder .query (urlRecord .query ());
245- }
246- }
247- if (StringUtils .hasLength (urlRecord .fragment ())) {
248- builder .fragment (urlRecord .fragment ());
249- }
250- }
251- return builder ;
243+ };
252244 }
253245
254246 /**
@@ -517,6 +509,58 @@ public UriComponentsBuilder uriComponents(UriComponents uriComponents) {
517509 return this ;
518510 }
519511
512+ /**
513+ * Internal method to initialize this builder from an RFC {@code UriRecord}.
514+ */
515+ private UriComponentsBuilder rfcUriRecord (RfcUriParser .UriRecord record ) {
516+ scheme (record .scheme ());
517+ if (record .isOpaque ()) {
518+ if (record .path () != null ) {
519+ schemeSpecificPart (record .path ());
520+ }
521+ }
522+ else {
523+ userInfo (record .user ());
524+ host (record .host ());
525+ port (record .port ());
526+ if (record .path () != null ) {
527+ path (record .path ());
528+ }
529+ query (record .query ());
530+ }
531+ fragment (record .fragment ());
532+ return this ;
533+ }
534+
535+ /**
536+ * Internal method to initialize this builder from a WhatWG {@code UrlRecord}.
537+ */
538+ private UriComponentsBuilder whatWgUrlRecord (WhatWgUrlParser .UrlRecord record ) {
539+ if (!record .scheme ().isEmpty ()) {
540+ scheme (record .scheme ());
541+ }
542+ if (record .path ().isOpaque ()) {
543+ String ssp = record .path () + record .search ();
544+ schemeSpecificPart (ssp );
545+ }
546+ else {
547+ userInfo (record .userInfo ());
548+ String hostname = record .hostname ();
549+ if (StringUtils .hasText (hostname )) {
550+ host (hostname );
551+ }
552+ if (record .port () != null ) {
553+ port (record .portString ());
554+ }
555+ path (record .path ().toString ());
556+ query (record .query ());
557+ if (StringUtils .hasText (record .fragment ())) {
558+ fragment (record .fragment ());
559+ }
560+ }
561+ return this ;
562+ }
563+
520564 @ Override
521565 public UriComponentsBuilder scheme (@ Nullable String scheme ) {
522566 this .scheme = scheme ;
@@ -790,6 +834,34 @@ private interface PathComponentBuilder {
790834 }
791835
792836
837+ /**
838+ * Enum to represent different URI parsing mechanisms.
839+ */
840+ public enum ParserType {
841+
842+ /**
843+ * Parser that expects URI's conforming to RFC 3986 syntax.
844+ */
845+ RFC ,
846+
847+ /**
848+ * Parser based on algorithm defined in the WhatWG URL Living standard.
849+ * Browsers use this algorithm to align on lenient parsing of user typed
850+ * URL's that may deviate from RFC syntax.
851+ * <p>For more details, see:
852+ * <ul>
853+ * <li><a href="https://url.spec.whatwg.org">URL Living Standard</a>
854+ * <li><a href="https://url.spec.whatwg.org/#url-parsing">Section 4.4: URL parsing</a>
855+ * <li><a href="https://github.com/web-platform-tests/wpt/tree/master/url">web-platform-tests</a>
856+ * </ul>
857+ * <p>Use this if you need to leniently handle URL's that don't conform
858+ * to RFC syntax, or for alignment with browser parsing.
859+ */
860+ WHAT_WG
861+
862+ }
863+
864+
793865 private static class CompositePathComponentBuilder implements PathComponentBuilder {
794866
795867 private final Deque <PathComponentBuilder > builders = new ArrayDeque <>();
0 commit comments