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.apache.maven.wrapper;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.lang.reflect.Method;
24  import java.net.Authenticator;
25  import java.net.PasswordAuthentication;
26  import java.net.URI;
27  import java.net.URL;
28  import java.net.URLConnection;
29  import java.nio.charset.StandardCharsets;
30  import java.nio.file.Files;
31  import java.nio.file.Path;
32  import java.nio.file.StandardCopyOption;
33  import java.util.Locale;
34  
35  import static org.apache.maven.wrapper.MavenWrapperMain.MVNW_PASSWORD;
36  import static org.apache.maven.wrapper.MavenWrapperMain.MVNW_USERNAME;
37  
38  /**
39   * @author Hans Dockter
40   */
41  public class DefaultDownloader implements Downloader {
42      private final String applicationName;
43  
44      private final String applicationVersion;
45  
46      public DefaultDownloader(String applicationName, String applicationVersion) {
47          this.applicationName = applicationName;
48          this.applicationVersion = applicationVersion;
49          configureProxyAuthentication();
50          configureAuthentication();
51      }
52  
53      private void configureProxyAuthentication() {
54          if (System.getProperty("http.proxyUser") != null) {
55              Authenticator.setDefault(new SystemPropertiesProxyAuthenticator());
56          }
57      }
58  
59      private void configureAuthentication() {
60          if (System.getenv(MVNW_USERNAME) != null
61                  && System.getenv(MVNW_PASSWORD) != null
62                  && System.getProperty("http.proxyUser") == null) {
63              Authenticator.setDefault(new Authenticator() {
64                  @Override
65                  protected PasswordAuthentication getPasswordAuthentication() {
66                      return new PasswordAuthentication(
67                              System.getenv(MVNW_USERNAME),
68                              System.getenv(MVNW_PASSWORD).toCharArray());
69                  }
70              });
71          }
72      }
73  
74      @Override
75      public void download(URI address, Path destination) throws Exception {
76          if (Files.exists(destination)) {
77              return;
78          }
79          Files.createDirectories(destination.getParent());
80  
81          if (!"https".equals(address.getScheme())) {
82              Logger.warn("Using an insecure connection to download the Maven distribution."
83                      + " Please consider using HTTPS.");
84          }
85          downloadInternal(address, destination);
86      }
87  
88      private void downloadInternal(URI address, Path destination) throws IOException {
89          URL url = address.toURL();
90          URLConnection conn = url.openConnection();
91          addBasicAuthentication(address, conn);
92          final String userAgentValue = calculateUserAgent();
93          conn.setRequestProperty("User-Agent", userAgentValue);
94  
95          try (InputStream inStream = conn.getInputStream()) {
96              Files.copy(inStream, destination, StandardCopyOption.REPLACE_EXISTING);
97          }
98      }
99  
100     private void addBasicAuthentication(URI address, URLConnection connection) {
101         String userInfo = calculateUserInfo(address);
102         if (userInfo == null) {
103             return;
104         }
105         connection.setRequestProperty("Authorization", "Basic " + base64Encode(userInfo));
106     }
107 
108     /**
109      * Base64 encode user info for HTTP Basic Authentication. Try to use {@literal java.util.Base64} encoder which is
110      * available starting with Java 8. Fallback to {@literal javax.xml.bind.DatatypeConverter} from JAXB which is
111      * available starting with Java 6 but is not anymore in Java 9. Fortunately, both of these two Base64 encoders
112      * implement the right Base64 flavor, the one that does not split the output in multiple lines.
113      *
114      * @param userInfo user info
115      * @return Base64 encoded user info
116      * @throws RuntimeException if no public Base64 encoder is available on this JVM
117      */
118     private String base64Encode(String userInfo) {
119         ClassLoader loader = getClass().getClassLoader();
120         try {
121             Method getEncoderMethod = loader.loadClass("java.util.Base64").getMethod("getEncoder");
122             Method encodeMethod =
123                     loader.loadClass("java.util.Base64$Encoder").getMethod("encodeToString", byte[].class);
124             Object encoder = getEncoderMethod.invoke(null);
125             return (String) encodeMethod.invoke(encoder, new Object[] {userInfo.getBytes(StandardCharsets.UTF_8)});
126         } catch (Exception java7OrEarlier) {
127             try {
128                 Method encodeMethod = loader.loadClass("javax.xml.bind.DatatypeConverter")
129                         .getMethod("printBase64Binary", byte[].class);
130                 return (String) encodeMethod.invoke(null, new Object[] {userInfo.getBytes(StandardCharsets.UTF_8)});
131             } catch (Exception java5OrEarlier) {
132                 throw new RuntimeException(
133                         "Downloading Maven distributions with HTTP Basic Authentication"
134                                 + " is not supported on your JVM.",
135                         java5OrEarlier);
136             }
137         }
138     }
139 
140     private String calculateUserInfo(URI uri) {
141         String username = System.getenv(MVNW_USERNAME);
142         String password = System.getenv(MVNW_PASSWORD);
143         if (username != null && password != null) {
144             return username + ':' + password;
145         }
146         return uri.getUserInfo();
147     }
148 
149     private String calculateUserAgent() {
150         String appVersion = applicationVersion;
151 
152         String javaVendor = System.getProperty("java.vendor");
153         String javaVersion = System.getProperty("java.version");
154         String javaVendorVersion = System.getProperty("java.vm.version");
155         String osName = System.getProperty("os.name");
156         String osVersion = System.getProperty("os.version");
157         String osArch = System.getProperty("os.arch");
158         return String.format(
159                 Locale.ROOT,
160                 "%s/%s (%s;%s;%s) (%s;%s;%s)",
161                 applicationName,
162                 appVersion,
163                 osName,
164                 osVersion,
165                 osArch,
166                 javaVendor,
167                 javaVersion,
168                 javaVendorVersion);
169     }
170 
171     private static class SystemPropertiesProxyAuthenticator extends Authenticator {
172         @Override
173         protected PasswordAuthentication getPasswordAuthentication() {
174             return new PasswordAuthentication(
175                     System.getProperty("http.proxyUser"),
176                     System.getProperty("http.proxyPassword", "").toCharArray());
177         }
178     }
179 }