package com.limegroup.gnutella.browser;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import com.limegroup.gnutella.ByteReader;
import com.limegroup.gnutella.Constants;
import com.limegroup.gnutella.MessageService;
import com.limegroup.gnutella.io.AcceptObserver;
import com.limegroup.gnutella.io.NIOServerSocket;
import com.limegroup.gnutella.io.SocketFactory;
import com.limegroup.gnutella.util.IOUtils;
import com.limegroup.gnutella.util.NetworkUtils;
import com.limegroup.gnutella.util.ThreadFactory;
import com.limegroup.gnutella.util.URLDecoder;
/**
* Listens on an HTTP port, accepts incoming connections, and dispatches
* threads to handle requests. This allows simple HTTP requests.
*/
public class HTTPAcceptor {
/** Magnet request for a default action on parameters */
private static final String MAGNET_DEFAULT = "magnet10/default.js?";
/** Magnet request for a paused response */
private static final String MAGNET_PAUSE = "magnet10/pause";
/** Start of Magnet URI */
private static final String MAGNET = "magnet:?";
/** HTTP no content return */
private static final String NOCONTENT = "HTTP/1.1 204 No Content\r\n";
/** Magnet detail command */
private static final String MAGNETDETAIL = "magcmd/detail?";
/**
* The socket that listens for incoming connections. Can be changed to
* listen to new ports.
*
* LOCKING: obtain _socketLock before modifying either. Notify _socketLock
* when done.
*/
private volatile ServerSocket _socket=null;
private int _port=45100;
/** Try to supress duplicate requests from some browsers */
private static String _lastRequest = null;
private static long _lastRequestTime = 0;
/**
* Starts listening to incoming connections.
*/
public void start() {
// Create the server socket, bind it to a port, and listen for
// incoming connections. If there are problems, we can continue
// onward.
//1. Try suggested port.
try {
setListeningPort(_port);
} catch (IOException e) {
boolean error = true;
//2. Try 20 different consecutive ports from 45100 to 45119 (inclusive)
// no longer random, since this listening socket is used by the executable stub
// to launch magnet downloads, so the stub needs to know what (small) range of
// ports to try...
for (int i=1; i<20; i++) {
_port = i+45100;
try {
setListeningPort(_port);
error = false;
break;
} catch (IOException ignored) {}
}
// no luck setting up? show user error message
if(error)
MessageService.showError("ERROR_NO_PORTS_AVAILABLE");
}
}
/**
* @requires only one thread is calling this method at a time
* @modifies this
* @effects sets the port on which the ConnectionManager is listening.
* If that fails, this is <i>not</i> modified and IOException is thrown.
* If port==0, tells this to stop listening to incoming connections.
* This is properly synchronized and can be called even while run() is
* being called.
*/
private void setListeningPort(int port) throws IOException {
//1. Special case: if unchanged, do nothing.
if (_socket!=null && _port==port) {
return;
}
//2. Special case if port==0. This ALWAYS works.
//Note that we must close the socket BEFORE grabbing
//the lock. Otherwise deadlock will occur since
//the acceptor thread is listening to the socket
//while holding the lock. Also note that port
//will not have changed before we grab the lock.
else if (port == 0) {
IOUtils.close(_socket);
_socket = null;
_port = 0;
return;
}
//3. Normal case. See note about locking above.
else {
//a) Try new port.
ServerSocket newSocket=null;
try {
newSocket = SocketFactory.newServerSocket(port, new SocketListener());
} catch (IOException e) {
throw e;
}
//b) Close old socket
IOUtils.close(_socket);
//c) Replace with new sock.
_socket=newSocket;
_port=port;
return;
}
}
/**
* Return the listening port.
*/
public int getPort() {
return _port;
}
/** Dispatches sockets to a thread that'll handle them. */
private class SocketListener implements AcceptObserver {
public void handleIOException(IOException ignored) {}
public void shutdown() {}
public void handleAccept(Socket client) {
if(NetworkUtils.isLocalHost(client)) {
// Dispatch asynchronously.
ThreadFactory.startThread(new ConnectionDispatchRunner(client), "ConnectionDispatchRunner");
}
}
}
private class ConnectionDispatchRunner implements Runnable {
private Socket _socket;
/**
* @modifies socket, this' managers
* @effects starts a new thread to handle the given socket and
* registers it with the appropriate protocol-specific manager.
* Returns once the thread has been started. If socket does
* not speak a known protocol, closes the socket immediately and
* returns.
*/
public ConnectionDispatchRunner(Socket socket) {
_socket=socket;
}
public void run() {
try {
InputStream in = _socket.getInputStream();
_socket.setSoTimeout(Constants.TIMEOUT);
//dont read a word of size more than 8
//("GNUTELLA" is the longest word we know at this time)
String word=IOUtils.readWord(in,8);
_socket.setSoTimeout(0);
if (word.equals("GET")) {
handleHTTPRequest(_socket);
} else if (word.equals("MAGNET")) {
ExternalControl.fireMagnet(_socket);
}
} catch (IOException e) {
} finally {
IOUtils.close(_socket);
}
}
}
/**
* Read and parse any HTTP request. Forward on to HTTPHandler.
*
* @param socket the <tt>Socket</tt> instance over which we're reading
*/
private void handleHTTPRequest(Socket socket) throws IOException {
// Set the timeout so that we don't do block reading.
socket.setSoTimeout(Constants.TIMEOUT);
// open the stream from the socket for reading
ByteReader br = new ByteReader(socket.getInputStream());
// read the first line. if null, throw an exception
String str = br.readLine();
if (str == null) {
throw new IOException();
}
str.trim();
str = URLDecoder.decode(str);
if (str.indexOf("magnet10") > 0) {
int loc = 0;
if ((loc = str.indexOf(MAGNET_DEFAULT)) > 0) {
//Parse params out
int loc2 = str.lastIndexOf(" HTTP");
String command =
str.substring(loc+MAGNET_DEFAULT.length(), loc2);
triggerMagnetHandling(socket, MAGNET+command);
} else if ((loc = str.indexOf(MAGNET_PAUSE)) > 0) {
//System.out.println("Pause called:"+str);
try { Thread.sleep(250); } catch(Exception e) {};
returnNoContent(socket);
}
} else if (str.indexOf(MAGNETDETAIL) >= 0) {
int loc = 0;
if ((loc = str.indexOf(MAGNETDETAIL)) < 0)
return;
int loc2 = str.lastIndexOf(" HTTP");
String command =
str.substring(loc+MAGNETDETAIL.length(), loc2);
String page=MagnetHTML.buildMagnetDetailPage(command);
HTTPHandler.createPage(socket, page);
} else if (str.indexOf(MAGNET) >= 0) {
// trigger an operation
int loc = str.indexOf(MAGNET);
int loc2 = str.lastIndexOf(" HTTP");
if ( loc < 0 )
return;
String command = str.substring(loc, loc2);
triggerMagnetHandling(socket, command);
}
try { socket.close(); } catch (IOException e) { }
}
private void triggerMagnetHandling(Socket socket, String command) {
// Supress duplicate requests from some browsers
long curTime = (new Date()).getTime();
if ( !(command.equals(_lastRequest) &&
(curTime - _lastRequestTime) < 1500l) ) {
// trigger an operation
ExternalControl.handleMagnetRequest(command);
_lastRequest = command;
_lastRequestTime = curTime;
}
//else System.out.println("Duplicate request:"+command);
returnNoContent(socket);
}
private void returnNoContent(Socket socket) {
try {
BufferedOutputStream out =
new BufferedOutputStream(socket.getOutputStream());
String s = NOCONTENT;
byte[] bytes=s.getBytes();
out.write(bytes);
out.flush();
socket.close();
} catch (IOException e) {
}
}
}