/*
* Created on 2 Oct 2006
* Created by Paul Gardner
* Copyright (C) 2006 Aelitis, 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; either version 2
* of the License, or (at your option) any later version.
* 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.
*
* AELITIS, SAS au capital de 63.529,40 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/
package com.aelitis.azureus.core.networkmanager.impl.http;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.peer.impl.PEPeerTransport;
import org.gudy.azureus2.core3.torrent.TOTorrentFile;
import org.gudy.azureus2.core3.util.BEncoder;
import org.gudy.azureus2.core3.util.Debug;
import com.aelitis.azureus.core.networkmanager.NetworkConnection;
import com.aelitis.azureus.core.networkmanager.NetworkManager;
import com.aelitis.azureus.core.networkmanager.Transport;
import com.aelitis.azureus.core.networkmanager.impl.TransportHelper;
import com.aelitis.azureus.core.networkmanager.impl.tcp.IncomingSocketChannelManager;
import com.aelitis.azureus.core.peermanager.PeerManager;
import com.aelitis.azureus.core.peermanager.PeerManagerRegistration;
import com.aelitis.azureus.core.peermanager.PeerManagerRoutingListener;
import com.aelitis.azureus.core.peermanager.messaging.MessageStreamDecoder;
import com.aelitis.azureus.core.peermanager.messaging.MessageStreamEncoder;
import com.aelitis.azureus.core.peermanager.messaging.MessageStreamFactory;
import com.aelitis.azureus.core.stats.AzureusCoreStats;
import com.aelitis.azureus.core.stats.AzureusCoreStatsProvider;
import com.aelitis.azureus.core.util.CopyOnWriteList;
public class
HTTPNetworkManager
{
private static final String NL = "\r\n";
private static final LogIDs LOGID = LogIDs.NWMAN;
private static final HTTPNetworkManager instance = new HTTPNetworkManager();
public static HTTPNetworkManager getSingleton(){ return( instance ); }
private final IncomingSocketChannelManager http_incoming_manager;
private long total_requests;
private long total_webseed_requests;
private long total_getright_requests;
private long total_invalid_requests;
private long total_ok_requests;
private CopyOnWriteList<URLHandler> url_handlers = new CopyOnWriteList<URLHandler>();
private
HTTPNetworkManager()
{
Set types = new HashSet();
types.add( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_COUNT );
types.add( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_OK_COUNT );
types.add( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_INVALID_COUNT );
types.add( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_WEBSEED_COUNT );
types.add( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_GETRIGHT_COUNT );
AzureusCoreStats.registerProvider(
types,
new AzureusCoreStatsProvider()
{
public void
updateStats(
Set types,
Map values )
{
if ( types.contains( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_COUNT )){
values.put( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_COUNT, new Long( total_requests ));
}
if ( types.contains( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_OK_COUNT )){
values.put( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_OK_COUNT, new Long( total_ok_requests ));
}
if ( types.contains( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_INVALID_COUNT )){
values.put( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_INVALID_COUNT, new Long( total_invalid_requests ));
}
if ( types.contains( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_WEBSEED_COUNT )){
values.put( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_WEBSEED_COUNT, new Long( total_webseed_requests ));
}
if ( types.contains( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_GETRIGHT_COUNT )){
values.put( AzureusCoreStats.ST_NET_HTTP_IN_REQUEST_GETRIGHT_COUNT, new Long( total_getright_requests)); }
}
});
/*
try{
System.out.println( "/webseed?info_hash=" + URLEncoder.encode( new String( ByteFormatter.decodeString("C9C04D96F11FB5C5ECC99D418D3575FBFC2208B0"), "ISO-8859-1"), "ISO-8859-1" ));
}catch( Throwable e ){
e.printStackTrace();
}
*/
http_incoming_manager = new IncomingSocketChannelManager( "HTTP.Data.Listen.Port", "HTTP.Data.Listen.Port.Enable" );
NetworkManager.ByteMatcher matcher =
new NetworkManager.ByteMatcher()
{
public int matchThisSizeOrBigger(){ return( 4 + 1 + 11 ); } // GET ' ' <url of 1> ' HTTP/1.1<cr><nl>'
public int maxSize() { return 256; } // max GET <url> size - boiler plate plus small url plus hash
public int minSize() { return 3; } // enough to match GET
public Object
matches(
TransportHelper transport,
ByteBuffer to_compare,
int port )
{
total_requests++;
InetSocketAddress address = transport.getAddress();
int old_limit = to_compare.limit();
int old_position = to_compare.position();
boolean ok = false;
try{
byte[] head = new byte[3];
to_compare.get( head );
// note duplication of this in min-matches below
if ( head[0] != 'G' || head[1] != 'E' || head[2] != 'T' ){
return( null );
}
byte[] line_bytes = new byte[to_compare.remaining()];
to_compare.get( line_bytes );
try{
// format is GET url HTTP/1.1<NL>
String url = new String( line_bytes, "ISO-8859-1" );
int space = url.indexOf(' ');
if ( space == -1 ){
return( null );
}
// note that we don't insist on a full URL here, just the start of one
url = url.substring( space + 1 ).trim();
if ( url.indexOf( "/index.html") != -1 ){
ok = true;
return( new Object[]{ transport, getIndexPage() });
}else if ( url.indexOf( "/ping.html") != -1 ){
// ping is used for inbound HTTP port checking
ok = true;
return( new Object[]{ transport, getPingPage( url ) });
}else if ( url.indexOf( "/test503.html" ) != -1 ){
ok = true;
return( new Object[]{ transport, getTest503()});
}
String hash_str = null;
int hash_pos = url.indexOf( "?info_hash=" );
if ( hash_pos != -1 ){
int hash_start = hash_pos + 11;
int hash_end = url.indexOf( '&', hash_pos );
if ( hash_end == -1 ){
// not read the end yet
return( null );
}else{
hash_str = url.substring( hash_start, hash_end );
}
}else{
hash_pos = url.indexOf( "/files/" );
if ( hash_pos != -1 ){
int hash_start = hash_pos + 7;
int hash_end = url.indexOf('/', hash_start );
if ( hash_end == -1 ){
// not read the end of the hash yet
return( null );
}else{
hash_str = url.substring( hash_start, hash_end );
}
}
}
if ( hash_str != null ){
byte[] hash = URLDecoder.decode( hash_str, "ISO-8859-1" ).getBytes( "ISO-8859-1" );
PeerManagerRegistration reg_data = PeerManager.getSingleton().manualMatchHash( address, hash );
if ( reg_data != null ){
// trim back URL as it currently has header in it too
int pos = url.indexOf( ' ' );
String trimmed = pos==-1?url:url.substring(0,pos);
ok = true;
return( new Object[]{ trimmed, reg_data });
}
}else{
int link_pos = url.indexOf( "/links/" );
if ( link_pos != -1 ){
int pos = url.indexOf( ' ', link_pos );
if ( pos == -1 ){
return( null );
}
String link = url.substring(0,pos).substring( link_pos+7 );
link = URLDecoder.decode( link, "UTF-8" );
PeerManagerRegistration reg_data = PeerManager.getSingleton().manualMatchLink( address, link );
if ( reg_data != null ){
TOTorrentFile file = reg_data.getLink( link );
if ( file != null ){
StringBuffer target_url = new StringBuffer( 512 );
target_url.append( "/files/" );
target_url.append( URLEncoder.encode( new String( file.getTorrent().getHash(), "ISO-8859-1" ), "ISO-8859-1" ));
byte[][] bits = file.getPathComponents();
for (int i=0;i<bits.length;i++){
target_url.append( "/" );
target_url.append( URLEncoder.encode( new String( bits[i], "ISO-8859-1" ), "ISO-8859-1" ));
}
ok = true;
return( new Object[]{ target_url.toString(), reg_data });
}
}
}
}
String trimmed = url;
int pos = trimmed.indexOf( ' ' );
if ( pos != -1 ){
trimmed = trimmed.substring( 0, pos );
}
for ( URLHandler handler: url_handlers ){
if ( handler.matches( trimmed )){
ok = true;
return( new Object[]{ handler, transport, "GET " + url });
}
}
if (Logger.isEnabled()){
Logger.log(new LogEvent(LOGID, "HTTP decode from " + address + " failed: no match for " + url ));
}
return( new Object[]{ transport, getNotFound() });
}catch( Throwable e ){
if (Logger.isEnabled()){
Logger.log(new LogEvent(LOGID, "HTTP decode from " + address + " failed, " + e.getMessage()));
}
return( null );
}
}finally{
if ( ok ){
total_ok_requests++;
}else{
total_invalid_requests++;
}
// restore buffer structure
to_compare.limit( old_limit );
to_compare.position( old_position );
}
}
public Object
minMatches(
TransportHelper transport,
ByteBuffer to_compare,
int port )
{
byte[] head = new byte[3];
to_compare.get( head );
if (head[0] != 'G' || head[1] != 'E' || head[2] != 'T' ){
return( null );
}
return( "" );
}
public byte[][]
getSharedSecrets()
{
return( null );
}
public int
getSpecificPort()
{
return( http_incoming_manager.getTCPListeningPortNumber());
}
};
// register for incoming connection routing
NetworkManager.getSingleton().requestIncomingConnectionRouting(
matcher,
new NetworkManager.RoutingListener()
{
public void
connectionRouted(
final NetworkConnection connection,
Object _routing_data )
{
Object[] x = (Object[])_routing_data;
Object entry1 = x[0];
if ( entry1 instanceof TransportHelper ){
// routed on failure
writeReply(connection, (TransportHelper)x[0], (String)x[1]);
return;
}else if ( entry1 instanceof URLHandler ){
((URLHandler)entry1).handle(
(TransportHelper)x[1],
(String)x[2] );
return;
}
final String url = (String)entry1;
final PeerManagerRegistration routing_data = (PeerManagerRegistration)x[1];
if (Logger.isEnabled()){
Logger.log(new LogEvent(LOGID, "HTTP connection from " + connection.getEndpoint().getNotionalAddress() + " routed successfully on '" + url + "'" ));
}
PeerManager.getSingleton().manualRoute(
routing_data,
connection,
new PeerManagerRoutingListener()
{
public boolean
routed(
PEPeerTransport peer )
{
if ( url.indexOf( "/webseed" ) != -1 ){
total_webseed_requests++;
new HTTPNetworkConnectionWebSeed( HTTPNetworkManager.this, connection, peer );
return( true );
}else if ( url.indexOf( "/files/" ) != -1 ){
total_getright_requests++;
new HTTPNetworkConnectionFile( HTTPNetworkManager.this, connection, peer );
return( true );
}
return( false );
}
});
}
public boolean
autoCryptoFallback()
{
return( false );
}
},
new MessageStreamFactory() {
public MessageStreamEncoder createEncoder() { return new HTTPMessageEncoder(); }
public MessageStreamDecoder createDecoder() { return new HTTPMessageDecoder(); }
});
}
protected void
reRoute(
final HTTPNetworkConnection old_http_connection,
final byte[] old_hash,
final byte[] new_hash,
final String header )
{
final NetworkConnection old_connection = old_http_connection.getConnection();
PeerManagerRegistration reg_data =
PeerManager.getSingleton().manualMatchHash(
old_connection.getEndpoint().getNotionalAddress(),
new_hash );
if ( reg_data == null ){
old_http_connection.close( "Re-routing failed - registration not found" );
return;
}
final Transport transport = old_connection.detachTransport();
old_http_connection.close( "Switching torrents" );
final NetworkConnection new_connection =
NetworkManager.getSingleton().bindTransport(
transport,
new HTTPMessageEncoder(),
new HTTPMessageDecoder( header ));
PeerManager.getSingleton().manualRoute(
reg_data,
new_connection,
new PeerManagerRoutingListener()
{
public boolean
routed(
PEPeerTransport peer )
{
HTTPNetworkConnection new_http_connection;
if ( header.indexOf( "/webseed" ) != -1 ){
new_http_connection = new HTTPNetworkConnectionWebSeed( HTTPNetworkManager.this, new_connection, peer );
}else if ( header.indexOf( "/files/" ) != -1 ){
new_http_connection = new HTTPNetworkConnectionFile( HTTPNetworkManager.this, new_connection, peer );
}else{
return( false );
}
// fake a wakeup so pre-read header is processed
new_http_connection.readWakeup();
/*
System.out.println(
"Re-routed " + new_connection.getEndpoint().getNotionalAddress() +
" from " + ByteFormatter.encodeString( old_hash ) + " to " +
ByteFormatter.encodeString( new_hash ) );
*/
return( true );
}
});
}
public boolean
isHTTPListenerEnabled()
{
return( http_incoming_manager.isEnabled());
}
public int
getHTTPListeningPortNumber()
{
return( http_incoming_manager.getTCPListeningPortNumber());
}
public void
setExplicitBindAddress(
InetAddress address )
{
http_incoming_manager.setExplicitBindAddress( address );
}
public void
clearExplicitBindAddress()
{
http_incoming_manager.clearExplicitBindAddress();
}
public boolean
isEffectiveBindAddress(
InetAddress address )
{
return( http_incoming_manager.isEffectiveBindAddress( address ));
}
protected String
getIndexPage()
{
return( "HTTP/1.1 200 OK" + NL +
"Connection: Close" + NL +
"Content-Length: 0" + NL +
NL );
}
protected String
getPingPage(
String url )
{
int pos = url.indexOf( ' ' );
if ( pos != -1 ){
url = url.substring( 0, pos );
}
pos = url.indexOf( '?' );
Map response = new HashMap();
boolean ok = false;
if ( pos != -1 ){
StringTokenizer tok = new StringTokenizer(url.substring(pos+1), "&");
while( tok.hasMoreTokens()){
String token = tok.nextToken();
pos = token.indexOf('=');
if ( pos != -1 ){
String lhs = token.substring(0,pos);
String rhs = token.substring(pos+1);
if ( lhs.equals( "check" )){
response.put( "check", rhs );
ok = true;
}
}
}
}
if ( ok ){
try{
byte[] bytes = BEncoder.encode( response );
byte[] length = new byte[4];
ByteBuffer.wrap( length ).putInt( bytes.length );
return( "HTTP/1.1 200 OK" + NL +
"Connection: Close" + NL +
"Content-Length: " + ( bytes.length + 4 )+ NL +
NL +
new String( length, "ISO-8859-1" ) + new String( bytes, "ISO-8859-1" ) );
}catch( Throwable e ){
}
}
return( getNotFound());
}
protected String
getTest503()
{
return( "HTTP/1.1 503 Service Unavailable" + NL +
"Connection: Close" + NL +
"Content-Length: 4" + NL +
NL +
"1234" );
}
protected String
getNotFound()
{
return( "HTTP/1.1 404 Not Found" + NL +
"Connection: Close" + NL +
"Content-Length: 0" + NL +
NL );
}
protected String
getRangeNotSatisfiable()
{
return( "HTTP/1.1 416 Not Satisfiable" + NL +
"Connection: Close" + NL +
"Content-Length: 0" + NL +
NL );
}
protected void
writeReply(
final NetworkConnection connection,
final TransportHelper transport,
final String data )
{
byte[] bytes;
try{
bytes = data.getBytes( "ISO-8859-1" );
}catch( UnsupportedEncodingException e ){
bytes = data.getBytes();
}
final ByteBuffer bb = ByteBuffer.wrap( bytes );
try{
transport.write( bb, false );
if ( bb.remaining() > 0 ){
transport.registerForWriteSelects(
new TransportHelper.selectListener()
{
public boolean
selectSuccess(
TransportHelper helper,
Object attachment )
{
try{
int written = helper.write( bb, false );
if ( bb.remaining() > 0 ){
helper.registerForWriteSelects( this, null );
}else{
if (Logger.isEnabled()){
Logger.log(new LogEvent(LOGID, "HTTP connection from " + connection.getEndpoint().getNotionalAddress() + " closed" ));
}
connection.close( null );
}
return( written > 0 );
}catch( Throwable e ){
helper.cancelWriteSelects();
if (Logger.isEnabled()){
Logger.log(new LogEvent(LOGID, "HTTP connection from " + connection.getEndpoint().getNotionalAddress() + " failed to write error '" + data + "'" ));
}
connection.close(e==null?null:Debug.getNestedExceptionMessage(e));
return( false );
}
}
public void
selectFailure(
TransportHelper helper,
Object attachment,
Throwable msg)
{
helper.cancelWriteSelects();
if (Logger.isEnabled()){
Logger.log(new LogEvent(LOGID, "HTTP connection from " + connection.getEndpoint().getNotionalAddress() + " failed to write error '" + data + "'" ));
}
connection.close(msg==null?null:Debug.getNestedExceptionMessage(msg));
}
},
null );
}else{
if (Logger.isEnabled()){
Logger.log(new LogEvent(LOGID, "HTTP connection from " + connection.getEndpoint().getNotionalAddress() + " closed" ));
}
connection.close( null );
}
}catch( Throwable e ){
if (Logger.isEnabled()){
Logger.log(new LogEvent(LOGID, "HTTP connection from " + connection.getEndpoint().getNotionalAddress() + " failed to write error '" + data + "'" ));
}
connection.close(e==null?null:Debug.getNestedExceptionMessage(e));
}
}
public void
addURLHandler(
URLHandler handler )
{
url_handlers.add( handler );
}
public void
removeURLHandler(
URLHandler handler )
{
url_handlers.remove( handler );
}
public interface
URLHandler
{
public boolean
matches(
String url );
public void
handle(
TransportHelper transport,
String header_so_far );
}
}