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