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}