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