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_CREDENTIAL_ENCODING}.
153     */
154    public static Charset getHttpCredentialsEncoding(RepositorySystemSession session, RemoteRepository repository) {
155        return Charset.forName(ConfigUtils.getString(
156                session,
157                ConfigurationProperties.DEFAULT_HTTP_CREDENTIAL_ENCODING,
158                ConfigurationProperties.HTTP_CREDENTIAL_ENCODING + "." + repository.getId(),
159                ConfigurationProperties.HTTP_CREDENTIAL_ENCODING));
160    }
161
162    /**
163     * Getter for {@link ConfigurationProperties#CONNECT_TIMEOUT}.
164     */
165    public static int getHttpConnectTimeout(RepositorySystemSession session, RemoteRepository repository) {
166        return ConfigUtils.getInteger(
167                session,
168                ConfigurationProperties.DEFAULT_CONNECT_TIMEOUT,
169                ConfigurationProperties.CONNECT_TIMEOUT + "." + repository.getId(),
170                ConfigurationProperties.CONNECT_TIMEOUT);
171    }
172
173    /**
174     * Getter for {@link ConfigurationProperties#REQUEST_TIMEOUT}.
175     */
176    public static int getHttpRequestTimeout(RepositorySystemSession session, RemoteRepository repository) {
177        return ConfigUtils.getInteger(
178                session,
179                ConfigurationProperties.DEFAULT_REQUEST_TIMEOUT,
180                ConfigurationProperties.REQUEST_TIMEOUT + "." + repository.getId(),
181                ConfigurationProperties.REQUEST_TIMEOUT);
182    }
183
184    /**
185     * Getter for {@link ConfigurationProperties#HTTP_RETRY_HANDLER_COUNT}.
186     */
187    public static int getHttpRetryHandlerCount(RepositorySystemSession session, RemoteRepository repository) {
188        int result = ConfigUtils.getInteger(
189                session,
190                ConfigurationProperties.DEFAULT_HTTP_RETRY_HANDLER_COUNT,
191                ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT + "." + repository.getId(),
192                ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT);
193        if (result < 0) {
194            throw new IllegalArgumentException(
195                    ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT + " value must be >= 0");
196        }
197        return result;
198    }
199
200    /**
201     * Getter for {@link ConfigurationProperties#HTTP_RETRY_HANDLER_INTERVAL}.
202     */
203    public static long getHttpRetryHandlerInterval(RepositorySystemSession session, RemoteRepository repository) {
204        long result = ConfigUtils.getLong(
205                session,
206                ConfigurationProperties.DEFAULT_HTTP_RETRY_HANDLER_INTERVAL,
207                ConfigurationProperties.HTTP_RETRY_HANDLER_INTERVAL + "." + repository.getId(),
208                ConfigurationProperties.HTTP_RETRY_HANDLER_INTERVAL);
209        if (result < 0) {
210            throw new IllegalArgumentException(
211                    ConfigurationProperties.HTTP_RETRY_HANDLER_INTERVAL + " value must be >= 0");
212        }
213        return result;
214    }
215
216    /**
217     * Getter for {@link ConfigurationProperties#HTTP_RETRY_HANDLER_INTERVAL_MAX}.
218     */
219    public static long getHttpRetryHandlerIntervalMax(RepositorySystemSession session, RemoteRepository repository) {
220        long result = ConfigUtils.getLong(
221                session,
222                ConfigurationProperties.DEFAULT_HTTP_RETRY_HANDLER_INTERVAL_MAX,
223                ConfigurationProperties.HTTP_RETRY_HANDLER_INTERVAL_MAX + "." + repository.getId(),
224                ConfigurationProperties.HTTP_RETRY_HANDLER_INTERVAL_MAX);
225        if (result < 0) {
226            throw new IllegalArgumentException(
227                    ConfigurationProperties.HTTP_RETRY_HANDLER_INTERVAL_MAX + " value must be >= 0");
228        }
229        return result;
230    }
231
232    /**
233     * Getter for {@link ConfigurationProperties#HTTP_EXPECT_CONTINUE}.
234     */
235    public static Optional<Boolean> getHttpExpectContinue(
236            RepositorySystemSession session, RemoteRepository repository) {
237        String expectContinue = ConfigUtils.getString(
238                session,
239                null,
240                ConfigurationProperties.HTTP_EXPECT_CONTINUE + "." + repository.getId(),
241                ConfigurationProperties.HTTP_EXPECT_CONTINUE);
242        if (expectContinue != null) {
243            return Optional.of(Boolean.parseBoolean(expectContinue));
244        }
245        return Optional.empty();
246    }
247
248    /**
249     * Getter for {@link ConfigurationProperties#HTTP_REUSE_CONNECTIONS}.
250     */
251    public static boolean isHttpReuseConnections(RepositorySystemSession session, RemoteRepository repository) {
252        return ConfigUtils.getBoolean(
253                session,
254                ConfigurationProperties.DEFAULT_HTTP_REUSE_CONNECTIONS,
255                ConfigurationProperties.HTTP_REUSE_CONNECTIONS + "." + repository.getId(),
256                ConfigurationProperties.HTTP_REUSE_CONNECTIONS);
257    }
258
259    /**
260     * Getter for {@link ConfigurationProperties#HTTP_RETRY_HANDLER_SERVICE_UNAVAILABLE}.
261     */
262    public static Set<Integer> getHttpServiceUnavailableCodes(
263            RepositorySystemSession session, RemoteRepository repository) {
264        String stringValue = ConfigUtils.getString(
265                session,
266                ConfigurationProperties.DEFAULT_HTTP_RETRY_HANDLER_SERVICE_UNAVAILABLE,
267                ConfigurationProperties.HTTP_RETRY_HANDLER_SERVICE_UNAVAILABLE + "." + repository.getId(),
268                ConfigurationProperties.HTTP_RETRY_HANDLER_SERVICE_UNAVAILABLE);
269        Set<Integer> result = new HashSet<>();
270        try {
271            for (String code : ConfigUtils.parseCommaSeparatedUniqueNames(stringValue)) {
272                result.add(Integer.parseInt(code));
273            }
274        } catch (NumberFormatException e) {
275            throw new IllegalArgumentException(
276                    "Illegal HTTP codes for " + ConfigurationProperties.HTTP_RETRY_HANDLER_SERVICE_UNAVAILABLE
277                            + " (list of integers): " + stringValue);
278        }
279        return result;
280    }
281
282    /**
283     * Getter for {@link ConfigurationProperties#HTTP_LOCAL_ADDRESS}.
284     */
285    public static Optional<InetAddress> getHttpLocalAddress(
286            RepositorySystemSession session, RemoteRepository repository) {
287        String bindAddress = ConfigUtils.getString(
288                session,
289                null,
290                ConfigurationProperties.HTTP_LOCAL_ADDRESS + "." + repository.getId(),
291                ConfigurationProperties.HTTP_LOCAL_ADDRESS);
292        if (bindAddress != null) {
293            try {
294                return Optional.of(InetAddress.getByName(bindAddress));
295            } catch (UnknownHostException uhe) {
296                throw new IllegalArgumentException(
297                        "Given bind address (" + bindAddress + ") cannot be resolved for remote repository "
298                                + repository,
299                        uhe);
300            }
301        }
302        return Optional.empty();
303    }
304
305    /**
306     * Shared code to create "base {@link URI}" for most common HTTP remote repositories and all HTTP transports.
307     * Note: this method just applies common validation and adjustments to URI, but it does not enforce protocol
308     * to be HTTP/HTTPS!
309     * <p>
310     * Validations and adjustments applied:
311     * <ul>
312     *     <li>URI string is parsed from {@link RemoteRepository#getUrl()} returned string</li>
313     *     <li>URI must have parsable {@link URI#parseServerAuthority()}</li>
314     *     <li>URI must not be opaque</li>
315     *     <li>URI must not have fragment or query</li>
316     *     <li>URI path is adjusted to end with {@code /} (slash).</li>
317     * </ul>
318     *
319     * @since 2.0.18
320     */
321    public static URI getBaseUri(RemoteRepository repository) throws URISyntaxException {
322        URI uri = new URI(repository.getUrl()).parseServerAuthority();
323        if (uri.isOpaque()) {
324            throw new URISyntaxException(repository.getUrl(), "URL must not be opaque");
325        }
326        if (uri.getRawFragment() != null || uri.getRawQuery() != null) {
327            throw new URISyntaxException(repository.getUrl(), "URL must not have fragment or query");
328        }
329        String path = uri.getRawPath();
330        if (path == null) {
331            path = "/";
332        }
333        if (!path.startsWith("/")) {
334            path = "/" + path;
335        }
336        if (!path.endsWith("/")) {
337            path = path + "/";
338        }
339        return new URI(uri.getScheme() + "://" + uri.getRawAuthority() + path);
340    }
341}