View Javadoc
1   /*
2    * Copyright (c) 2002-2026 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host.html;
16  
17  import java.net.MalformedURLException;
18  import java.net.URL;
19  
20  import org.htmlunit.util.StringUtils;
21  import org.htmlunit.util.UrlUtils;
22  
23  /**
24   * Implementation of the {@code HTMLHyperlinkElementUtils} mixin.
25   * Provides URL decomposition property logic
26   * @see <a href="https://html.spec.whatwg.org/multipage/links.html#htmlhyperlinkelementutils">
27   *      HTMLHyperlinkElementUtils</a>
28   *
29   * @author Lai Quang Duong
30   * @author Ronald Brill
31   */
32  final class HTMLHyperlinkElementUtils {
33  
34      /**
35       * Private ctor to keep Checkstyle happy
36       */
37      private HTMLHyperlinkElementUtils() {
38      }
39  
40      /**
41       * Returns the {@code search} component of the URL.
42       * @param url the URL
43       * @return the query string prefixed with {@code ?}, or empty string if no query
44       */
45      static String getSearch(final URL url) {
46          final String query = url.getQuery();
47          if (query == null) {
48              return "";
49          }
50          return "?" + query;
51      }
52  
53      /**
54       * Returns a new URL with the {@code search} component set.
55       * @param url the current URL
56       * @param search the new search value (with or without leading {@code ?})
57       * @return the new URL
58       * @throws MalformedURLException if an error occurs
59       */
60      static URL setSearch(final URL url, final String search) throws MalformedURLException {
61          final String query;
62          if (search == null
63                  || StringUtils.isEmptyString(search)
64                  || StringUtils.equalsChar('?', search)) {
65              query = null;
66          }
67          else if (search.charAt(0) == '?') {
68              query = search.substring(1);
69          }
70          else {
71              query = search;
72          }
73          return UrlUtils.getUrlWithNewQuery(url, query);
74      }
75  
76      /**
77       * Returns the {@code hash} component of the URL.
78       * @param url the URL
79       * @return the fragment prefixed with {@code #}, or empty string if no fragment
80       */
81      static String getHash(final URL url) {
82          final String hash = url.getRef();
83          if (hash == null) {
84              return "";
85          }
86          return "#" + hash;
87      }
88  
89      /**
90       * Returns a new URL with the {@code hash} component set.
91       * @param url the current URL
92       * @param hash the new hash value
93       * @return the new URL
94       * @throws MalformedURLException if an error occurs
95       */
96      static URL setHash(final URL url, final String hash) throws MalformedURLException {
97          return UrlUtils.getUrlWithNewRef(url, hash);
98      }
99  
100     /**
101      * Returns the {@code hostname} component of the URL.
102      * @param url the URL
103      * @return the hostname
104      */
105     static String getHostname(final URL url) {
106         return UrlUtils.encodeAnchor(url.getHost());
107     }
108 
109     /**
110      * Returns a new URL with the {@code host} component set.
111      * Parses the host string for an optional {@code :port} suffix.
112      * @param url the current URL
113      * @param host the new host value (e.g. {@code "example.com:8080"})
114      * @return the new URL
115      * @throws MalformedURLException if an error occurs
116      */
117     static URL setHost(final URL url, final String host) throws MalformedURLException {
118         final String hostname;
119         final int port;
120         final int index = host.indexOf(':');
121         if (index != -1) {
122             hostname = host.substring(0, index);
123             port = Integer.parseInt(host.substring(index + 1));
124         }
125         else {
126             hostname = host;
127             port = -1;
128         }
129         return UrlUtils.getUrlWithNewHostAndPort(url, hostname, port);
130     }
131 
132     /**
133      * Returns a new URL with the {@code protocol} set, or {@code null}
134      * if the protocol is invalid or should not be applied.
135      * @param url the current URL
136      * @param protocol the new protocol value (with or without trailing {@code :})
137      * @return the new URL, or {@code null} if the protocol is invalid
138      */
139     static URL setProtocol(final URL url, final String protocol) {
140         if (protocol.isEmpty()) {
141             return null;
142         }
143 
144         final String bareProtocol = StringUtils.substringBefore(protocol, ":").trim();
145         if (!UrlUtils.isValidScheme(bareProtocol)) {
146             return null;
147         }
148         if (!UrlUtils.isSpecialScheme(bareProtocol)) {
149             return null;
150         }
151 
152         try {
153             URL result = UrlUtils.getUrlWithNewProtocol(url, bareProtocol);
154             result = UrlUtils.removeRedundantPort(result);
155             return result;
156         }
157         catch (final MalformedURLException ignored) {
158             return null;
159         }
160     }
161 
162     /**
163      * Returns a new URL with the {@code pathname} set.
164      * @param url the current URL
165      * @param pathname the new pathname value
166      * @return the new URL
167      * @throws MalformedURLException if an error occurs
168      */
169     static URL setPathname(final URL url, final String pathname) throws MalformedURLException {
170         return UrlUtils.getUrlWithNewPath(url, pathname);
171     }
172 
173     /**
174      * Returns the {@code username} component of the URL.
175      * @param url the URL
176      * @return the username, or empty string if no user info
177      */
178     static String getUsername(final URL url) {
179         final String userInfo = url.getUserInfo();
180         if (userInfo == null) {
181             return "";
182         }
183         return StringUtils.substringBefore(userInfo, ":");
184     }
185 
186     /**
187      * Returns the {@code password} component of the URL.
188      * @param url the URL
189      * @return the password, or empty string if no user info
190      */
191     static String getPassword(final URL url) {
192         final String userInfo = url.getUserInfo();
193         if (userInfo == null) {
194             return "";
195         }
196         return StringUtils.substringAfter(userInfo, ":");
197     }
198 
199     /**
200      * Checks whether the given port is the default port for the protocol.
201      * @param protocol the protocol (e.g. {@code "http"}, {@code "https"})
202      * @param port the port number
203      * @return {@code true} if the port is the default for the protocol
204      */
205     static boolean isDefaultPort(final String protocol, final int port) {
206         return ("http".equals(protocol) && port == 80)
207                 || ("https".equals(protocol) && port == 443);
208     }
209 }