View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.util.connector.transport.http;
20  
21  import java.net.InetAddress;
22  import java.net.URI;
23  import java.net.URISyntaxException;
24  import java.net.UnknownHostException;
25  import java.nio.charset.Charset;
26  import java.util.Collections;
27  import java.util.HashSet;
28  import java.util.Map;
29  import java.util.Optional;
30  import java.util.Set;
31  
32  import org.eclipse.aether.ConfigurationProperties;
33  import org.eclipse.aether.RepositorySystemSession;
34  import org.eclipse.aether.repository.RemoteRepository;
35  import org.eclipse.aether.util.ConfigUtils;
36  
37  /**
38   * A utility class to read HTTP transport related configuration. It implements all HTTP transport related configurations from
39   * {@link ConfigurationProperties} and transport implementations are free to use those that are supported by themselves.
40   *
41   * @see ConfigurationProperties
42   * @see RepositorySystemSession#getConfigProperties()
43   * @since 2.0.15
44   */
45  public final class HttpTransporterUtils {
46      private HttpTransporterUtils() {}
47  
48      /**
49       * Getter for {@link ConfigurationProperties#USER_AGENT}.
50       */
51      public static String getUserAgent(RepositorySystemSession session, RemoteRepository repository) {
52          return ConfigUtils.getString(
53                  session,
54                  ConfigurationProperties.DEFAULT_USER_AGENT,
55                  ConfigurationProperties.USER_AGENT,
56                  "aether.connector.userAgent");
57      }
58  
59      /**
60       * Getter for {@link ConfigurationProperties#HTTPS_SECURITY_MODE}.
61       */
62      public static String getHttpsSecurityMode(RepositorySystemSession session, RemoteRepository repository) {
63          String result = ConfigUtils.getString(
64                  session,
65                  ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT,
66                  ConfigurationProperties.HTTPS_SECURITY_MODE + "." + repository.getId(),
67                  ConfigurationProperties.HTTPS_SECURITY_MODE);
68          if (!ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT.equals(result)
69                  && !ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(result)) {
70              throw new IllegalArgumentException("Unsupported '" + result + "' HTTPS security mode.");
71          }
72          return result;
73      }
74  
75      /**
76       * Getter for {@link ConfigurationProperties#HTTP_CONNECTION_MAX_TTL}.
77       */
78      public static int getHttpConnectionMaxTtlSeconds(RepositorySystemSession session, RemoteRepository repository) {
79          int result = ConfigUtils.getInteger(
80                  session,
81                  ConfigurationProperties.DEFAULT_HTTP_CONNECTION_MAX_TTL,
82                  ConfigurationProperties.HTTP_CONNECTION_MAX_TTL + "." + repository.getId(),
83                  ConfigurationProperties.HTTP_CONNECTION_MAX_TTL);
84          if (result < 0) {
85              throw new IllegalArgumentException(ConfigurationProperties.HTTP_CONNECTION_MAX_TTL + " value must be >= 0");
86          }
87          return result;
88      }
89  
90      /**
91       * Getter for {@link ConfigurationProperties#HTTP_MAX_CONNECTIONS_PER_ROUTE}.
92       */
93      public static int getHttpMaxConnectionsPerRoute(RepositorySystemSession session, RemoteRepository repository) {
94          int result = ConfigUtils.getInteger(
95                  session,
96                  ConfigurationProperties.DEFAULT_HTTP_MAX_CONNECTIONS_PER_ROUTE,
97                  ConfigurationProperties.HTTP_MAX_CONNECTIONS_PER_ROUTE + "." + repository.getId(),
98                  ConfigurationProperties.HTTP_MAX_CONNECTIONS_PER_ROUTE);
99          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 }