001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.util.connector.transport.http;
020
021import java.net.InetAddress;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.net.UnknownHostException;
025import java.nio.charset.Charset;
026import java.util.Collections;
027import java.util.HashSet;
028import java.util.Map;
029import java.util.Optional;
030import java.util.Set;
031
032import org.eclipse.aether.ConfigurationProperties;
033import org.eclipse.aether.RepositorySystemSession;
034import org.eclipse.aether.repository.RemoteRepository;
035import org.eclipse.aether.util.ConfigUtils;
036
037/**
038 * A utility class to read HTTP transport related configuration. It implements all HTTP transport related configurations from
039 * {@link ConfigurationProperties} and transport implementations are free to use those that are supported by themselves.
040 *
041 * @see ConfigurationProperties
042 * @see RepositorySystemSession#getConfigProperties()
043 * @since 2.0.15
044 */
045public final class HttpTransporterUtils {
046    private HttpTransporterUtils() {}
047
048    /**
049     * Getter for {@link ConfigurationProperties#USER_AGENT}.
050     */
051    public static String getUserAgent(RepositorySystemSession session, RemoteRepository repository) {
052        return ConfigUtils.getString(
053                session,
054                ConfigurationProperties.DEFAULT_USER_AGENT,
055                ConfigurationProperties.USER_AGENT,
056                "aether.connector.userAgent");
057    }
058
059    /**
060     * Getter for {@link ConfigurationProperties#HTTPS_SECURITY_MODE}.
061     */
062    public static String getHttpsSecurityMode(RepositorySystemSession session, RemoteRepository repository) {
063        String result = ConfigUtils.getString(
064                session,
065                ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT,
066                ConfigurationProperties.HTTPS_SECURITY_MODE + "." + repository.getId(),
067                ConfigurationProperties.HTTPS_SECURITY_MODE);
068        if (!ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT.equals(result)
069                && !ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(result)) {
070            throw new IllegalArgumentException("Unsupported '" + result + "' HTTPS security mode.");
071        }
072        return result;
073    }
074
075    /**
076     * Getter for {@link ConfigurationProperties#HTTP_CONNECTION_MAX_TTL}.
077     */
078    public static int getHttpConnectionMaxTtlSeconds(RepositorySystemSession session, RemoteRepository repository) {
079        int result = ConfigUtils.getInteger(
080                session,
081                ConfigurationProperties.DEFAULT_HTTP_CONNECTION_MAX_TTL,
082                ConfigurationProperties.HTTP_CONNECTION_MAX_TTL + "." + repository.getId(),
083                ConfigurationProperties.HTTP_CONNECTION_MAX_TTL);
084        if (result < 0) {
085            throw new IllegalArgumentException(ConfigurationProperties.HTTP_CONNECTION_MAX_TTL + " value must be >= 0");
086        }
087        return result;
088    }
089
090    /**
091     * Getter for {@link ConfigurationProperties#HTTP_MAX_CONNECTIONS_PER_ROUTE}.
092     */
093    public static int getHttpMaxConnectionsPerRoute(RepositorySystemSession session, RemoteRepository repository) {
094        int result = ConfigUtils.getInteger(
095                session,
096                ConfigurationProperties.DEFAULT_HTTP_MAX_CONNECTIONS_PER_ROUTE,
097                ConfigurationProperties.HTTP_MAX_CONNECTIONS_PER_ROUTE + "." + repository.getId(),
098                ConfigurationProperties.HTTP_MAX_CONNECTIONS_PER_ROUTE);
099        if (result < 1) {
100            throw new IllegalArgumentException(
101                    ConfigurationProperties.HTTP_MAX_CONNECTIONS_PER_ROUTE + " value must be > 0");
102        }
103        return result;
104    }
105
106    /**
107     * Getter for {@link ConfigurationProperties#HTTP_HEADERS}.
108     */
109    @SuppressWarnings("unchecked")
110    public static Map<String, String> getHttpHeaders(RepositorySystemSession session, RemoteRepository repository) {
111        return (Map<String, String>) ConfigUtils.getMap(
112                session,
113                Collections.emptyMap(),
114                ConfigurationProperties.HTTP_HEADERS + "." + repository.getId(),
115                ConfigurationProperties.HTTP_HEADERS);
116    }
117
118    /**
119     * Getter for {@link ConfigurationProperties#HTTP_PREEMPTIVE_AUTH}.
120     */
121    public static boolean isHttpPreemptiveAuth(RepositorySystemSession session, RemoteRepository repository) {
122        return ConfigUtils.getBoolean(
123                session,
124                ConfigurationProperties.DEFAULT_HTTP_PREEMPTIVE_AUTH,
125                ConfigurationProperties.HTTP_PREEMPTIVE_AUTH + "." + repository.getId(),
126                ConfigurationProperties.HTTP_PREEMPTIVE_AUTH);
127    }
128
129    /**
130     * Getter for {@link ConfigurationProperties#HTTP_PREEMPTIVE_PUT_AUTH}.
131     */
132    public static boolean isHttpPreemptivePutAuth(RepositorySystemSession session, RemoteRepository repository) {
133        return ConfigUtils.getBoolean(
134                session,
135                ConfigurationProperties.DEFAULT_HTTP_PREEMPTIVE_PUT_AUTH,
136                ConfigurationProperties.HTTP_PREEMPTIVE_PUT_AUTH + "." + repository.getId(),
137                ConfigurationProperties.HTTP_PREEMPTIVE_PUT_AUTH);
138    }
139
140    /**
141     * Getter for {@link ConfigurationProperties#HTTP_SUPPORT_WEBDAV}.
142     */
143    public static boolean isHttpSupportWebDav(RepositorySystemSession session, RemoteRepository repository) {
144        return ConfigUtils.getBoolean(
145                session,
146                ConfigurationProperties.DEFAULT_HTTP_SUPPORT_WEBDAV,
147                ConfigurationProperties.HTTP_SUPPORT_WEBDAV + "." + repository.getId(),
148                ConfigurationProperties.HTTP_SUPPORT_WEBDAV);
149    }
150
151    /**
152     * Getter for {@link ConfigurationProperties#HTTP_SEND_RFC9457_ACCEPT}.
153     *
154     * @since 2.0.19
155     */
156    public static boolean isHttpSendRfc9457Accept(RepositorySystemSession session, RemoteRepository repository) {
157        return ConfigUtils.getBoolean(
158                session,
159                ConfigurationProperties.DEFAULT_HTTP_SEND_RFC9457_ACCEPT,
160                ConfigurationProperties.HTTP_SEND_RFC9457_ACCEPT + "." + repository.getId(),
161                ConfigurationProperties.HTTP_SEND_RFC9457_ACCEPT);
162    }
163
164    /**
165     * Getter for {@link ConfigurationProperties#HTTP_CREDENTIAL_ENCODING}.
166     */
167    public static Charset getHttpCredentialsEncoding(RepositorySystemSession session, RemoteRepository repository) {
168        return Charset.forName(ConfigUtils.getString(
169                session,
170                ConfigurationProperties.DEFAULT_HTTP_CREDENTIAL_ENCODING,
171                ConfigurationProperties.HTTP_CREDENTIAL_ENCODING + "." + repository.getId(),
172                ConfigurationProperties.HTTP_CREDENTIAL_ENCODING));
173    }
174
175    /**
176     * Getter for {@link ConfigurationProperties#CONNECT_TIMEOUT}.
177     */
178    public static int getHttpConnectTimeout(RepositorySystemSession session, RemoteRepository repository) {
179        return ConfigUtils.getInteger(
180                session,
181                ConfigurationProperties.DEFAULT_CONNECT_TIMEOUT,
182                ConfigurationProperties.CONNECT_TIMEOUT + "." + repository.getId(),
183                ConfigurationProperties.CONNECT_TIMEOUT);
184    }
185
186    /**
187     * Getter for {@link ConfigurationProperties#REQUEST_TIMEOUT}.
188     */
189    public static int getHttpRequestTimeout(RepositorySystemSession session, RemoteRepository repository) {
190        return ConfigUtils.getInteger(
191                session,
192                ConfigurationProperties.DEFAULT_REQUEST_TIMEOUT,
193                ConfigurationProperties.REQUEST_TIMEOUT + "." + repository.getId(),
194                ConfigurationProperties.REQUEST_TIMEOUT);
195    }
196
197    /**
198     * Getter for {@link ConfigurationProperties#HTTP_RETRY_HANDLER_COUNT}.
199     */
200    public static int getHttpRetryHandlerCount(RepositorySystemSession session, RemoteRepository repository) {
201        int result = ConfigUtils.getInteger(
202                session,
203                ConfigurationProperties.DEFAULT_HTTP_RETRY_HANDLER_COUNT,
204                ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT + "." + repository.getId(),
205                ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT);
206        if (result < 0) {
207            throw new IllegalArgumentException(
208                    ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT + " value must be >= 0");
209        }
210        return result;
211    }
212
213    /**
214     * Getter for {@link ConfigurationProperties#HTTP_RETRY_HANDLER_INTERVAL}.
215     */
216    public static long getHttpRetryHandlerInterval(RepositorySystemSession session, RemoteRepository repository) {
217        long result = ConfigUtils.getLong(
218                session,
219                ConfigurationProperties.DEFAULT_HTTP_RETRY_HANDLER_INTERVAL,
220                ConfigurationProperties.HTTP_RETRY_HANDLER_INTERVAL + "." + repository.getId(),
221                ConfigurationProperties.HTTP_RETRY_HANDLER_INTERVAL);
222        if (result < 0) {
223            throw new IllegalArgumentException(
224                    ConfigurationProperties.HTTP_RETRY_HANDLER_INTERVAL + " value must be >= 0");
225        }
226        return result;
227    }
228
229    /**
230     * Getter for {@link ConfigurationProperties#HTTP_RETRY_HANDLER_INTERVAL_MAX}.
231     */
232    public static long getHttpRetryHandlerIntervalMax(RepositorySystemSession session, RemoteRepository repository) {
233        long result = ConfigUtils.getLong(
234                session,
235                ConfigurationProperties.DEFAULT_HTTP_RETRY_HANDLER_INTERVAL_MAX,
236                ConfigurationProperties.HTTP_RETRY_HANDLER_INTERVAL_MAX + "." + repository.getId(),
237                ConfigurationProperties.HTTP_RETRY_HANDLER_INTERVAL_MAX);
238        if (result < 0) {
239            throw new IllegalArgumentException(
240                    ConfigurationProperties.HTTP_RETRY_HANDLER_INTERVAL_MAX + " value must be >= 0");
241        }
242        return result;
243    }
244
245    /**
246     * Getter for {@link ConfigurationProperties#HTTP_EXPECT_CONTINUE}.
247     */
248    public static Optional<Boolean> getHttpExpectContinue(
249            RepositorySystemSession session, RemoteRepository repository) {
250        String expectContinue = ConfigUtils.getString(
251                session,
252                null,
253                ConfigurationProperties.HTTP_EXPECT_CONTINUE + "." + repository.getId(),
254                ConfigurationProperties.HTTP_EXPECT_CONTINUE);
255        if (expectContinue != null) {
256            return Optional.of(Boolean.parseBoolean(expectContinue));
257        }
258        return Optional.empty();
259    }
260
261    /**
262     * Getter for {@link ConfigurationProperties#HTTP_REUSE_CONNECTIONS}.
263     */
264    public static boolean isHttpReuseConnections(RepositorySystemSession session, RemoteRepository repository) {
265        return ConfigUtils.getBoolean(
266                session,
267                ConfigurationProperties.DEFAULT_HTTP_REUSE_CONNECTIONS,
268                ConfigurationProperties.HTTP_REUSE_CONNECTIONS + "." + repository.getId(),
269                ConfigurationProperties.HTTP_REUSE_CONNECTIONS);
270    }
271
272    /**
273     * Getter for {@link ConfigurationProperties#HTTP_RETRY_HANDLER_SERVICE_UNAVAILABLE}.
274     */
275    public static Set<Integer> getHttpServiceUnavailableCodes(
276            RepositorySystemSession session, RemoteRepository repository) {
277        String stringValue = ConfigUtils.getString(
278                session,
279                ConfigurationProperties.DEFAULT_HTTP_RETRY_HANDLER_SERVICE_UNAVAILABLE,
280                ConfigurationProperties.HTTP_RETRY_HANDLER_SERVICE_UNAVAILABLE + "." + repository.getId(),
281                ConfigurationProperties.HTTP_RETRY_HANDLER_SERVICE_UNAVAILABLE);
282        Set<Integer> result = new HashSet<>();
283        try {
284            for (String code : ConfigUtils.parseCommaSeparatedUniqueNames(stringValue)) {
285                result.add(Integer.parseInt(code));
286            }
287        } catch (NumberFormatException e) {
288            throw new IllegalArgumentException(
289                    "Illegal HTTP codes for " + ConfigurationProperties.HTTP_RETRY_HANDLER_SERVICE_UNAVAILABLE
290                            + " (list of integers): " + stringValue);
291        }
292        return result;
293    }
294
295    /**
296     * Getter for {@link ConfigurationProperties#HTTP_LOCAL_ADDRESS}.
297     */
298    public static Optional<InetAddress> getHttpLocalAddress(
299            RepositorySystemSession session, RemoteRepository repository) {
300        String bindAddress = ConfigUtils.getString(
301                session,
302                null,
303                ConfigurationProperties.HTTP_LOCAL_ADDRESS + "." + repository.getId(),
304                ConfigurationProperties.HTTP_LOCAL_ADDRESS);
305        if (bindAddress != null) {
306            try {
307                return Optional.of(InetAddress.getByName(bindAddress));
308            } catch (UnknownHostException uhe) {
309                throw new IllegalArgumentException(
310                        "Given bind address (" + bindAddress + ") cannot be resolved for remote repository "
311                                + repository,
312                        uhe);
313            }
314        }
315        return Optional.empty();
316    }
317
318    /**
319     * Shared code to create "base {@link URI}" for most common HTTP remote repositories and all HTTP transports.
320     * Note: this method just applies common validation and adjustments to URI, but it does not enforce protocol
321     * to be HTTP/HTTPS!
322     * <p>
323     * Validations and adjustments applied:
324     * <ul>
325     *     <li>URI string is parsed from {@link RemoteRepository#getUrl()} returned string</li>
326     *     <li>URI must have parsable {@link URI#parseServerAuthority()}</li>
327     *     <li>URI must not be opaque</li>
328     *     <li>URI must not have fragment or query</li>
329     *     <li>URI path is adjusted to end with {@code /} (slash).</li>
330     * </ul>
331     *
332     * @since 2.0.18
333     */
334    public static URI getBaseUri(RemoteRepository repository) throws URISyntaxException {
335        URI uri = new URI(repository.getUrl()).parseServerAuthority();
336        if (uri.isOpaque()) {
337            throw new URISyntaxException(repository.getUrl(), "URL must not be opaque");
338        }
339        if (uri.getRawFragment() != null || uri.getRawQuery() != null) {
340            throw new URISyntaxException(repository.getUrl(), "URL must not have fragment or query");
341        }
342        String path = uri.getRawPath();
343        if (path == null) {
344            path = "/";
345        }
346        if (!path.startsWith("/")) {
347            path = "/" + path;
348        }
349        if (!path.endsWith("/")) {
350            path = path + "/";
351        }
352        return new URI(uri.getScheme() + "://" + uri.getRawAuthority() + path);
353    }
354}