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.TestLoggerFactory;
044import org.eclipse.aether.internal.test.util.TestUtils;
045import org.eclipse.aether.repository.Authentication;
046import org.eclipse.aether.repository.Proxy;
047import org.eclipse.aether.repository.RemoteRepository;
048import org.eclipse.aether.spi.connector.transport.GetTask;
049import org.eclipse.aether.spi.connector.transport.PeekTask;
050import org.eclipse.aether.spi.connector.transport.PutTask;
051import org.eclipse.aether.spi.connector.transport.Transporter;
052import org.eclipse.aether.spi.connector.transport.TransporterFactory;
053import org.eclipse.aether.transfer.NoTransporterException;
054import org.eclipse.aether.transfer.TransferCancelledException;
055import org.eclipse.aether.util.repository.AuthenticationBuilder;
056import org.junit.After;
057import org.junit.Before;
058import org.junit.Rule;
059import org.junit.Test;
060import org.junit.rules.TestName;
061
062/**
063 */
064public class HttpTransporterTest
065{
066
067    static
068    {
069        System.setProperty( "javax.net.ssl.trustStore",
070                            new File( "src/test/resources/ssl/server-store" ).getAbsolutePath() );
071        System.setProperty( "javax.net.ssl.trustStorePassword", "server-pwd" );
072        System.setProperty( "javax.net.ssl.keyStore",
073                            new File( "src/test/resources/ssl/client-store" ).getAbsolutePath() );
074        System.setProperty( "javax.net.ssl.keyStorePassword", "client-pwd" );
075    }
076
077    @Rule
078    public TestName testName = new TestName();
079
080    private DefaultRepositorySystemSession session;
081
082    private TransporterFactory factory;
083
084    private Transporter transporter;
085
086    private File repoDir;
087
088    private HttpServer httpServer;
089
090    private Authentication auth;
091
092    private Proxy proxy;
093
094    private RemoteRepository newRepo( String url )
095    {
096        return new RemoteRepository.Builder( "test", "default", url ).setAuthentication( auth ).setProxy( proxy ).build();
097    }
098
099    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}