View Javadoc
1   package org.apache.maven.wrapper;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import static org.apache.maven.wrapper.MavenWrapperMain.MVNW_PASSWORD;
23  import static org.apache.maven.wrapper.MavenWrapperMain.MVNW_USERNAME;
24  
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.lang.reflect.Method;
28  import java.net.Authenticator;
29  import java.net.PasswordAuthentication;
30  import java.net.URI;
31  import java.net.URL;
32  import java.net.URLConnection;
33  import java.nio.charset.StandardCharsets;
34  import java.nio.file.Files;
35  import java.nio.file.Path;
36  import java.nio.file.StandardCopyOption;
37  import java.util.Locale;
38  
39  /**
40   * @author Hans Dockter
41   */
42  public class DefaultDownloader
43      implements Downloader
44  {
45      private final String applicationName;
46  
47      private final String applicationVersion;
48  
49      public DefaultDownloader( String applicationName, String applicationVersion )
50      {
51          this.applicationName = applicationName;
52          this.applicationVersion = applicationVersion;
53          configureProxyAuthentication();
54          configureAuthentication();
55      }
56  
57      private void configureProxyAuthentication()
58      {
59          if ( System.getProperty( "http.proxyUser" ) != null )
60          {
61              Authenticator.setDefault( new SystemPropertiesProxyAuthenticator() );
62          }
63      }
64  
65      private void configureAuthentication()
66      {
67          if ( System.getenv( MVNW_USERNAME ) != null && System.getenv( MVNW_PASSWORD ) != null
68              && System.getProperty( "http.proxyUser" ) == null )
69          {
70              Authenticator.setDefault( new Authenticator()
71              {
72                  @Override
73                  protected PasswordAuthentication getPasswordAuthentication()
74                  {
75                      return new PasswordAuthentication( System.getenv( MVNW_USERNAME ),
76                                                         System.getenv( MVNW_PASSWORD ).toCharArray() );
77                  }
78              } );
79          }
80      }
81  
82      @Override
83      public void download( URI address, Path destination )
84          throws Exception
85      {
86          if ( Files.exists( destination ) )
87          {
88              return;
89          }
90          Files.createDirectories( destination.getParent() );
91  
92          if ( !"https".equals( address.getScheme() ) )
93          {
94              Logger.warn( "Using an insecure connection to download the Maven distribution."
95                  + " Please consider using HTTPS." );
96          }
97          downloadInternal( address, destination );
98      }
99  
100     private void downloadInternal( URI address, Path destination )
101         throws IOException
102     {
103         URL url = address.toURL();
104         URLConnection conn = url.openConnection();
105         addBasicAuthentication( address, conn );
106         final String userAgentValue = calculateUserAgent();
107         conn.setRequestProperty( "User-Agent", userAgentValue );
108 
109         try ( InputStream inStream = conn.getInputStream() )
110         {
111             Files.copy( inStream, destination, StandardCopyOption.REPLACE_EXISTING );
112         }
113     }
114 
115     private void addBasicAuthentication( URI address, URLConnection connection )
116     {
117         String userInfo = calculateUserInfo( address );
118         if ( userInfo == null )
119         {
120             return;
121         }
122         connection.setRequestProperty( "Authorization", "Basic " + base64Encode( userInfo ) );
123     }
124 
125     /**
126      * Base64 encode user info for HTTP Basic Authentication. Try to use {@literal java.util.Base64} encoder which is
127      * available starting with Java 8. Fallback to {@literal javax.xml.bind.DatatypeConverter} from JAXB which is
128      * available starting with Java 6 but is not anymore in Java 9. Fortunately, both of these two Base64 encoders
129      * implement the right Base64 flavor, the one that does not split the output in multiple lines.
130      *
131      * @param userInfo user info
132      * @return Base64 encoded user info
133      * @throws RuntimeException if no public Base64 encoder is available on this JVM
134      */
135     private String base64Encode( String userInfo )
136     {
137         ClassLoader loader = getClass().getClassLoader();
138         try
139         {
140             Method getEncoderMethod = loader.loadClass( "java.util.Base64" ).getMethod( "getEncoder" );
141             Method encodeMethod =
142                 loader.loadClass( "java.util.Base64$Encoder" ).getMethod( "encodeToString", byte[].class );
143             Object encoder = getEncoderMethod.invoke( null );
144             return (String) encodeMethod.invoke( encoder,
145                                                  new Object[] { userInfo.getBytes( StandardCharsets.UTF_8 ) } );
146         }
147         catch ( Exception java7OrEarlier )
148         {
149             try
150             {
151                 Method encodeMethod =
152                     loader.loadClass( "javax.xml.bind.DatatypeConverter" ).getMethod( "printBase64Binary",
153                                                                                       byte[].class );
154                 return (String) encodeMethod.invoke( null,
155                                                      new Object[] { userInfo.getBytes( StandardCharsets.UTF_8 ) } );
156             }
157             catch ( Exception java5OrEarlier )
158             {
159                 throw new RuntimeException( "Downloading Maven distributions with HTTP Basic Authentication"
160                     + " is not supported on your JVM.", java5OrEarlier );
161             }
162         }
163     }
164 
165     private String calculateUserInfo( URI uri )
166     {
167         String username = System.getenv( MVNW_USERNAME );
168         String password = System.getenv( MVNW_PASSWORD );
169         if ( username != null && password != null )
170         {
171             return username + ':' + password;
172         }
173         return uri.getUserInfo();
174     }
175 
176     private String calculateUserAgent()
177     {
178         String appVersion = applicationVersion;
179 
180         String javaVendor = System.getProperty( "java.vendor" );
181         String javaVersion = System.getProperty( "java.version" );
182         String javaVendorVersion = System.getProperty( "java.vm.version" );
183         String osName = System.getProperty( "os.name" );
184         String osVersion = System.getProperty( "os.version" );
185         String osArch = System.getProperty( "os.arch" );
186         return String.format( Locale.ROOT, "%s/%s (%s;%s;%s) (%s;%s;%s)", applicationName, appVersion, osName,
187                               osVersion, osArch, javaVendor, javaVersion, javaVendorVersion );
188     }
189 
190     private static class SystemPropertiesProxyAuthenticator
191         extends Authenticator
192     {
193         @Override
194         protected PasswordAuthentication getPasswordAuthentication()
195         {
196             return new PasswordAuthentication( System.getProperty( "http.proxyUser" ),
197                                                System.getProperty( "http.proxyPassword", "" ).toCharArray() );
198         }
199     }
200 }