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 }