001package org.eclipse.aether.transport.http;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import static org.junit.Assert.*;
023
024import java.io.File;
025import java.io.FileNotFoundException;
026import java.net.ConnectException;
027import java.net.ServerSocket;
028import java.net.SocketTimeoutException;
029import java.net.URI;
030import java.nio.charset.StandardCharsets;
031import java.util.HashMap;
032import java.util.Map;
033import java.util.concurrent.atomic.AtomicReference;
034
035import org.apache.http.client.HttpResponseException;
036import org.apache.http.conn.ConnectTimeoutException;
037import org.apache.http.pool.ConnPoolControl;
038import org.apache.http.pool.PoolStats;
039import org.eclipse.aether.ConfigurationProperties;
040import org.eclipse.aether.DefaultRepositoryCache;
041import org.eclipse.aether.DefaultRepositorySystemSession;
042import org.eclipse.aether.internal.test.util.TestFileUtils;
043import org.eclipse.aether.internal.test.util.TestUtils;
044import org.eclipse.aether.repository.Authentication;
045import org.eclipse.aether.repository.Proxy;
046import org.eclipse.aether.repository.RemoteRepository;
047import org.eclipse.aether.spi.connector.transport.GetTask;
048import org.eclipse.aether.spi.connector.transport.PeekTask;
049import org.eclipse.aether.spi.connector.transport.PutTask;
050import org.eclipse.aether.spi.connector.transport.Transporter;
051import org.eclipse.aether.spi.connector.transport.TransporterFactory;
052import org.eclipse.aether.transfer.NoTransporterException;
053import org.eclipse.aether.transfer.TransferCancelledException;
054import org.eclipse.aether.util.repository.AuthenticationBuilder;
055import org.junit.After;
056import org.junit.Before;
057import org.junit.Rule;
058import org.junit.Test;
059import org.junit.rules.TestName;
060
061/**
062 */
063public class HttpTransporterTest
064{
065
066    static
067    {
068        System.setProperty( "javax.net.ssl.trustStore",
069                            new File( "src/test/resources/ssl/server-store" ).getAbsolutePath() );
070        System.setProperty( "javax.net.ssl.trustStorePassword", "server-pwd" );
071        System.setProperty( "javax.net.ssl.keyStore",
072                            new File( "src/test/resources/ssl/client-store" ).getAbsolutePath() );
073        System.setProperty( "javax.net.ssl.keyStorePassword", "client-pwd" );
074    }
075
076    @Rule
077    public TestName testName = new TestName();
078
079    private DefaultRepositorySystemSession session;
080
081    private TransporterFactory factory;
082
083    private Transporter transporter;
084
085    private File repoDir;
086
087    private HttpServer httpServer;
088
089    private Authentication auth;
090
091    private Proxy proxy;
092
093    private RemoteRepository newRepo( String url )
094    {
095        return new RemoteRepository.Builder( "test", "default", url ).setAuthentication( auth ).setProxy( proxy ).build();
096    }
097
098    private void newTransporter( String url )
099        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}