View Javadoc
1   package org.apache.maven.it;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.OutputStream;
25  import java.io.PushbackInputStream;
26  import java.net.ServerSocket;
27  import java.net.Socket;
28  import java.util.regex.Matcher;
29  import java.util.regex.Pattern;
30  
31  /**
32   * A simple HTTP proxy that only understands the CONNECT method to check HTTPS tunneling.
33   *
34   * @author Benjamin Bentmann
35   */
36  public class TunnelingProxyServer
37      implements Runnable
38  {
39  
40      private int port;
41  
42      private volatile ServerSocket server;
43  
44      private String targetHost;
45  
46      private int targetPort;
47  
48      private String connectFilter;
49  
50      public TunnelingProxyServer( int port, String targetHost, int targetPort, String connectFilter )
51      {
52          this.port = port;
53          this.targetHost = targetHost;
54          this.targetPort = targetPort;
55          this.connectFilter = connectFilter;
56      }
57  
58      public int getPort()
59      {
60          return ( server != null ) ? server.getLocalPort() : port;
61      }
62  
63      public void start()
64          throws IOException
65      {
66          server = new ServerSocket( port, 4 );
67          new Thread( this ).start();
68      }
69  
70      public void stop()
71          throws IOException
72      {
73          if ( server != null )
74          {
75              server.close();
76              server = null;
77          }
78      }
79  
80      public void run()
81      {
82          try
83          {
84              while ( true )
85              {
86                  new ClientHandler( server.accept() ).start();
87              }
88          }
89          catch ( Exception e )
90          {
91              // closed
92          }
93      }
94  
95      class ClientHandler
96          extends Thread
97      {
98  
99          private Socket client;
100 
101         public ClientHandler( Socket client )
102         {
103             this.client = client;
104         }
105 
106         public void run()
107         {
108             try
109             {
110                 PushbackInputStream is = new PushbackInputStream( client.getInputStream() );
111 
112                 String dest = null;
113 
114                 while ( true )
115                 {
116                     String line = readLine( is );
117                     if ( line == null || line.length() <= 0 )
118                     {
119                         break;
120                     }
121                     Matcher m = Pattern.compile( "CONNECT +([^:]+:[0-9]+) +.*" ).matcher( line );
122                     if ( m.matches() )
123                     {
124                         dest = m.group( 1 );
125                     }
126                 }
127 
128                 OutputStream os = client.getOutputStream();
129 
130                 if ( dest == null || ( connectFilter != null && !dest.matches( connectFilter ) ) )
131                 {
132                     os.write( ( "HTTP/1.0 400 Bad request for " + dest + "\r\n\r\n" ).getBytes( "UTF-8" ) );
133                     return;
134                 }
135 
136                 os.write( "HTTP/1.0 200 Connection established\r\n\r\n".getBytes( "UTF-8" ) );
137 
138                 Socket server = new Socket( targetHost, targetPort );
139 
140                 Thread t1 = new StreamPumper( is, server.getOutputStream() );
141                 t1.start();
142                 Thread t2 = new StreamPumper( server.getInputStream(), os );
143                 t2.start();
144                 t1.join();
145                 t2.join();
146 
147                 server.close();
148             }
149             catch ( Exception e )
150             {
151                 e.printStackTrace();
152             }
153             finally
154             {
155                 try
156                 {
157                     client.close();
158                 }
159                 catch ( IOException e )
160                 {
161                     e.printStackTrace();
162                 }
163             }
164         }
165 
166         private String readLine( PushbackInputStream is )
167             throws IOException
168         {
169             StringBuilder buffer = new StringBuilder( 1024 );
170 
171             while ( true )
172             {
173                 int b = is.read();
174                 if ( b < 0 )
175                 {
176                     return null;
177                 }
178                 else if ( b == '\n' )
179                 {
180                     break;
181                 }
182                 else if ( b == '\r' )
183                 {
184                     b = is.read();
185                     if ( b != '\n' )
186                     {
187                         is.unread( b );
188                     }
189                     break;
190                 }
191                 else
192                 {
193                     buffer.append( (char) b );
194                 }
195             }
196 
197             return buffer.toString();
198         }
199 
200     }
201 
202     static class StreamPumper
203         extends Thread
204     {
205 
206         private final InputStream is;
207 
208         private final OutputStream os;
209 
210         public StreamPumper( InputStream is, OutputStream os )
211         {
212             this.is = is;
213             this.os = os;
214         }
215 
216         public void run()
217         {
218             try
219             {
220                 for ( byte[] buffer = new byte[1024 * 8]; ; )
221                 {
222                     int n = is.read( buffer );
223                     if ( n < 0 )
224                     {
225                         break;
226                     }
227                     os.write( buffer, 0, n );
228                 }
229             }
230             catch ( IOException e )
231             {
232                 // closed
233             }
234             finally
235             {
236                 try
237                 {
238                     is.close();
239                 }
240                 catch ( IOException e )
241                 {
242                     e.printStackTrace();
243                 }
244                 try
245                 {
246                     os.close();
247                 }
248                 catch ( IOException e )
249                 {
250                     e.printStackTrace();
251                 }
252             }
253         }
254 
255     }
256 
257 }