package com.limegroup.gnutella;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.StringTokenizer;
import junit.framework.Test;
import com.limegroup.gnutella.http.HTTPHeaderName;
import com.limegroup.gnutella.http.HTTPRequestMethod;
import com.limegroup.gnutella.http.HTTPUtils;
import com.limegroup.gnutella.settings.SharingSettings;
import com.limegroup.gnutella.settings.UploadSettings;
import com.limegroup.gnutella.stubs.ActivityCallbackStub;
import com.limegroup.gnutella.util.BaseTestCase;
import com.limegroup.gnutella.util.CommonUtils;
/**
* This class tests HTTP requests involving URNs, as specified in HUGE v094,
* utilizing the X-Gnutella-Content-URN header and the
* X-Gnutella-Alternate-Location header.
*/
public final class UrnHttpRequestTest extends BaseTestCase {
private static RouterService ROUTER_SERVICE;
private static final String STATUS_503 = "HTTP/1.1 503 Service Unavailable";
private static final String STATUS_404 = "HTTP/1.1 404 Not Found";
/**
* Constructs a new test instance.
*/
public UrnHttpRequestTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(UrnHttpRequestTest.class);
}
public static void globalSetUp() {
ROUTER_SERVICE = new RouterService(new ActivityCallbackStub());
}
/**
* Runs this test individually.
*/
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
protected void setUp() throws Exception {
if(RouterService.isStarted()) return;
final File TEMP_DIR = new File("temp");
TEMP_DIR.mkdirs();
TEMP_DIR.deleteOnExit();
setSharedDirectories(new File[] {TEMP_DIR});
SharingSettings.EXTENSIONS_TO_SHARE.setValue("tmp");
String dirString = "com/limegroup/gnutella";
File testDir = CommonUtils.getResourceFile(dirString);
assertTrue("could not find the images directory", testDir.isDirectory());
File[] files = testDir.listFiles();
if ( files != null ) {
for(int i=0; i<files.length; i++) {
if(!files[i].isFile()) continue;
CommonUtils.copyResourceFile(dirString+"/"+files[i].getName(),
new File(TEMP_DIR, files[i].getName() + ".tmp"));
}
}
ROUTER_SERVICE.start();
try {
// sleep to let the file manager initialize
Thread.sleep(4000);
} catch(InterruptedException e) {
fail("thread should not have been interrupted", e);
}
assertGreaterThan("FileManager should have loaded files",
4, RouterService.getFileManager().getNumFiles());
}
/**
* Tests requests that follow the traditional "get" syntax to make sure that
* the X-Gnutella-Content-URN header is always returned.
*/
public void testLimitReachedRequests() throws Exception {
int maxUploads = UploadSettings.HARD_MAX_UPLOADS.getValue();
UploadSettings.HARD_MAX_UPLOADS.setValue(0);
for(int i=0; i<RouterService.getFileManager().getNumFiles(); i++) {
FileDesc fd = RouterService.getFileManager().get(i);
String request = "/get/"+fd.getIndex()+"/"+fd.getFileName()+" HTTP/1.1\r\n"+
HTTPHeaderName.GNUTELLA_CONTENT_URN.httpStringValue()+": "+
fd.getSHA1Urn()+"\r\n\r\n";
sendRequestThatShouldFail(HTTPRequestMethod.GET, request, STATUS_503);
//sendRequestThatShouldFail(HTTPRequestMethod.HEAD, request, fd, STATUS_503);
}
UploadSettings.HARD_MAX_UPLOADS.setValue(maxUploads);
}
/**
* Test requests by URN.
*/
public void testHttpUrnRequest() throws Exception {
for(int i=0; i<RouterService.getFileManager().getNumFiles(); i++) {
FileDesc fd = RouterService.getFileManager().get(i);
String request = "/uri-res/N2R?"+fd.getSHA1Urn().httpStringValue()+
" HTTP/1.1\r\n\r\n";
sendRequestThatShouldSucceed(HTTPRequestMethod.GET, request, fd);
sendRequestThatShouldSucceed(HTTPRequestMethod.HEAD, request, fd);
}
}
/**
* Test requests by URN that came from LimeWire 2.8.6.
* /get/0//uri-res/N2R?urn:sha1:AZUCWY54D63______PHN7VSVTKZA3YYT HTTP/1.1
*/
public void testMalformedHttpUrnRequest() throws Exception {
for(int i=0; i<RouterService.getFileManager().getNumFiles(); i++) {
FileDesc fd = RouterService.getFileManager().get(i);
String request = "/get/0//uri-res/N2R?"+
fd.getSHA1Urn().httpStringValue()+" HTTP/1.1\r\n\r\n";
sendRequestThatShouldFail(HTTPRequestMethod.GET,
request, STATUS_404);
sendRequestThatShouldFail(HTTPRequestMethod.HEAD,
request, STATUS_404);
}
}
/**
* Tests requests that follow the traditional "get" syntax to make sure that
* the X-Gnutella-Content-URN header is always returned.
*/
public void testTraditionalGetForReturnedUrn() throws Exception {
for(int i=0; i<RouterService.getFileManager().getNumFiles(); i++) {
FileDesc fd = RouterService.getFileManager().get(i);
String request =
"/get/"+fd.getIndex()+"/"+fd.getFileName()+" HTTP/1.1\r\n"+
HTTPHeaderName.GNUTELLA_CONTENT_URN.httpStringValue()+": "+
fd.getSHA1Urn()+"\r\n\r\n";
sendRequestThatShouldSucceed(HTTPRequestMethod.GET, request, fd);
sendRequestThatShouldSucceed(HTTPRequestMethod.HEAD, request, fd);
}
}
/**
* Tests requests that follow the traditional "get" syntax but that also
* include the X-Gnutella-Content-URN header. In these requests, both the
* URN and the file name and index are correct, so a valid result is
* expected.
*/
public void testTraditionalGetWithContentUrn() throws Exception {
for(int i=0; i<RouterService.getFileManager().getNumFiles(); i++) {
FileDesc fd = RouterService.getFileManager().get(i);
sendRequestThatShouldSucceed(HTTPRequestMethod.GET,
"/get/"+fd.getIndex()+"/"+
fd.getFileName()+
" HTTP/1.1\r\n\r\n", fd);
sendRequestThatShouldSucceed(HTTPRequestMethod.HEAD,
"/get/"+fd.getIndex()+"/"+
fd.getFileName()+
" HTTP/1.1\r\n\r\n", fd);
}
}
/**
* Tests get requests that follow the traditional Gnutella get format and
* that include an invalid content URN header -- these should fail with
* error code 404.
*/
public void testTraditionalGetWithInvalidContentUrn() throws Exception {
for(int i=0; i<RouterService.getFileManager().getNumFiles(); i++) {
FileDesc fd = RouterService.getFileManager().get(i);
String request =
"/get/"+fd.getIndex()+"/"+fd.getFileName()+" HTTP/1.1\r\n"+
HTTPHeaderName.GNUTELLA_CONTENT_URN.httpStringValue()+": "+
"urn:sha1:PLSTHIPQGSSZTS5FJUPAKUZWUGYQYPFB"+"\r\n\r\n";
sendRequestThatShouldFail(HTTPRequestMethod.GET, request, STATUS_404);
sendRequestThatShouldFail(HTTPRequestMethod.HEAD, request, STATUS_404);
}
}
/**
* Tests to make sure that invalid traditional Gnutella get requests with
* matching X-Gnutella-Content-URN header values also fail with 404.
*/
public void testInvalidTraditionalGetWithValidContentUrn() throws Exception {
for(int i=0; i<RouterService.getFileManager().getNumFiles(); i++) {
FileDesc fd = RouterService.getFileManager().get(i);
String request =
"/get/"+fd.getIndex()+"/"+fd.getFileName()+"invalid"+" HTTP/1.1\r\n"+
HTTPHeaderName.GNUTELLA_CONTENT_URN.httpStringValue()+": "+
fd.getSHA1Urn();
sendRequestThatShouldFail(HTTPRequestMethod.GET, request, STATUS_404);
sendRequestThatShouldFail(HTTPRequestMethod.HEAD, request, STATUS_404);
}
}
/**
* Sends an HTTP request that should succeed and send back all of the
* expected headers.
*/
private void sendRequestThatShouldSucceed(HTTPRequestMethod method,
String request,
FileDesc fd) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(request.getBytes());
Socket sock = new TestSocket(new ByteArrayInputStream(baos.toByteArray()));
RouterService.getUploadManager().acceptUpload(method, sock, false);
String reply = sock.getOutputStream().toString();
StringTokenizer st = new StringTokenizer(reply, "\r\n");
boolean contentUrnHeaderPresent = false;
boolean OKPresent = false;
assertTrue("HTTP response headers should be present: "+fd, st.countTokens()>0);
while(st.hasMoreTokens()) {
String curString = st.nextToken();
if(HTTPHeaderName.ALT_LOCATION.matchesStartOfString(curString)) {
continue;
} else if(HTTPHeaderName.GNUTELLA_CONTENT_URN.matchesStartOfString(curString)) {
URN curUrn = null;
try {
String tmpString = HTTPUtils.extractHeaderValue(curString);
curUrn = URN.createSHA1Urn(tmpString);
} catch(IOException e) {
assertTrue("unexpected exception: "+e, false);
}
assertEquals(HTTPHeaderName.GNUTELLA_CONTENT_URN.toString()+
"s should be equal for "+fd,
fd.getSHA1Urn(), curUrn);
contentUrnHeaderPresent = true;
} else if(HTTPHeaderName.CONTENT_RANGE.matchesStartOfString(curString)) {
continue;
} else if(HTTPHeaderName.CONTENT_TYPE.matchesStartOfString(curString)) {
continue;
} else if(HTTPHeaderName.CONTENT_LENGTH.matchesStartOfString(curString)) {
String value = HTTPUtils.extractHeaderValue(curString);
assertEquals("sizes should match for "+fd, (int)fd.getFileSize(),
Integer.parseInt(value));
} else if(HTTPHeaderName.SERVER.matchesStartOfString(curString)) {
continue;
} else if(curString.equals("HTTP/1.1 200 OK")) {
OKPresent = true;
}
}
assertTrue("HTTP/1.1 200 OK should have been returned: "+fd, OKPresent);
assertTrue("content URN header should always be reported:\r\n"+
fd+"\r\n"+
"reply: "+reply,
contentUrnHeaderPresent);
}
/**
* Sends an HTTP request that should fail if everything is working
* correctly.
*/
private void sendRequestThatShouldFail(HTTPRequestMethod method,
String request,
String error) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(request.getBytes());
Socket sock =
new TestSocket(new ByteArrayInputStream(baos.toByteArray()));
RouterService.getUploadManager().acceptUpload(method, sock, false);
String reply = sock.getOutputStream().toString();
StringTokenizer st = new StringTokenizer(reply, "\r\n");
String curString = st.nextToken().trim();
assertEquals("received unexpected HTTP response", error, curString);
}
/**
* Helper class that allows us to control the InputStream returned from
* a dummy socket.
*/
private static class TestSocket extends Socket {
InputStream INPUT_STREAM;
OutputStream OUTPUT_STREAM;
private TestSocket(InputStream is) {
INPUT_STREAM = is;
OUTPUT_STREAM = new ByteArrayOutputStream();
}
public InputStream getInputStream() {
return INPUT_STREAM;
}
public OutputStream getOutputStream() {
return OUTPUT_STREAM;
}
public InetAddress getInetAddress() {
try {
return InetAddress.getLocalHost();
} catch(UnknownHostException e) {
return null;
}
}
}
}