View Javadoc
1   package org.eclipse.aether.transport.http;
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.junit.Assert.*;
23  
24  import java.io.File;
25  import java.io.FileNotFoundException;
26  import java.net.ConnectException;
27  import java.net.ServerSocket;
28  import java.net.SocketTimeoutException;
29  import java.net.URI;
30  import java.nio.charset.StandardCharsets;
31  import java.util.HashMap;
32  import java.util.Map;
33  import java.util.concurrent.atomic.AtomicReference;
34  
35  import org.apache.http.client.HttpResponseException;
36  import org.apache.http.conn.ConnectTimeoutException;
37  import org.apache.http.pool.ConnPoolControl;
38  import org.apache.http.pool.PoolStats;
39  import org.eclipse.aether.ConfigurationProperties;
40  import org.eclipse.aether.DefaultRepositoryCache;
41  import org.eclipse.aether.DefaultRepositorySystemSession;
42  import org.eclipse.aether.internal.test.util.TestFileUtils;
43  import org.eclipse.aether.internal.test.util.TestUtils;
44  import org.eclipse.aether.repository.Authentication;
45  import org.eclipse.aether.repository.Proxy;
46  import org.eclipse.aether.repository.RemoteRepository;
47  import org.eclipse.aether.spi.connector.transport.GetTask;
48  import org.eclipse.aether.spi.connector.transport.PeekTask;
49  import org.eclipse.aether.spi.connector.transport.PutTask;
50  import org.eclipse.aether.spi.connector.transport.Transporter;
51  import org.eclipse.aether.spi.connector.transport.TransporterFactory;
52  import org.eclipse.aether.transfer.NoTransporterException;
53  import org.eclipse.aether.transfer.TransferCancelledException;
54  import org.eclipse.aether.util.repository.AuthenticationBuilder;
55  import org.junit.After;
56  import org.junit.Before;
57  import org.junit.Rule;
58  import org.junit.Test;
59  import org.junit.rules.TestName;
60  
61  /**
62   */
63  public class HttpTransporterTest
64  {
65  
66      static
67      {
68          System.setProperty( "javax.net.ssl.trustStore",
69                              new File( "src/test/resources/ssl/server-store" ).getAbsolutePath() );
70          System.setProperty( "javax.net.ssl.trustStorePassword", "server-pwd" );
71          System.setProperty( "javax.net.ssl.keyStore",
72                              new File( "src/test/resources/ssl/client-store" ).getAbsolutePath() );
73          System.setProperty( "javax.net.ssl.keyStorePassword", "client-pwd" );
74      }
75  
76      @Rule
77      public TestName testName = new TestName();
78  
79      private DefaultRepositorySystemSession session;
80  
81      private TransporterFactory factory;
82  
83      private Transporter transporter;
84  
85      private File repoDir;
86  
87      private HttpServer httpServer;
88  
89      private Authentication auth;
90  
91      private Proxy proxy;
92  
93      private RemoteRepository newRepo( String url )
94      {
95          return new RemoteRepository.Builder( "test", "default", url ).setAuthentication( auth ).setProxy( proxy ).build();
96      }
97  
98      private void newTransporter( String url )
99          throws Exception
100     {
101         if ( transporter != null )
102         {
103             transporter.close();
104             transporter = null;
105         }
106         transporter = factory.newInstance( session, newRepo( url ) );
107     }
108 
109     @Before
110     public void setUp()
111         throws Exception
112     {
113         System.out.println( "=== " + testName.getMethodName() + " ===" );
114         session = TestUtils.newSession();
115         factory = new HttpTransporterFactory( );
116         repoDir = TestFileUtils.createTempDir();
117         TestFileUtils.writeString( new File( repoDir, "file.txt" ), "test" );
118         TestFileUtils.writeString( new File( repoDir, "dir/file.txt" ), "test" );
119         TestFileUtils.writeString( new File( repoDir, "empty.txt" ), "" );
120         TestFileUtils.writeString( new File( repoDir, "some space.txt" ), "space" );
121         File resumable = new File( repoDir, "resume.txt" );
122         TestFileUtils.writeString( resumable, "resumable" );
123         resumable.setLastModified( System.currentTimeMillis() - 90 * 1000 );
124         httpServer = new HttpServer().setRepoDir( repoDir ).start();
125         newTransporter( httpServer.getHttpUrl() );
126     }
127 
128     @After
129     public void tearDown()
130         throws Exception
131     {
132         if ( transporter != null )
133         {
134             transporter.close();
135             transporter = null;
136         }
137         if ( httpServer != null )
138         {
139             httpServer.stop();
140             httpServer = null;
141         }
142         factory = null;
143         session = null;
144     }
145 
146     @Test
147     public void testClassify()
148     {
149         assertEquals( Transporter.ERROR_OTHER, transporter.classify( new FileNotFoundException() ) );
150         assertEquals( Transporter.ERROR_OTHER, transporter.classify( new HttpResponseException( 403, "Forbidden" ) ) );
151         assertEquals( Transporter.ERROR_NOT_FOUND, transporter.classify( new HttpResponseException( 404, "Not Found" ) ) );
152     }
153 
154     @Test
155     public void testPeek()
156         throws Exception
157     {
158         transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
159     }
160 
161     @Test
162     public void testPeek_NotFound()
163         throws Exception
164     {
165         try
166         {
167             transporter.peek( new PeekTask( URI.create( "repo/missing.txt" ) ) );
168             fail( "Expected error" );
169         }
170         catch ( HttpResponseException e )
171         {
172             assertEquals( 404, e.getStatusCode() );
173             assertEquals( Transporter.ERROR_NOT_FOUND, transporter.classify( e ) );
174         }
175     }
176 
177     @Test
178     public void testPeek_Closed()
179         throws Exception
180     {
181         transporter.close();
182         try
183         {
184             transporter.peek( new PeekTask( URI.create( "repo/missing.txt" ) ) );
185             fail( "Expected error" );
186         }
187         catch ( IllegalStateException e )
188         {
189             assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
190         }
191     }
192 
193     @Test
194     public void testPeek_Authenticated()
195         throws Exception
196     {
197         httpServer.setAuthentication( "testuser", "testpass" );
198         auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
199         newTransporter( httpServer.getHttpUrl() );
200         transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
201     }
202 
203     @Test
204     public void testPeek_Unauthenticated()
205         throws Exception
206     {
207         httpServer.setAuthentication( "testuser", "testpass" );
208         try
209         {
210             transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
211             fail( "Expected error" );
212         }
213         catch ( HttpResponseException e )
214         {
215             assertEquals( 401, e.getStatusCode() );
216             assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
217         }
218     }
219 
220     @Test
221     public void testPeek_ProxyAuthenticated()
222         throws Exception
223     {
224         httpServer.setProxyAuthentication( "testuser", "testpass" );
225         auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
226         proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth );
227         newTransporter( "http://bad.localhost:1/" );
228         transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
229     }
230 
231     @Test
232     public void testPeek_ProxyUnauthenticated()
233         throws Exception
234     {
235         httpServer.setProxyAuthentication( "testuser", "testpass" );
236         proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort() );
237         newTransporter( "http://bad.localhost:1/" );
238         try
239         {
240             transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
241             fail( "Expected error" );
242         }
243         catch ( HttpResponseException e )
244         {
245             assertEquals( 407, e.getStatusCode() );
246             assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
247         }
248     }
249 
250     @Test
251     public void testPeek_SSL()
252         throws Exception
253     {
254         httpServer.addSslConnector();
255         newTransporter( httpServer.getHttpsUrl() );
256         transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
257     }
258 
259     @Test
260     public void testPeek_Redirect()
261         throws Exception
262     {
263         httpServer.addSslConnector();
264         transporter.peek( new PeekTask( URI.create( "redirect/file.txt" ) ) );
265         transporter.peek( new PeekTask( URI.create( "redirect/file.txt?scheme=https" ) ) );
266     }
267 
268     @Test
269     public void testGet_ToMemory()
270         throws Exception
271     {
272         RecordingTransportListener listener = new RecordingTransportListener();
273         GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
274         transporter.get( task );
275         assertEquals( "test", task.getDataString() );
276         assertEquals( 0L, listener.dataOffset );
277         assertEquals( 4L, listener.dataLength );
278         assertEquals( 1, listener.startedCount );
279         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
280         assertEquals( task.getDataString(), new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
281     }
282 
283     @Test
284     public void testGet_ToFile()
285         throws Exception
286     {
287         File file = TestFileUtils.createTempFile( "failure" );
288         RecordingTransportListener listener = new RecordingTransportListener();
289         GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setDataFile( file ).setListener( listener );
290         transporter.get( task );
291         assertEquals( "test", TestFileUtils.readString( file ) );
292         assertEquals( 0L, listener.dataOffset );
293         assertEquals( 4L, listener.dataLength );
294         assertEquals( 1, listener.startedCount );
295         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
296         assertEquals( "test", new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
297     }
298 
299     @Test
300     public void testGet_EmptyResource()
301         throws Exception
302     {
303         File file = TestFileUtils.createTempFile( "failure" );
304         RecordingTransportListener listener = new RecordingTransportListener();
305         GetTask task = new GetTask( URI.create( "repo/empty.txt" ) ).setDataFile( file ).setListener( listener );
306         transporter.get( task );
307         assertEquals( "", TestFileUtils.readString( file ) );
308         assertEquals( 0L, listener.dataOffset );
309         assertEquals( 0L, listener.dataLength );
310         assertEquals( 1, listener.startedCount );
311         assertEquals( 0, listener.progressedCount );
312         assertEquals( "", new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
313     }
314 
315     @Test
316     public void testGet_EncodedResourcePath()
317         throws Exception
318     {
319         GetTask task = new GetTask( URI.create( "repo/some%20space.txt" ) );
320         transporter.get( task );
321         assertEquals( "space", task.getDataString() );
322     }
323 
324     @Test
325     public void testGet_Authenticated()
326         throws Exception
327     {
328         httpServer.setAuthentication( "testuser", "testpass" );
329         auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
330         newTransporter( httpServer.getHttpUrl() );
331         RecordingTransportListener listener = new RecordingTransportListener();
332         GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
333         transporter.get( task );
334         assertEquals( "test", task.getDataString() );
335         assertEquals( 0L, listener.dataOffset );
336         assertEquals( 4L, listener.dataLength );
337         assertEquals( 1, listener.startedCount );
338         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
339         assertEquals( task.getDataString(), new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
340     }
341 
342     @Test
343     public void testGet_Unauthenticated()
344         throws Exception
345     {
346         httpServer.setAuthentication( "testuser", "testpass" );
347         try
348         {
349             transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
350             fail( "Expected error" );
351         }
352         catch ( HttpResponseException e )
353         {
354             assertEquals( 401, e.getStatusCode() );
355             assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
356         }
357     }
358 
359     @Test
360     public void testGet_ProxyAuthenticated()
361         throws Exception
362     {
363         httpServer.setProxyAuthentication( "testuser", "testpass" );
364         Authentication auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
365         proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth );
366         newTransporter( "http://bad.localhost:1/" );
367         RecordingTransportListener listener = new RecordingTransportListener();
368         GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
369         transporter.get( task );
370         assertEquals( "test", task.getDataString() );
371         assertEquals( 0L, listener.dataOffset );
372         assertEquals( 4L, listener.dataLength );
373         assertEquals( 1, listener.startedCount );
374         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
375         assertEquals( task.getDataString(), new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
376     }
377 
378     @Test
379     public void testGet_ProxyUnauthenticated()
380         throws Exception
381     {
382         httpServer.setProxyAuthentication( "testuser", "testpass" );
383         proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort() );
384         newTransporter( "http://bad.localhost:1/" );
385         try
386         {
387             transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
388             fail( "Expected error" );
389         }
390         catch ( HttpResponseException e )
391         {
392             assertEquals( 407, e.getStatusCode() );
393             assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
394         }
395     }
396 
397     @Test
398     public void testGet_SSL()
399         throws Exception
400     {
401         httpServer.addSslConnector();
402         newTransporter( httpServer.getHttpsUrl() );
403         RecordingTransportListener listener = new RecordingTransportListener();
404         GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
405         transporter.get( task );
406         assertEquals( "test", task.getDataString() );
407         assertEquals( 0L, listener.dataOffset );
408         assertEquals( 4L, listener.dataLength );
409         assertEquals( 1, listener.startedCount );
410         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
411         assertEquals( task.getDataString(), new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
412     }
413 
414     @Test
415     public void testGet_WebDav()
416         throws Exception
417     {
418         httpServer.setWebDav( true );
419         RecordingTransportListener listener = new RecordingTransportListener();
420         GetTask task = new GetTask( URI.create( "repo/dir/file.txt" ) ).setListener( listener );
421         ( (HttpTransporter) transporter ).getState().setWebDav( true );
422         transporter.get( task );
423         assertEquals( "test", task.getDataString() );
424         assertEquals( 0L, listener.dataOffset );
425         assertEquals( 4L, listener.dataLength );
426         assertEquals( 1, listener.startedCount );
427         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
428         assertEquals( task.getDataString(), new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
429         assertEquals( httpServer.getLogEntries().toString(), 1, httpServer.getLogEntries().size() );
430     }
431 
432     @Test
433     public void testGet_Redirect()
434         throws Exception
435     {
436         httpServer.addSslConnector();
437         RecordingTransportListener listener = new RecordingTransportListener();
438         GetTask task = new GetTask( URI.create( "redirect/file.txt?scheme=https" ) ).setListener( listener );
439         transporter.get( task );
440         assertEquals( "test", task.getDataString() );
441         assertEquals( 0L, listener.dataOffset );
442         assertEquals( 4L, listener.dataLength );
443         assertEquals( 1, listener.startedCount );
444         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
445         assertEquals( task.getDataString(), new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
446     }
447 
448     @Test
449     public void testGet_Resume()
450         throws Exception
451     {
452         File file = TestFileUtils.createTempFile( "re" );
453         RecordingTransportListener listener = new RecordingTransportListener();
454         GetTask task = new GetTask( URI.create( "repo/resume.txt" ) ).setDataFile( file, true ).setListener( listener );
455         transporter.get( task );
456         assertEquals( "resumable", TestFileUtils.readString( file ) );
457         assertEquals( 1L, listener.startedCount );
458         assertEquals( 2L, listener.dataOffset );
459         assertEquals( 9, listener.dataLength );
460         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
461         assertEquals( "sumable", new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
462     }
463 
464     @Test
465     public void testGet_ResumeLocalContentsOutdated()
466         throws Exception
467     {
468         File file = TestFileUtils.createTempFile( "re" );
469         file.setLastModified( System.currentTimeMillis() - 5 * 60 * 1000 );
470         RecordingTransportListener listener = new RecordingTransportListener();
471         GetTask task = new GetTask( URI.create( "repo/resume.txt" ) ).setDataFile( file, true ).setListener( listener );
472         transporter.get( task );
473         assertEquals( "resumable", TestFileUtils.readString( file ) );
474         assertEquals( 1L, listener.startedCount );
475         assertEquals( 0L, listener.dataOffset );
476         assertEquals( 9, listener.dataLength );
477         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
478         assertEquals( "resumable", new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
479     }
480 
481     @Test
482     public void testGet_ResumeRangesNotSupportedByServer()
483         throws Exception
484     {
485         httpServer.setRangeSupport( false );
486         File file = TestFileUtils.createTempFile( "re" );
487         RecordingTransportListener listener = new RecordingTransportListener();
488         GetTask task = new GetTask( URI.create( "repo/resume.txt" ) ).setDataFile( file, true ).setListener( listener );
489         transporter.get( task );
490         assertEquals( "resumable", TestFileUtils.readString( file ) );
491         assertEquals( 1L, listener.startedCount );
492         assertEquals( 0L, listener.dataOffset );
493         assertEquals( 9, listener.dataLength );
494         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
495         assertEquals( "resumable", new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
496     }
497 
498     @Test
499     public void testGet_Checksums_Nexus()
500         throws Exception
501     {
502         httpServer.setChecksumHeader( HttpServer.ChecksumHeader.NEXUS );
503         GetTask task = new GetTask( URI.create( "repo/file.txt" ) );
504         transporter.get( task );
505         assertEquals( "test", task.getDataString() );
506         assertEquals( "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", task.getChecksums().get( "SHA-1" ) );
507     }
508 
509     @Test
510     public void testGet_FileHandleLeak()
511         throws Exception
512     {
513         for ( int i = 0; i < 100; i++ )
514         {
515             File file = TestFileUtils.createTempFile( "failure" );
516             transporter.get( new GetTask( URI.create( "repo/file.txt" ) ).setDataFile( file ) );
517             assertTrue( i + ", " + file.getAbsolutePath(), file.delete() );
518         }
519     }
520 
521     @Test
522     public void testGet_NotFound()
523         throws Exception
524     {
525         try
526         {
527             transporter.get( new GetTask( URI.create( "repo/missing.txt" ) ) );
528             fail( "Expected error" );
529         }
530         catch ( HttpResponseException e )
531         {
532             assertEquals( 404, e.getStatusCode() );
533             assertEquals( Transporter.ERROR_NOT_FOUND, transporter.classify( e ) );
534         }
535     }
536 
537     @Test
538     public void testGet_Closed()
539         throws Exception
540     {
541         transporter.close();
542         try
543         {
544             transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
545             fail( "Expected error" );
546         }
547         catch ( IllegalStateException e )
548         {
549             assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
550         }
551     }
552 
553     @Test
554     public void testGet_StartCancelled()
555         throws Exception
556     {
557         RecordingTransportListener listener = new RecordingTransportListener();
558         listener.cancelStart = true;
559         GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
560         try
561         {
562             transporter.get( task );
563             fail( "Expected error" );
564         }
565         catch ( TransferCancelledException e )
566         {
567             assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
568         }
569         assertEquals( 0L, listener.dataOffset );
570         assertEquals( 4L, listener.dataLength );
571         assertEquals( 1, listener.startedCount );
572         assertEquals( 0, listener.progressedCount );
573     }
574 
575     @Test
576     public void testGet_ProgressCancelled()
577         throws Exception
578     {
579         RecordingTransportListener listener = new RecordingTransportListener();
580         listener.cancelProgress = true;
581         GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
582         try
583         {
584             transporter.get( task );
585             fail( "Expected error" );
586         }
587         catch ( TransferCancelledException e )
588         {
589             assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
590         }
591         assertEquals( 0L, listener.dataOffset );
592         assertEquals( 4L, listener.dataLength );
593         assertEquals( 1, listener.startedCount );
594         assertEquals( 1, listener.progressedCount );
595     }
596 
597     @Test
598     public void testPut_FromMemory()
599         throws Exception
600     {
601         RecordingTransportListener listener = new RecordingTransportListener();
602         PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
603         transporter.put( task );
604         assertEquals( 0L, listener.dataOffset );
605         assertEquals( 6L, listener.dataLength );
606         assertEquals( 1, listener.startedCount );
607         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
608         assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
609     }
610 
611     @Test
612     public void testPut_FromFile()
613         throws Exception
614     {
615         File file = TestFileUtils.createTempFile( "upload" );
616         RecordingTransportListener listener = new RecordingTransportListener();
617         PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataFile( file );
618         transporter.put( task );
619         assertEquals( 0L, listener.dataOffset );
620         assertEquals( 6L, listener.dataLength );
621         assertEquals( 1, listener.startedCount );
622         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
623         assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
624     }
625 
626     @Test
627     public void testPut_EmptyResource()
628         throws Exception
629     {
630         RecordingTransportListener listener = new RecordingTransportListener();
631         PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener );
632         transporter.put( task );
633         assertEquals( 0L, listener.dataOffset );
634         assertEquals( 0L, listener.dataLength );
635         assertEquals( 1, listener.startedCount );
636         assertEquals( 0, listener.progressedCount );
637         assertEquals( "", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
638     }
639 
640     @Test
641     public void testPut_EncodedResourcePath()
642         throws Exception
643     {
644         RecordingTransportListener listener = new RecordingTransportListener();
645         PutTask task =
646             new PutTask( URI.create( "repo/some%20space.txt" ) ).setListener( listener ).setDataString( "OK" );
647         transporter.put( task );
648         assertEquals( 0L, listener.dataOffset );
649         assertEquals( 2L, listener.dataLength );
650         assertEquals( 1, listener.startedCount );
651         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
652         assertEquals( "OK", TestFileUtils.readString( new File( repoDir, "some space.txt" ) ) );
653     }
654 
655     @Test
656     public void testPut_Authenticated_ExpectContinue()
657         throws Exception
658     {
659         httpServer.setAuthentication( "testuser", "testpass" );
660         auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
661         newTransporter( httpServer.getHttpUrl() );
662         RecordingTransportListener listener = new RecordingTransportListener();
663         PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
664         transporter.put( task );
665         assertEquals( 0L, listener.dataOffset );
666         assertEquals( 6L, listener.dataLength );
667         assertEquals( 1, listener.startedCount );
668         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
669         assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
670     }
671 
672     @Test
673     public void testPut_Authenticated_ExpectContinueBroken()
674         throws Exception
675     {
676         httpServer.setAuthentication( "testuser", "testpass" );
677         httpServer.setExpectSupport( HttpServer.ExpectContinue.BROKEN );
678         auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
679         newTransporter( httpServer.getHttpUrl() );
680         RecordingTransportListener listener = new RecordingTransportListener();
681         PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
682         transporter.put( task );
683         assertEquals( 0L, listener.dataOffset );
684         assertEquals( 6L, listener.dataLength );
685         assertEquals( 1, listener.startedCount );
686         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
687         assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
688     }
689 
690     @Test
691     public void testPut_Authenticated_ExpectContinueRejected()
692         throws Exception
693     {
694         httpServer.setAuthentication( "testuser", "testpass" );
695         httpServer.setExpectSupport( HttpServer.ExpectContinue.FAIL );
696         auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
697         newTransporter( httpServer.getHttpUrl() );
698         RecordingTransportListener listener = new RecordingTransportListener();
699         PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
700         transporter.put( task );
701         assertEquals( 0L, listener.dataOffset );
702         assertEquals( 6L, listener.dataLength );
703         assertEquals( 1, listener.startedCount );
704         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
705         assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
706     }
707 
708     @Test
709     public void testPut_Authenticated_ExpectContinueRejected_ExplicitlyConfiguredHeader()
710         throws Exception
711     {
712         Map<String, String> headers = new HashMap<>();
713         headers.put( "Expect", "100-continue" );
714         session.setConfigProperty( ConfigurationProperties.HTTP_HEADERS + ".test", headers );
715         httpServer.setAuthentication( "testuser", "testpass" );
716         httpServer.setExpectSupport( HttpServer.ExpectContinue.FAIL );
717         auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
718         newTransporter( httpServer.getHttpUrl() );
719         RecordingTransportListener listener = new RecordingTransportListener();
720         PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
721         transporter.put( task );
722         assertEquals( 0L, listener.dataOffset );
723         assertEquals( 6L, listener.dataLength );
724         assertEquals( 1, listener.startedCount );
725         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
726         assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
727     }
728 
729     @Test
730     public void testPut_Unauthenticated()
731         throws Exception
732     {
733         httpServer.setAuthentication( "testuser", "testpass" );
734         RecordingTransportListener listener = new RecordingTransportListener();
735         PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
736         try
737         {
738             transporter.put( task );
739             fail( "Expected error" );
740         }
741         catch ( HttpResponseException e )
742         {
743             assertEquals( 401, e.getStatusCode() );
744             assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
745         }
746         assertEquals( 0, listener.startedCount );
747         assertEquals( 0, listener.progressedCount );
748     }
749 
750     @Test
751     public void testPut_ProxyAuthenticated()
752         throws Exception
753     {
754         httpServer.setProxyAuthentication( "testuser", "testpass" );
755         Authentication auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
756         proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth );
757         newTransporter( "http://bad.localhost:1/" );
758         RecordingTransportListener listener = new RecordingTransportListener();
759         PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
760         transporter.put( task );
761         assertEquals( 0L, listener.dataOffset );
762         assertEquals( 6L, listener.dataLength );
763         assertEquals( 1, listener.startedCount );
764         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
765         assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
766     }
767 
768     @Test
769     public void testPut_ProxyUnauthenticated()
770         throws Exception
771     {
772         httpServer.setProxyAuthentication( "testuser", "testpass" );
773         proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort() );
774         newTransporter( "http://bad.localhost:1/" );
775         RecordingTransportListener listener = new RecordingTransportListener();
776         PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
777         try
778         {
779             transporter.put( task );
780             fail( "Expected error" );
781         }
782         catch ( HttpResponseException e )
783         {
784             assertEquals( 407, e.getStatusCode() );
785             assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
786         }
787         assertEquals( 0, listener.startedCount );
788         assertEquals( 0, listener.progressedCount );
789     }
790 
791     @Test
792     public void testPut_SSL()
793         throws Exception
794     {
795         httpServer.addSslConnector();
796         httpServer.setAuthentication( "testuser", "testpass" );
797         auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
798         newTransporter( httpServer.getHttpsUrl() );
799         RecordingTransportListener listener = new RecordingTransportListener();
800         PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
801         transporter.put( task );
802         assertEquals( 0L, listener.dataOffset );
803         assertEquals( 6L, listener.dataLength );
804         assertEquals( 1, listener.startedCount );
805         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
806         assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
807     }
808 
809     @Test
810     public void testPut_WebDav()
811         throws Exception
812     {
813         httpServer.setWebDav( true );
814         RecordingTransportListener listener = new RecordingTransportListener();
815         PutTask task =
816             new PutTask( URI.create( "repo/dir1/dir2/file.txt" ) ).setListener( listener ).setDataString( "upload" );
817         transporter.put( task );
818         assertEquals( 0L, listener.dataOffset );
819         assertEquals( 6L, listener.dataLength );
820         assertEquals( 1, listener.startedCount );
821         assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
822         assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "dir1/dir2/file.txt" ) ) );
823 
824         assertEquals( 5, httpServer.getLogEntries().size() );
825         assertEquals( "OPTIONS", httpServer.getLogEntries().get( 0 ).method );
826         assertEquals( "MKCOL", httpServer.getLogEntries().get( 1 ).method );
827         assertEquals( "/repo/dir1/dir2/", httpServer.getLogEntries().get( 1 ).path );
828         assertEquals( "MKCOL", httpServer.getLogEntries().get( 2 ).method );
829         assertEquals( "/repo/dir1/", httpServer.getLogEntries().get( 2 ).path );
830         assertEquals( "MKCOL", httpServer.getLogEntries().get( 3 ).method );
831         assertEquals( "/repo/dir1/dir2/", httpServer.getLogEntries().get( 3 ).path );
832         assertEquals( "PUT", httpServer.getLogEntries().get( 4 ).method );
833     }
834 
835     @Test
836     public void testPut_FileHandleLeak()
837         throws Exception
838     {
839         for ( int i = 0; i < 100; i++ )
840         {
841             File src = TestFileUtils.createTempFile( "upload" );
842             File dst = new File( repoDir, "file.txt" );
843             transporter.put( new PutTask( URI.create( "repo/file.txt" ) ).setDataFile( src ) );
844             assertTrue( i + ", " + src.getAbsolutePath(), src.delete() );
845             assertTrue( i + ", " + dst.getAbsolutePath(), dst.delete() );
846         }
847     }
848 
849     @Test
850     public void testPut_Closed()
851         throws Exception
852     {
853         transporter.close();
854         try
855         {
856             transporter.put( new PutTask( URI.create( "repo/missing.txt" ) ) );
857             fail( "Expected error" );
858         }
859         catch ( IllegalStateException e )
860         {
861             assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
862         }
863     }
864 
865     @Test
866     public void testPut_StartCancelled()
867         throws Exception
868     {
869         RecordingTransportListener listener = new RecordingTransportListener();
870         listener.cancelStart = true;
871         PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
872         try
873         {
874             transporter.put( task );
875             fail( "Expected error" );
876         }
877         catch ( TransferCancelledException e )
878         {
879             assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
880         }
881         assertEquals( 0L, listener.dataOffset );
882         assertEquals( 6L, listener.dataLength );
883         assertEquals( 1, listener.startedCount );
884         assertEquals( 0, listener.progressedCount );
885     }
886 
887     @Test
888     public void testPut_ProgressCancelled()
889         throws Exception
890     {
891         RecordingTransportListener listener = new RecordingTransportListener();
892         listener.cancelProgress = true;
893         PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
894         try
895         {
896             transporter.put( task );
897             fail( "Expected error" );
898         }
899         catch ( TransferCancelledException e )
900         {
901             assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
902         }
903         assertEquals( 0L, listener.dataOffset );
904         assertEquals( 6L, listener.dataLength );
905         assertEquals( 1, listener.startedCount );
906         assertEquals( 1, listener.progressedCount );
907     }
908 
909     @Test
910     public void testGetPut_AuthCache()
911         throws Exception
912     {
913         httpServer.setAuthentication( "testuser", "testpass" );
914         auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
915         newTransporter( httpServer.getHttpUrl() );
916         GetTask get = new GetTask( URI.create( "repo/file.txt" ) );
917         transporter.get( get );
918         RecordingTransportListener listener = new RecordingTransportListener();
919         PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
920         transporter.put( task );
921         assertEquals( 1, listener.startedCount );
922     }
923 
924     @Test( timeout = 20000L )
925     public void testConcurrency()
926         throws Exception
927     {
928         httpServer.setAuthentication( "testuser", "testpass" );
929         auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
930         newTransporter( httpServer.getHttpUrl() );
931         final AtomicReference<Throwable> error = new AtomicReference<>();
932         Thread[] threads = new Thread[20];
933         for ( int i = 0; i < threads.length; i++ )
934         {
935             final String path = "repo/file.txt?i=" + i;
936             threads[i] = new Thread()
937             {
938                 @Override
939                 public void run()
940                 {
941                     try
942                     {
943                         for ( int j = 0; j < 100; j++ )
944                         {
945                             GetTask task = new GetTask( URI.create( path ) );
946                             transporter.get( task );
947                             assertEquals( "test", task.getDataString() );
948                         }
949                     }
950                     catch ( Throwable t )
951                     {
952                         error.compareAndSet( null, t );
953                         System.err.println( path );
954                         t.printStackTrace();
955                     }
956                 }
957             };
958             threads[i].setName( "Task-" + i );
959         }
960         for ( Thread thread : threads )
961         {
962             thread.start();
963         }
964         for ( Thread thread : threads )
965         {
966             thread.join();
967         }
968         assertNull( String.valueOf( error.get() ), error.get() );
969     }
970 
971     @Test( timeout = 1000L )
972     public void testConnectTimeout()
973         throws Exception
974     {
975         session.setConfigProperty( ConfigurationProperties.CONNECT_TIMEOUT, 100 );
976         int port = 1;
977         newTransporter( "http://localhost:" + port );
978         try
979         {
980             transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
981             fail( "Expected error" );
982         }
983         catch ( ConnectTimeoutException | ConnectException e )
984         {
985             assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
986         }
987     }
988 
989     @Test( timeout = 1000L )
990     public void testRequestTimeout()
991         throws Exception
992     {
993         session.setConfigProperty( ConfigurationProperties.REQUEST_TIMEOUT, 100 );
994         ServerSocket server = new ServerSocket( 0 );
995         newTransporter( "http://localhost:" + server.getLocalPort() );
996         try
997         {
998             try
999             {
1000                 transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
1001                 fail( "Expected error" );
1002             }
1003             catch ( SocketTimeoutException e )
1004             {
1005                 assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
1006             }
1007         }
1008         finally
1009         {
1010             server.close();
1011         }
1012     }
1013 
1014     @Test
1015     public void testUserAgent()
1016         throws Exception
1017     {
1018         session.setConfigProperty( ConfigurationProperties.USER_AGENT, "SomeTest/1.0" );
1019         newTransporter( httpServer.getHttpUrl() );
1020         transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
1021         assertEquals( 1, httpServer.getLogEntries().size() );
1022         for ( HttpServer.LogEntry log : httpServer.getLogEntries() )
1023         {
1024             assertEquals( "SomeTest/1.0", log.headers.get( "User-Agent" ) );
1025         }
1026     }
1027 
1028     @Test
1029     public void testCustomHeaders()
1030         throws Exception
1031     {
1032         Map<String, String> headers = new HashMap<>();
1033         headers.put( "User-Agent", "Custom/1.0" );
1034         headers.put( "X-CustomHeader", "Custom-Value" );
1035         session.setConfigProperty( ConfigurationProperties.USER_AGENT, "SomeTest/1.0" );
1036         session.setConfigProperty( ConfigurationProperties.HTTP_HEADERS + ".test", headers );
1037         newTransporter( httpServer.getHttpUrl() );
1038         transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
1039         assertEquals( 1, httpServer.getLogEntries().size() );
1040         for ( HttpServer.LogEntry log : httpServer.getLogEntries() )
1041         {
1042             for ( Map.Entry<String, String> entry : headers.entrySet() )
1043             {
1044                 assertEquals( entry.getKey(), entry.getValue(), log.headers.get( entry.getKey() ) );
1045             }
1046         }
1047     }
1048 
1049     @Test
1050     public void testServerAuthScope_NotUsedForProxy()
1051         throws Exception
1052     {
1053         String username = "testuser", password = "testpass";
1054         httpServer.setProxyAuthentication( username, password );
1055         auth = new AuthenticationBuilder().addUsername( username ).addPassword( password ).build();
1056         proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort() );
1057         newTransporter( "http://" + httpServer.getHost() + ":12/" );
1058         try
1059         {
1060             transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
1061             fail( "Server auth must not be used as proxy auth" );
1062         }
1063         catch ( HttpResponseException e )
1064         {
1065             assertEquals( 407, e.getStatusCode() );
1066         }
1067     }
1068 
1069     @Test
1070     public void testProxyAuthScope_NotUsedForServer()
1071         throws Exception
1072     {
1073         String username = "testuser", password = "testpass";
1074         httpServer.setAuthentication( username, password );
1075         Authentication auth = new AuthenticationBuilder().addUsername( username ).addPassword( password ).build();
1076         proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth );
1077         newTransporter( "http://" + httpServer.getHost() + ":12/" );
1078         try
1079         {
1080             transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
1081             fail( "Proxy auth must not be used as server auth" );
1082         }
1083         catch ( HttpResponseException e )
1084         {
1085             assertEquals( 401, e.getStatusCode() );
1086         }
1087     }
1088 
1089     @Test
1090     public void testAuthSchemeReuse()
1091         throws Exception
1092     {
1093         httpServer.setAuthentication( "testuser", "testpass" );
1094         httpServer.setProxyAuthentication( "proxyuser", "proxypass" );
1095         session.setCache( new DefaultRepositoryCache() );
1096         auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
1097         Authentication auth = new AuthenticationBuilder().addUsername( "proxyuser" ).addPassword( "proxypass" ).build();
1098         proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth );
1099         newTransporter( "http://bad.localhost:1/" );
1100         GetTask task = new GetTask( URI.create( "repo/file.txt" ) );
1101         transporter.get( task );
1102         assertEquals( "test", task.getDataString() );
1103         assertEquals( 3, httpServer.getLogEntries().size() );
1104         httpServer.getLogEntries().clear();
1105         newTransporter( "http://bad.localhost:1/" );
1106         task = new GetTask( URI.create( "repo/file.txt" ) );
1107         transporter.get( task );
1108         assertEquals( "test", task.getDataString() );
1109         assertEquals( 1, httpServer.getLogEntries().size() );
1110         assertNotNull( httpServer.getLogEntries().get( 0 ).headers.get( "Authorization" ) );
1111         assertNotNull( httpServer.getLogEntries().get( 0 ).headers.get( "Proxy-Authorization" ) );
1112     }
1113 
1114     @Test
1115     public void testConnectionReuse()
1116         throws Exception
1117     {
1118         httpServer.addSslConnector();
1119         session.setCache( new DefaultRepositoryCache() );
1120         for ( int i = 0; i < 3; i++ )
1121         {
1122             newTransporter( httpServer.getHttpsUrl() );
1123             GetTask task = new GetTask( URI.create( "repo/file.txt" ) );
1124             transporter.get( task );
1125             assertEquals( "test", task.getDataString() );
1126         }
1127         PoolStats stats =
1128             ( (ConnPoolControl<?>) ( (HttpTransporter) transporter ).getState().getConnectionManager() ).getTotalStats();
1129         assertEquals( stats.toString(), 1, stats.getAvailable() );
1130     }
1131 
1132     @Test( expected = NoTransporterException.class )
1133     public void testInit_BadProtocol()
1134         throws Exception
1135     {
1136         newTransporter( "bad:/void" );
1137     }
1138 
1139     @Test( expected = NoTransporterException.class )
1140     public void testInit_BadUrl()
1141         throws Exception
1142     {
1143         newTransporter( "http://localhost:NaN" );
1144     }
1145 
1146     @Test
1147     public void testInit_CaseInsensitiveProtocol()
1148         throws Exception
1149     {
1150         newTransporter( "http://localhost" );
1151         newTransporter( "HTTP://localhost" );
1152         newTransporter( "Http://localhost" );
1153         newTransporter( "https://localhost" );
1154         newTransporter( "HTTPS://localhost" );
1155         newTransporter( "HttpS://localhost" );
1156     }
1157 
1158 }