package com.limegroup.gnutella.uploader;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.nio.entity.ConsumingNHttpEntity;
import org.apache.http.nio.protocol.SimpleNHttpRequestHandler;
import org.apache.http.protocol.HttpContext;
import org.limewire.io.GUID;
import org.limewire.io.NetworkUtils;
import org.limewire.util.Base32;
import com.google.inject.Inject;
import com.limegroup.gnutella.MessageRouter;
import com.limegroup.gnutella.Uploader.UploadStatus;
import com.limegroup.gnutella.http.HTTPHeaderName;
import com.limegroup.gnutella.messages.PushRequest;
import com.limegroup.gnutella.messages.PushRequestImpl;
import com.limegroup.gnutella.messages.Message.Network;
/**
* Handles HTTP push requests by proxying them and sending them to the specified
* client.
*/
public class HttpPushRequestHandler extends SimpleNHttpRequestHandler {
private static final Log LOG = LogFactory.getLog(HttpPushRequestHandler.class);
public static final String P_SERVER_ID = "ServerId";
public static final String P_GUID = "guid";
public static final String P_FILE = "file";
public static final String P_TLS = "tls";
private HTTPUploadSessionManager sessionManager;
private MessageRouter messageRouter;
@Inject
HttpPushRequestHandler(HTTPUploadSessionManager sessionManager, MessageRouter messageRouter) {
if (sessionManager == null) {
throw new IllegalArgumentException();
}
if (messageRouter == null) {
throw new IllegalArgumentException();
}
this.sessionManager = sessionManager;
this.messageRouter = messageRouter;
}
public ConsumingNHttpEntity entityRequest(HttpEntityEnclosingRequest request,
HttpContext context) throws HttpException, IOException {
return null;
}
@Override
public void handle(HttpRequest request, HttpResponse response,
HttpContext context) throws HttpException, IOException {
HTTPUploader uploader = null;
HttpPushRequest pushRequest = parsePushRequest(request);
if (pushRequest == null) {
response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
uploader = sessionManager.getOrCreateUploader(request,
context, UploadType.MALFORMED_REQUEST,
"Malformed Request");
uploader.setState(UploadStatus.MALFORMED_REQUEST);
} else {
uploader = sessionManager.getOrCreateUploader(request,
context, UploadType.PUSH_PROXY, pushRequest.clientGUID);
uploader.setState(UploadStatus.PUSH_PROXY);
if (!sendRequest(pushRequest)) {
response.setStatusCode(HttpStatus.SC_GONE);
response.setReasonPhrase("Servent not connected");
} else {
response.setStatusCode(HttpStatus.SC_ACCEPTED);
response.setReasonPhrase("Message sent");
}
}
sessionManager.sendResponse(uploader, response);
}
/**
* Returns the push request from <code>request</code>.
*
* @return null, if the request was not valid
*/
private HttpPushRequest parsePushRequest(HttpRequest request) {
String uri = request.getRequestLine().getUri();
// start after the '?'
int i = uri.indexOf('?');
if (i == -1) {
return null;
}
String queryString = uri.substring(i + 1);
StringTokenizer t = new StringTokenizer(queryString, "=&");
if (t.countTokens() < 2 || t.countTokens() % 2 != 0) {
return null;
}
String clientGUID = null;
int fileIndex = 0;
boolean useTLS = false;
while (t.hasMoreTokens()) {
final String key = t.nextToken();
final String val = t.nextToken();
if (key.equalsIgnoreCase(P_SERVER_ID)) {
if (clientGUID != null) // already have a name?
return null;
// must convert from base32 to base 16.
byte[] base16 = Base32.decode(val);
if (base16.length != 16)
return null;
clientGUID = new GUID(base16).toHexString();
} else if (key.equalsIgnoreCase(P_GUID)) {
if (clientGUID != null || val.length() != 32)
return null;
// already in base16
clientGUID = val;
} else if (key.equalsIgnoreCase(P_FILE)) {
if (fileIndex != 0)
return null;
try {
fileIndex = Integer.parseInt(val);
} catch (NumberFormatException e) {
return null;
}
if (fileIndex < 0)
return null;
} else if (key.equalsIgnoreCase(P_TLS)) {
useTLS = "true".equalsIgnoreCase(val);
}
}
if (clientGUID == null) {
return null;
}
Header header = request.getLastHeader(HTTPHeaderName.NODE
.httpStringValue());
if (header == null) {
LOG.info("Missing X-Node header push proxy request");
return null;
}
InetSocketAddress address = getNodeAddress(header.getValue());
if (address == null) {
LOG.info("Invalid node address for push proxy request: " + header.getValue());
return null;
}
return new HttpPushRequest(clientGUID, fileIndex, address, useTLS);
}
private InetSocketAddress getNodeAddress(String value) {
StringTokenizer t = new StringTokenizer(value, ":");
if (t.countTokens() == 2) {
try {
InetAddress address = InetAddress.getByName(t.nextToken()
.trim());
int port = Integer.parseInt(t.nextToken().trim());
if (NetworkUtils.isValidAddress(address)
&& NetworkUtils.isValidPort(port)) {
return new InetSocketAddress(address, port);
}
} catch (UnknownHostException badHost) {
} catch (NumberFormatException nfe) {
}
}
return null;
}
/**
* Sends <code>request</code> through {@link MessageRouter}.
*
* @return false, if sending failed
*/
private boolean sendRequest(HttpPushRequest request) {
byte[] clientGUID = GUID.fromHexString(request.getClientGUID());
PushRequest push = new PushRequestImpl(GUID.makeGuid(), (byte) 0,
clientGUID, request.getFileIndex(), request.getAddress()
.getAddress().getAddress(), request.getAddress()
.getPort(), Network.TCP, request.isUseTLS());
try {
messageRouter.sendPushRequest(push);
} catch (IOException e) {
LOG.debug("Sending of push proxy request failed", e);
return false;
}
return true;
}
private static class HttpPushRequest {
private String clientGUID;
private int fileIndex;
private InetSocketAddress address;
private boolean useTLS;
public HttpPushRequest(String clientGUID, int fileIndex,
InetSocketAddress address, boolean useTLS) {
this.clientGUID = clientGUID;
this.fileIndex = fileIndex;
this.address = address;
this.useTLS = useTLS;
}
public String getClientGUID() {
return clientGUID;
}
public int getFileIndex() {
return fileIndex;
}
public InetSocketAddress getAddress() {
return address;
}
public boolean isUseTLS() {
return useTLS;
}
}
}