/*
* Created on Oct 2, 2008
* Created by Paul Gardner
*
* Copyright 2008 Vuze, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
package com.aelitis.azureus.core.util.http;
import java.net.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;
import java.io.*;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.gudy.azureus2.core3.security.SESecurityManager;
import org.gudy.azureus2.core3.util.*;
import com.aelitis.azureus.core.util.CopyOnWriteList;
public class
HTTPAuthHelper
{
public static final boolean TRACE = false;
public static final int MAX_PROCESSORS = 32;
public static final int CONNECT_TIMEOUT = 30*1000;
public static final int READ_TIMEOUT = 30*1000;
private HTTPAuthHelper parent;
private Map children = new HashMap();
private URL delegate_to;
private String delegate_to_host;
private int delegate_to_port;
private boolean delegate_is_https;
private CopyOnWriteList listeners = new CopyOnWriteList();
private int port;
private ServerSocket server_socket;
private boolean http_only_detected;
private Map cookie_names_set = new HashMap();
private ThreadPool thread_pool = new ThreadPool("HTTPSniffer", MAX_PROCESSORS, true );
private List processors = new ArrayList();
private volatile boolean destroyed;
public
HTTPAuthHelper(
URL url )
throws Exception
{
this( null, url );
}
protected
HTTPAuthHelper(
HTTPAuthHelper _parent,
URL _delegate_to )
throws Exception
{
parent = _parent;
delegate_to = _delegate_to;
delegate_to_host = delegate_to.getHost();
delegate_is_https = delegate_to.getProtocol().toLowerCase().equals( "https" );
delegate_to_port = delegate_to.getPort()==-1?delegate_to.getDefaultPort():delegate_to.getPort();
server_socket = new ServerSocket();
server_socket.setReuseAddress( true );
server_socket.bind( new InetSocketAddress( "127.0.0.1", 0 ));
port = server_socket.getLocalPort();
}
public void
start()
{
new AEThread2(
"HTTPSniffingProxy: " + delegate_to_host + ":" + delegate_to_port + "/" + delegate_is_https + "/" + port,
true )
{
public void
run()
{
try{
while( !destroyed ){
Socket socket = server_socket.accept();
socket.setSoTimeout( READ_TIMEOUT );
synchronized( HTTPAuthHelper.this ){
if ( processors.size() >= MAX_PROCESSORS ){
try{
Debug.out( "Too many processors" );
socket.close();
}catch( Throwable e ){
}
}else{
processor proc = new processor( socket );
processors.add( proc );
proc.start();
}
}
}
}catch( Throwable e ){
if ( !destroyed ){
Debug.printStackTrace( e );
}
}
}
}.start();
}
public int
getPort()
{
return( port );
}
public boolean
wasHTTPOnlyCookieDetected()
{
return( http_only_detected );
}
protected void
setHTTPOnlyCookieDetected()
{
http_only_detected = true;
if ( parent != null ){
parent.setHTTPOnlyCookieDetected();
}
}
protected String
getKey(
URL url )
{
int child_port = url.getPort()==-1?url.getDefaultPort():url.getPort();
String key = url.getProtocol() + ":" + url.getHost() + ":" + child_port;
return( key );
}
protected HTTPAuthHelper
getChild(
String url_str,
boolean optional )
throws Exception
{
if ( parent != null ){
return( parent.getChild( url_str,optional ));
}
String lc_url_str = url_str.toLowerCase();
if ( lc_url_str.startsWith( "http://" ) || lc_url_str.startsWith( "https://")){
URL child_url = new URL( url_str );
String child_key = getKey( child_url );
if ( child_key.equals( getKey( delegate_to ))){
return( this );
}
synchronized( this ){
if ( destroyed ){
throw( new Exception( "Destroyed" ));
}
HTTPAuthHelper child = (HTTPAuthHelper)children.get( child_key );
if ( optional ){
// create children for related domains
String base_host = delegate_to.getHost();
String child_host = child_url.getHost();
int base_pos = base_host.lastIndexOf( '.' );
base_pos = base_host.lastIndexOf( '.', base_pos-1 );
int child_pos = child_host.lastIndexOf( '.' );
child_pos = child_host.lastIndexOf( '.', child_pos-1 );
String base_dom = base_host.substring( base_pos, base_host.length());
String child_dom = child_host.substring( child_pos, child_host.length());
if ( base_dom.equals( child_dom )){
optional = false;
}
}
if ( child == null && !optional ){
child = new HTTPAuthHelper( this, new URL( url_str ));
children.put( child_key, child );
child.start();
}
return( child );
}
}else{
//relative
return( this );
}
}
protected void
addSetCookieName(
String name,
String value )
{
if ( parent != null ){
parent.addSetCookieName( name, value );
}else{
boolean new_entry;
synchronized( cookie_names_set ){
trace( "SetCookieName: " + name );
String old_value = (String)cookie_names_set.put( name, value );
new_entry = old_value==null || !old_value.equals( value );
}
if ( new_entry ){
Iterator it = listeners.iterator();
while( it.hasNext()){
try{
((HTTPAuthHelperListener)it.next()).cookieFound( this, name, value );
}catch( Throwable e ){
Debug.printStackTrace(e );
}
}
}
}
}
protected boolean
hasSetCookieName(
String name )
{
if ( parent != null ){
return( parent.hasSetCookieName( name ));
}else{
synchronized( cookie_names_set ){
trace( "GetCookieName: " + name );
return( cookie_names_set.containsKey( name ));
}
}
}
public void
addListener(
HTTPAuthHelperListener listener )
{
listeners.add( listener );
}
public void
destroy()
{
List processors_to_destroy;
List chidren_to_destroy;
synchronized( this ){
if ( destroyed ){
return;
}
destroyed = true;
chidren_to_destroy = new ArrayList( children.values());
children.clear();
processors_to_destroy = new ArrayList( processors );
processors.clear();
try{
server_socket.close();
}catch( Throwable e ){
}
}
for (int i=0;i<chidren_to_destroy.size();i++){
try{
((HTTPAuthHelper)chidren_to_destroy.get(i)).destroy();
}catch( Throwable e ){
}
}
for (int i=0;i<processors_to_destroy.size();i++){
try{
((processor)processors_to_destroy.get(i)).destroy();
}catch( Throwable e ){
}
}
}
protected class
processor
{
private static final String NL = "\r\n";
private Socket socket_in;
private Socket socket_out;
private volatile boolean destroyed;
protected
processor(
Socket _socket )
{
socket_in = _socket;
}
protected void
start()
{
thread_pool.run(
new AERunnable()
{
public void
runSupport()
{
try{
sniff();
}finally{
synchronized( HTTPAuthHelper.this ){
processors.remove( processor.this );
}
}
}
});
}
protected void
sniff()
{
try{
InputStream is = socket_in.getInputStream();
String request_header = readHeader( is );
connectToDelegate();
process( request_header );
}catch( Throwable e ){
if ( !( e instanceof IOException )){
Debug.out( e );
}
destroy();
}
}
protected void
connectToDelegate()
throws IOException
{
try{
if ( delegate_is_https ){
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, RandomUtils.SECURE_RANDOM );
SSLSocketFactory factory = sc.getSocketFactory();
try{
socket_out = factory.createSocket();
socket_out.connect( new InetSocketAddress( delegate_to_host, delegate_to_port ), CONNECT_TIMEOUT );
}catch( SSLException ssl_excep ){
factory = SESecurityManager.installServerCertificates( "AZ-sniffer:" + delegate_to_host + ":" + port, delegate_to_host, delegate_to_port );
socket_out = factory.createSocket();
socket_out.connect( new InetSocketAddress( delegate_to_host, delegate_to_port ), 30*1000 );
}
}else{
socket_out = new Socket();
socket_out.connect( new InetSocketAddress( delegate_to_host, delegate_to_port ), CONNECT_TIMEOUT );
}
}catch( Throwable e ){
if ( e instanceof IOException ){
throw((IOException)e );
}
throw( new IOException( e.toString()));
}finally{
if ( socket_out != null ){
synchronized( this ){
if ( destroyed ){
try{
socket_out.close();
}catch( Throwable e ){
}finally{
socket_out = null;
}
throw( new IOException( "destroyed" ));
}
}
}
}
}
protected void
process(
String request_header )
throws Exception
{
final OutputStream target_os = socket_out.getOutputStream();
String[] request_lines = splitHeader( request_header );
String target_url = request_lines[0];
int space_pos = target_url.indexOf(' ');
target_url = target_url.substring( space_pos ).trim();
space_pos = target_url.indexOf(' ');
target_url = target_url.substring( 0, space_pos ).trim();
trace( "Page request for " + target_url );
List cookies_to_remove = new ArrayList();
for (int i=0;i<request_lines.length;i++){
String line_out = request_lines[i];
String line_in = line_out.trim().toLowerCase();
String[] bits = line_in.split(":");
if ( bits.length >= 2 ){
String lhs = bits[0].trim();
if ( lhs.equals( "host" )){
String port_str;
if ( delegate_to_port == 80 || delegate_to_port == 443 ){
port_str = "";
}else{
port_str = ":" + delegate_to_port;
}
line_out = "Host: " + delegate_to_host + port_str;
}else if ( lhs.equals( "connection" )){
line_out = "Connection: close";
}else if ( lhs.equals( "referer" )){
String page = line_out.substring( line_out.indexOf( ':' )+1).trim();
page = page.substring( page.indexOf( "://") + 3);
int pos = page.indexOf( '/' );
if ( pos >= 0 ){
page = page.substring( pos );
}else{
page = "/";
}
String port_str;
if ( delegate_to_port == 80 || delegate_to_port == 443 ){
port_str = "";
}else{
port_str = ":" + delegate_to_port;
}
line_out = "Referer: http" + (delegate_is_https?"s":"") + "://" + delegate_to_host + port_str + page;
}else if ( lhs.equals( "cookie" )){
String cookies_str = line_out.substring( line_out.indexOf( ':' )+1).trim();
String[] cookies = cookies_str.split( ";" );
String cookies_out = "";
for (int j=0;j<cookies.length;j++){
String cookie = cookies[j];
String name = cookie.split( "=" )[0].trim();
if ( hasSetCookieName( name )){
cookies_out += (cookies_out.length()==0?"":"; ") + cookie;
}else{
cookies_to_remove.add( name );
}
}
if ( cookies_out.length() > 0 ){
line_out = "Cookie: " + cookies_out;
}else{
line_out = null;
}
}
}
if ( line_out != null ){
trace( "-> " + line_out );
target_os.write((line_out+NL).getBytes());
}
}
target_os.write( NL.getBytes());
target_os.flush();
new AEThread2( "HTTPSniffingProxy:proc:2", true )
{
public void
run()
{
try{
InputStream source_is = socket_in.getInputStream();
byte[] buffer = new byte[32000];
while( !destroyed ){
int len = source_is.read( buffer );
if ( len <= 0 ){
break;
}
target_os.write( buffer, 0, len );
trace( "POST:" + new String( buffer, 0, len ));
}
}catch( Throwable e ){
}
}
}.start();
InputStream target_is = socket_out.getInputStream();
OutputStream source_os = socket_in.getOutputStream();
String reply_header = readHeader( target_is );
String[] reply_lines = splitHeader( reply_header );
String content_type = null;
String content_charset = "ISO-8859-1";
for (int i=0;i<reply_lines.length;i++){
String line_in = reply_lines[i].trim().toLowerCase();
String[] bits = line_in.split(":");
if ( bits.length >= 2 ){
String lhs = bits[0].trim();
if ( lhs.equals( "content-type" )){
String rhs = reply_lines[i].substring( line_in.indexOf( ':' ) + 1 ).trim();
String[] x = rhs.split( ";" );
content_type = x[0];
if ( x.length > 1 ){
int pos = rhs.toLowerCase().indexOf( "charset" );
if ( pos >= 0 ){
String cc = rhs.substring( pos+1 );
pos = cc.indexOf('=');
if ( pos != -1 ){
cc = cc.substring( pos+1 ).trim();
if ( Charset.isSupported( cc )){
content_charset = cc;
}
}
}
}
}
}
}
boolean rewrite = false;
boolean chunked = false;
String content_encoding = null;
if ( content_type == null ){
rewrite = true;
}else{
content_type = content_type.toLowerCase();
if ( content_type.indexOf( "text/" ) != -1 ){
rewrite = true;
}
}
for (int i=0;i<reply_lines.length;i++){
String line_out = reply_lines[i];
String line_in = line_out.trim().toLowerCase();
String[] bits = line_in.split(":");
if ( bits.length >= 2 ){
String lhs = bits[0].trim();
if ( lhs.equals( "set-cookie" )){
String cookies_in = line_out.substring( line_out.indexOf( ':' )+1 );
String[] cookies;
if ( cookies_in.toLowerCase().indexOf( "expires" ) == -1 ){
cookies = cookies_in.split( "," );
}else{
cookies = new String[]{ cookies_in };
}
String cookies_out = "";
for (int c=0;c<cookies.length;c++){
String cookie = cookies[c];
String[] x = cookie.split( ";" );
String modified_cookie = "";
for (int j=0;j<x.length;j++){
String entry = x[j].trim();
if ( entry.equalsIgnoreCase( "httponly" )){
setHTTPOnlyCookieDetected();
}else if ( entry.equalsIgnoreCase( "secure" )){
}else if ( entry.toLowerCase().startsWith( "domain" )){
// remove domain restriction so cookie sent to localhost
}else if ( entry.toLowerCase().startsWith( "expires" )){
// force to be session cookie otherwise we'll end up sending
// cookies from multiple sites to 'localhost'
}else{
if ( j == 0 ){
int pos = entry.indexOf( '=' );
String name = entry.substring( 0, pos ).trim();
String value = entry.substring( pos+1 ).trim();
addSetCookieName( name, value );
}
modified_cookie += (modified_cookie.length()==0?"":"; ") + entry;
}
}
cookies_out += (c==0?"":", " ) + modified_cookie;
}
line_out = "Set-Cookie: " + cookies_out;
}else if ( lhs.equals( "set-cookie2" )){
// http://www.ietf.org/rfc/rfc2965.txt
// one or more comma separated
String cookies_in = line_out.substring( line_out.indexOf( ':' )+1 );
String[] cookies = cookies_in.split( "," );
String cookies_out = "";
for (int c=0;c<cookies.length;c++){
String cookie = cookies[c];
String[] x = cookie.split( ";" );
String modified_cookie = "";
for (int j=0;j<x.length;j++){
String entry = x[j].trim();
if ( entry.equalsIgnoreCase( "secure" )){
}else if ( entry.equalsIgnoreCase( "discard" )){
}else if ( entry.toLowerCase().startsWith( "domain" )){
}else if ( entry.toLowerCase().startsWith( "port" )){
}else{
if ( j == 0 ){
int pos = entry.indexOf( '=' );
String name = entry.substring( 0, pos ).trim();
String value = entry.substring( pos+1 ).trim();
addSetCookieName( name, value );
}
modified_cookie += (modified_cookie.length()==0?"":"; ") + entry;
}
}
cookies_out += (c==0?"":", " ) + modified_cookie + "; Discard";
}
line_out = "Set-Cookie2: " + cookies_out;
}else if ( lhs.equals( "connection" )){
line_out = "Connection: close";
}else if ( lhs.equals( "location" )){
String page = line_out.substring( line_out.indexOf( ':' )+1).trim();
String child_url = page.trim();
HTTPAuthHelper child = getChild( child_url, false );
int pos = page.indexOf( "://" );
if ( pos >= 0 ){
// absolute
page = page.substring( pos + 3);
pos = page.indexOf( '/' );
if ( pos >= 0 ){
page = page.substring( pos );
}else{
page = "/";
}
}else{
// relative. actually illegal as must be absolute
if ( !page.startsWith( "/" )){
String temp = target_url;
int marker = temp.indexOf( "://" );
if ( marker != -1 ){
// strip out absolute part
temp = temp.substring( marker + 3 );
marker = temp.indexOf( "/" );
if ( marker == -1 ){
temp = "/";
}else{
temp = temp.substring( marker );
}
}else{
if ( !temp.startsWith( "/" )){
temp = "/" + temp;
}
}
marker = temp.lastIndexOf( "/" );
if ( marker >= 0 ){
temp = temp.substring( 0, marker+1 );
}
page = temp + page;
}
}
line_out = "Location: http://127.0.0.1:" + child.getPort() + page;
}else if ( lhs.equals( "content-encoding" )){
if ( rewrite ){
String encoding = bits[1].trim();
if ( encoding.equalsIgnoreCase( "gzip" ) ||
encoding.equalsIgnoreCase( "deflate" )){
content_encoding = encoding;
line_out = null;
}
}
}else if ( lhs.equals( "content-length" )){
if ( rewrite ){
line_out = null;
}
}else if ( lhs.equals( "transfer-encoding" )){
if ( bits[1].indexOf( "chunked" ) != -1 ){
chunked = true;
if ( rewrite ){
line_out = null;
}
}
}
}
if ( line_out != null ){
trace( "<- " + line_out );
source_os.write((line_out+NL).getBytes());
}
}
for ( int i=0;i<cookies_to_remove.size();i++ ){
String name = (String)cookies_to_remove.get(i);
if ( !hasSetCookieName( name )){
String remove_str = "Set-Cookie: " + name + "=X; expires=Sun, 01 Jan 2000 01:00:00 GMT";
trace( "<- (cookie removal) " + remove_str );
source_os.write((remove_str+NL).getBytes());
remove_str = "Set-Cookie2: " + name + "=X; Max-Age=0; Version=1";
trace( "<- (cookie removal) " + remove_str );
source_os.write((remove_str+NL).getBytes());
}
}
byte[] buffer = new byte[32000];
if ( rewrite ){
StringBuffer sb = new StringBuffer();
if ( chunked ){
// chunking uses ISO-8859-1
while( true ){
int len = target_is.read( buffer );
if ( len <= 0 ){
break;
}
sb.append(new String( buffer, 0, len, "ISO-8859-1" ));
}
StringBuffer sb_dechunked = new StringBuffer( sb.length());
String chunk = "";
int total_length = 0;
int sb_pos = 0;
while( sb_pos < sb.length()){
chunk += sb.charAt( sb_pos++ );
// second time around the chunk will be prefixed with NL
// from end of previous
// so make sure we ignore this
if ( chunk.endsWith( NL ) && chunk.length() > 2 ){
int semi_pos = chunk.indexOf( ';' );
if ( semi_pos != -1 ){
chunk = chunk.substring( 0, semi_pos );
}
chunk = chunk.trim();
int chunk_length = Integer.parseInt( chunk, 16 );
if ( chunk_length <= 0 ){
break;
}
total_length += chunk_length;
if ( total_length > 2*1024*1024 ){
throw (new IOException("Chunk size " + chunk_length
+ " too large"));
}
char[] chunk_buffer = new char[chunk_length];
sb.getChars( sb_pos, sb_pos + chunk_length, chunk_buffer, 0 );
sb_dechunked.append( chunk_buffer );
sb_pos += chunk_length;
chunk = "";
}
}
// dechunked ISO-8859-1 - unzip if required and then apply correct charset
target_is = new ByteArrayInputStream( sb_dechunked.toString().getBytes( "ISO-8859-1" ));
}
if ( content_encoding != null ){
if ( content_encoding.equalsIgnoreCase( "gzip" )){
target_is = new GZIPInputStream( target_is );
}else if ( content_encoding.equalsIgnoreCase( "deflate" )){
target_is = new InflaterInputStream( target_is );
}
}
sb.setLength(0);
while( !destroyed ){
int len = target_is.read( buffer );
if ( len <= 0 ){
break;
}
sb.append(new String( buffer, 0, len, content_charset ));
}
String str = sb.toString();
String lc_str = str.toLowerCase();
StringBuffer result = null;
int str_pos = 0;
// FileUtil.writeBytesAsFile( "C:\\temp\\xxx" + new Random().nextInt(100000) + ".txt", str.getBytes());
while( true ){
// http://a.b
int url_start = str.length() - str_pos >=10?lc_str.indexOf( "http", str_pos ):-1;
if ( url_start == -1 ){
break;
}
int match_pos;
if ( lc_str.charAt( url_start + 4 ) == 's' ){
match_pos = url_start + 5;
}else{
match_pos = url_start + 4;
}
if ( lc_str.substring( match_pos, match_pos+3 ).equals( "://" )){
int url_end = -1;
for (int i=match_pos+3;;i++){
char c = lc_str.charAt(i);
if ( c == '/' ){
url_end = i+1;
break;
}else if ( c == '.' || c == '-' || c == ':' ){
}else if ( c >= '0' && c <= '9' ){
}else if ( c >= 'a' && c <= 'z' ){
}else{
url_end = i;
break;
}
if ( i == lc_str.length()-1 ){
url_end = i;
}
}
if ( url_end > url_start ){
String url_str = str.substring( url_start, url_end );
boolean appended = false;
try{
// make sure vald URL
URL url = new URL( url_str );
if ( url.getHost().length() > 0 ){
boolean existing_only = true;
// override if form action or meta
for (int i=url_start-1;i>=0&&url_start-i<512;i--){
if ( lc_str.charAt( i ) == '<' ){
String prefix = lc_str.substring(i, url_start);
if ( prefix.indexOf( "form" ) != -1 ){
existing_only = false;
}else if ( prefix.indexOf( "meta" ) != -1 &&
prefix.indexOf( "http-equiv" ) != -1 ){
existing_only = false;
}
break;
}
}
HTTPAuthHelper child = getChild( url_str, existing_only );
if ( child != null ){
String replacement = "http://127.0.0.1:" + child.getPort();
if ( url_str.endsWith( "/" )){
replacement += "/";
}
if ( result == null ){
result = new StringBuffer( str.length());
if ( url_start > 0 ){
result.append( str.subSequence( 0, url_start ));
}
}else if ( url_start > str_pos ){
result.append( str.subSequence( str_pos, url_start ));
}
trace( "Replacing " + url_str + " with " + replacement );
result.append( replacement );
appended = true;
}else{
trace( " No child for " + url_str );
}
}
}catch( Throwable e ){
}
if ( result != null && !appended ){
result.append( str.subSequence( str_pos, url_end ));
}
str_pos = url_end;
}else{
break;
}
}else{
if ( result != null ){
result.append( str.subSequence( str_pos, match_pos ));
}
str_pos = match_pos;
}
}
if ( result != null ){
if ( str_pos < str.length() ){
result.append( str.subSequence( str_pos, str.length()));
}
sb = result;
}
source_os.write( ( "Content-Length: " + sb.length() + NL ).getBytes());
source_os.write( NL.getBytes());
source_os.write( sb.toString().getBytes( content_charset ));
}else{
source_os.write( NL.getBytes());
while( !destroyed ){
int len = target_is.read( buffer );
if ( len <= 0 ){
break;
}
source_os.write( buffer, 0, len );
}
}
}
protected String
readHeader(
InputStream is )
throws IOException
{
String header = "";
byte[] buffer = new byte[1];
while( true ){
if ( is.read( buffer ) != 1 ){
break;
}
header += (char)buffer[0];
if ( header.endsWith( NL + NL )){
break;
}
}
return( header );
}
protected String[]
splitHeader(
String str )
{
String[] bits = str.split( NL );
return( bits );
}
protected void
destroy()
{
synchronized( this ){
if ( destroyed ){
return;
}
destroyed = true;
}
if ( socket_out != null ){
try{
socket_out.close();
}catch( Throwable e ){
}
}
try{
socket_in.close();
}catch( Throwable e ){
}
}
}
protected void
trace(
String str )
{
if ( TRACE ){
System.out.println( str );
}
}
public static void
main(
String[] args )
{
try{
HTTPAuthHelper proxy = new HTTPAuthHelper( new URL( "http://www.sf.net/" ));
proxy.start();
System.out.println( "port=" + proxy.getPort());
while( true ){
Thread.sleep(1000);
}
}catch( Throwable e ){
e.printStackTrace();
}
}
}