package com.limegroup.gnutella.downloader; import static junit.framework.Assert.assertEquals; import static org.limewire.util.AssertComparisons.assertGreaterThan; import static org.limewire.util.AssertComparisons.assertLessThan; import static org.limewire.util.AssertComparisons.assertLessThanOrEquals; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.limewire.collection.Function; import org.limewire.collection.IntPair; import org.limewire.collection.RoundRobinQueue; import org.limewire.concurrent.ManagedThread; import org.limewire.gnutella.tests.NetworkManagerStub; import org.limewire.io.BandwidthThrottle; import org.limewire.io.GUID; import org.limewire.io.IP; import org.limewire.io.IpPort; import org.limewire.io.NetworkInstanceUtils; import org.limewire.nio.ssl.SSLUtils; import org.limewire.service.ErrorService; import org.limewire.util.DebugRunnable; import org.limewire.util.StringUtils; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.util.Providers; import com.limegroup.gnutella.ApplicationServices; import com.limegroup.gnutella.NetworkManager; import com.limegroup.gnutella.PushEndpoint; import com.limegroup.gnutella.PushEndpointCache; import com.limegroup.gnutella.PushEndpointFactoryImpl; import com.limegroup.gnutella.RemoteFileDesc; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.altlocs.AltLocUtils; import com.limegroup.gnutella.altlocs.AlternateLocation; import com.limegroup.gnutella.altlocs.AlternateLocationCollection; import com.limegroup.gnutella.altlocs.AlternateLocationFactory; import com.limegroup.gnutella.altlocs.AlternateLocationFactoryImpl; import com.limegroup.gnutella.dht.db.SearchListener; import com.limegroup.gnutella.filters.AbstractIPFilter; import com.limegroup.gnutella.filters.IPFilter; import com.limegroup.gnutella.filters.IPList; import com.limegroup.gnutella.http.FeaturesWriter; import com.limegroup.gnutella.http.HTTPHeaderName; import com.limegroup.gnutella.http.HTTPHeaderValue; import com.limegroup.gnutella.http.HTTPHeaderValueCollection; import com.limegroup.gnutella.http.HTTPUtils; import com.limegroup.gnutella.http.HttpTestUtils; import com.limegroup.gnutella.tigertree.HashTreeWriteHandler; import com.limegroup.gnutella.tigertree.HashTreeWriteHandlerFactory; import com.limegroup.gnutella.tigertree.SimpleHashTreeNodeManager; import com.limegroup.gnutella.tigertree.dime.TigerWriteHandlerFactoryImpl; // NOT A SINGLETON!! public class TestUploader { private static final Log LOG = LogFactory.getLog(TestUploader.class); /** My name, for debugging */ private volatile String name; /** Number of bytes uploaded */ private volatile int fullRequestsUploaded; /** The number of connections received */ private int connects=0; /** The maximum number of connect attempts */ private int maxConnects = Integer.MAX_VALUE; /** The last request sent, e.g., "GET /get/0/file.txt HTTP/1.0" */ private String request=null; /** The throttle rate in kilobytes/sec */ private volatile float rate; /**The number of bytes this uploader uploads before dying*/ private volatile int stopAfter; /** This is stopped. */ private boolean stopped; private final Object stoppedLock = new Object(); /** switch to send incorrect bytes to simulate a bad uploader*/ private boolean sendCorrupt; /** the boundary between stop/start to send corrupt bytes */ private float corruptPercentage; private AlternateLocationCollection storedGoodLocs,storedBadLocs; private List<AlternateLocation> incomingGoodAltLocs, incomingBadAltLocs; private URN _sha1; private boolean http11 = true; private ServerSocket server; private Socket socket; private boolean busy = false; private int retryAfter = -1; private int timesBusy = Integer.MAX_VALUE; //Note about queue testing: This is how the queuing simulation works: If //queue is set, the uploader sends the X-Queue header etc in handleRequest //method, and sets values for minPollTime and maxPollTime. In the loop //method, if queue was set, when handleRequest returns, queue is set to //false and handleRequest is called one more time. This time handleRequest //will upload the file normally, but it will also check that the downloader //1. Kept the socket open 2. Responsed within the given time range. //3. completed the download after coming out of the queue. private boolean queue = false; private long minPollTime = -1; private long maxPollTime = -1; public final int MIN_POLL = 45000; public final int MAX_POLL = 120000; private int partial = 0; private Long creationTime = null; private boolean unqueue = true; private volatile int queuePos = 1; private boolean killedByDownloader = false; private int start,stop; /** * The offset for the low chunk. */ private int lowChunkOffset = 0; /** * The offset for the high chunk. */ private int highChunkOffset = 0; /** * The number of requests this uploader has received. */ private int requestsReceived = 0; /** * Whether or not this uploader should respond with HTTP/1.1 */ private boolean respondWithHTTP11 = true; /** * Whether or not we'll include the THEX-Tree header in our response. */ private boolean sendThexTreeHeader = false; /** * Whether or not we'll include the THEX-Tree in our response. */ private boolean sendThexTree = false; /** * Whether or not thex was requested. */ private boolean thexWasRequested = false; /** * Whether or not to send the content length in the response. */ private boolean sendContentLength = true; /** * If we should queue when thex is requested. */ private boolean queueOnThex = false; /** * Whether or not we should use a bad THEX response header. */ private boolean useBadThexResponseHeader = false; /** * whether or not we are interested in receiving push locs */ private boolean interestedInFalts = false; /** * whether we are firewalled */ private boolean isFirewalled = false; /** * whether we stall while writing headers */ private boolean stallHeaders = false; /** * if not -1 X-FWTP is written out with the value */ private int FWTPort = -1; /** * Use this to throttle sending our data */ private BandwidthThrottle throttle; /** * a callback to notify every time we start an http11 request */ private HTTP11Listener _httpListener; /** * The sum of the number of bytes we need to upload across all requests. If * this value is less than totalUploaded and the uploader encountered an * IOException in handle request it means the downloader killed the * connection. */ private int totalAmountToUpload; /** * <tt>IPFilter</tt> for only allowing local connections. */ private static final IPList local = new IPList(); static { local.add("127.0.0.1"); } /** * String to send if we are writing the X-Push-Proxies header */ private String _proxiesString; private final NetworkManager networkManager; /** * If the network manager we got is a stub, keep a ref here * so it can be customized. * INVARIANT: networkManagerStub == networkManager */ private final NetworkManagerStub networkManagerStub; @Inject public TestUploader(Injector injector) { this(injector.getInstance(NetworkManager.class)); } public TestUploader(NetworkManager networkManager) { this.networkManager = networkManager; this.networkManagerStub = networkManager instanceof NetworkManagerStub ? (NetworkManagerStub) networkManager : null; } /** * Starts the TestUploader listening on the given port. Will upload a * special test file to any requesters via HTTP. Non-blocking; starts * another thread to do the listening. */ public void start(String name, final int port, boolean tls) { this.name=name; reset(); try { if(!tls) server = new ServerSocket(); else { SSLContext context = SSLUtils.getTLSContext(); SSLServerSocket sslServer = (SSLServerSocket)context.getServerSocketFactory().createServerSocket(); sslServer.setEnabledCipherSuites(new String[] { "TLS_DH_anon_WITH_AES_128_CBC_SHA" } ); sslServer.setNeedClientAuth(false); sslServer.setWantClientAuth(false); server = sslServer; } server.setReuseAddress(true); server.bind(new InetSocketAddress(port)); } catch (IOException e) { LOG.debug("Couldn't bind socket to port "+port+"\n"); throw new RuntimeException(e); } //spawn loop(); Thread t = new ManagedThread(new DebugRunnable(new Runnable() { @Override public void run() { loop(port); } })); t.setDaemon(true); t.start(); } void start(String name) throws IOException { this.name=name; reset(); LOG.debug("starting to handle request with direct socket given"); Thread t = new ManagedThread(name) { @Override public void run() { synchronized(TestUploader.this) { try{ while(socket==null) { LOG.debug("socket is null"); TestUploader.this.wait(); } } catch(InterruptedException hmm) { throw new RuntimeException(hmm); } } Runnable r = new SocketHandler(socket); r.run(); } }; t.start(); } public synchronized void setSocket(Socket s) { LOG.debug("setting socket"); socket=s; notify(); } public void stopThread() { LOG.debug("stopping thread"); try { if ( server != null ) server.close(); } catch (IOException e) {} } /** * Resets the rate, amount uploaded, stop byte, etc. */ public void reset() { LOG.debug("resetting uploader "+name); storedGoodLocs = null;//new AlternateLocationCollection(); storedBadLocs = null; incomingGoodAltLocs = null;//new AlternateLocationCollection(); fullRequestsUploaded = 0; stopAfter = -1; rate = 10000; stopped = false; sendCorrupt = false; corruptPercentage = 0; busy = false; retryAfter = -1; timesBusy = Integer.MAX_VALUE; queue = false; partial = 0; minPollTime = -1; maxPollTime = -1; unqueue = true; queuePos=1; killedByDownloader = false; totalAmountToUpload = 0; requestsReceived = 0; connects = 0; maxConnects = Integer.MAX_VALUE; lowChunkOffset = 0; highChunkOffset = 0; respondWithHTTP11 = true; sendThexTreeHeader = false; sendThexTree = false; thexWasRequested = false; sendContentLength = true; queueOnThex = false; useBadThexResponseHeader = false; _httpListener = null; incomingBadAltLocs = new ArrayList<AlternateLocation>(); incomingGoodAltLocs = new ArrayList<AlternateLocation>(); FWTPort = -1; } public int fullRequestsUploaded() { return fullRequestsUploaded; } public int getAmountUploaded() { return fullRequestsUploaded+amountThisRequest; } /** * If not -1 port is written out in X-FWTP header */ public void setFWTPort(int port) { FWTPort = port; } /** Sets the upload throttle rate * Note: Even if the rate is set to zero the send method will send atleast * one byte per second, in order to detect socket closes. * @param rate kilobytes/sec. */ public void setRate(float rate) { this.rate=rate; } /** * Determine how many times this test uploader should respond * with a busy status. */ public void setTimesBusy(int nTimes) { this.timesBusy = nTimes; } /** * Determines what should be sent in the Retry-After header */ public void setRetryAfter(int seconds) { this.retryAfter = seconds; } /** * Sets this test uploader to be busy. * Use setTimesBusy to set the number of times this * uploader should respond as busy. * By default it will respond Integer.MAX_VALUE times. */ public void setBusy(boolean busy) { this.busy = busy; } public void setQueue(boolean q) { this.queue = q; } public void setCreationTime(Long l) { this.creationTime = l; } public void setPartial(boolean part) { if(part) this.partial = 1; else this.partial = 0;//reset } /** Sets whether this should send bad data. */ public void setCorruption(boolean corrupt) { this.sendCorrupt = corrupt; } /** * Sets the corrupt percentage of the corrupted bytes. */ public void setCorruptPercentage(float num) { this.corruptPercentage = num; } /** * Sets the number of bytes that this should send. This lets the user * simulate a broken upload. * @param n the number of bytes to send, or -1 if no limit */ public void stopAfter(int n) { this.stopAfter = n; } /** * Store the alternate locations that this uploader knows about. * @param alts the alternate locations */ public void setGoodAlternateLocations(AlternateLocationCollection alts) { storedGoodLocs = alts; } public void setBadAlternateLocations(AlternateLocationCollection alts) { storedBadLocs = alts; } /** * Sets the offset for the low chunk. */ public void setLowChunkOffset(int offset) { lowChunkOffset = offset; } /** * Sets the offset for the high chunk. */ public void setHighChunkOffset(int offset) { highChunkOffset = offset; } /** * Sets whether or not this uploader will respond with HTTP/1.1 */ public void setHTTP11(boolean yes) { respondWithHTTP11 = yes; } public void setHTTPListener(HTTP11Listener listener) { _httpListener = listener; } /** * Sets whether or not we'll send the thex tree header in our response. */ public void setSendThexTreeHeader(boolean yes) { sendThexTreeHeader = yes; } /** * Sets whether or not we'll send the thex tree in our response. */ public void setSendThexTree(boolean yes) { sendThexTree = yes; } /** * Determiens whether or not thex was requested this time. */ public boolean thexWasRequested() { return thexWasRequested; } /** * Sets whether or not to send the content length in the response. */ public void setSendContentLength(boolean yes) { sendContentLength = yes; } /** * Sets whether or not to queue on the thex request. */ public void setQueueOnThex(boolean yes) { queueOnThex = yes; } /** * Sets whether or not the uploader should receive falts */ public void setInterestedInFalts(boolean yes) { interestedInFalts=yes; } /** * sets whether the uploader is firewalled, which affects headers written */ public void setFirewalled(boolean yes) { if (networkManagerStub != null) networkManagerStub.setAcceptedIncomingConnection(!yes); isFirewalled=yes; } /** * sets which proxies we should write in the proxies header */ public void setProxiesString(String str) { _proxiesString=str; } /** * Sets whether or not we'll use a bad thex response header. */ public void setUseBadThexResponseHeader(boolean yes ) { useBadThexResponseHeader = yes; } public void setStallHeaders(boolean yes) { stallHeaders = yes; } /** * Get the alternate locations that this uploader has read from headers */ public URN getReportedSHA1() { return _sha1; } /** Returns the number of connections this accepted. */ public int getConnections() { return connects; } /** * Sets the maximum amount of connects allowed. */ public void setMaxConnects(int max) { maxConnects = max; } /** * Returns the number of requests this received. */ public int getRequestsReceived() { return requestsReceived; } /** Returns the last request sent or null if none. * @return a request like "GET /get/0/file.txt HTTP/1.1" */ public String getRequest() { return request; } /** * Repeatedly accepts connections and handles them. */ private void loop(int port) { Socket socket = null; while(true) { try { socket = server.accept(); connects++; if(connects > maxConnects) { LOG.debug("over-connected"); socket.close(); continue; } // make sure it's from us InetAddress address = socket.getInetAddress(); if (isBannedIP(address.getHostAddress())) { LOG.debug("Banned address -- closing"); socket.close(); continue; } LOG.debug("Uploader accepted connection"); //spawn thread to handle request final Socket mySocket = socket; Thread runner=new ManagedThread(new SocketHandler(mySocket),name); runner.start(); } catch (IOException e) { LOG.debug("exception in accept", e); try { server.close(); } catch (IOException ignore) { } return; //server socket closed. } //handling next request } } /** * Returns whether <tt>ip</tt> is a banned address. * @param ip an address in resolved dotted-quad format, e.g., 18.239.0.144 * @return true iff ip is a banned address. */ public boolean isBannedIP(String ip) { return !local.contains(new IP(ip)); } private void handleRequest(Socket socket) throws IOException { LOG.debug("Handling a request on socket: " + socket); //Find the region of the file to upload. If a Range request is present, //use that. Otherwise, send the whole file. Skip all other headers. //TODO2: Later we should also check the validity of the requests BufferedReader input = new BufferedReader( new InputStreamReader(socket.getInputStream())); OutputStream output = new BufferedOutputStream(socket.getOutputStream()); if(rate > 0) throttle = new BandwidthThrottle(rate*1024); else throttle = new BandwidthThrottle(Float.MAX_VALUE); start = 0; stop = TestFile.length(); boolean firstLine=true; boolean thexReq = false; while (true) { String line=input.readLine(); LOG.debug("read "+line); if (firstLine) { if(line != null && !line.equals("")) { requestsReceived++; } request=line; firstLine=false; } if (line==null) throw new IOException("Unexpected close"); if (line.equals("")) break; if(HTTPHeaderName.GNUTELLA_CONTENT_URN.matchesStartOfString(line)) { _sha1 = readContentUrn(line); } if(HTTPHeaderName.NALTS.matchesStartOfString(line) || HTTPHeaderName.BFALT_LOCATION.matchesStartOfString(line)) readAlternateLocations(line,false); if(HTTPHeaderName.ALT_LOCATION.matchesStartOfString(line) || HTTPHeaderName.FALT_LOCATION.matchesStartOfString(line)) readAlternateLocations(line,true); int i=line.indexOf("Range:"); assertLessThanOrEquals("Range should be at the beginning or not at all", 0, i); if (i==0) { IntPair p = null; try { p=parseRange(line); } catch (Exception e) { throw new RuntimeException("Bad Range request: \""+line+"\"", e); } start = Math.max(0, p.a + lowChunkOffset); stop = Math.min(TestFile.length(), Math.max(1, p.b + highChunkOffset)); } i = line.indexOf("GET"); if(i==0) { http11 = line.indexOf("1.1") > 0; thexReq = line.indexOf(TestFile.tree().getThexURI()) > 0; thexWasRequested |= thexReq; } } if(thexReq && queueOnThex) { queueOnThex = false; queue = true; sendThexTree = true; } if(thexReq && useBadThexResponseHeader) { sendThexTree = true; } if(thexReq && sendThexTree && !queue) { if (_httpListener != null) _httpListener.thexRequestStarted(); LOG.debug("sending thex tree."); sendThexTree = false; sendThexTree(output); output.flush(); LOG.debug("done sending thex tree."); } else { //Send the data. if (_httpListener != null) _httpListener.requestStarted(this); send(output, start, stop); } if (_httpListener != null) { if (thexReq) _httpListener.thexRequestHandled(); else _httpListener.requestHandled(); } } private void sendThexTree(OutputStream out) throws IOException { HashTreeWriteHandlerFactory tigerWriteHandlerFactory = new TigerWriteHandlerFactoryImpl( new SimpleHashTreeNodeManager()); HashTreeWriteHandler tigerWriteHandler = tigerWriteHandlerFactory .createTigerWriteHandler(TestFile.tree()); if(!useBadThexResponseHeader) { String str = "HTTP/1.1 200 OK\r\n" + "ugly-header: ugly-value\r\n" + "hot diggity doo\r\n" + "Content-Length: " + tigerWriteHandler.getOutputLength() + "\r\n" + "\r\n"; out.write(StringUtils.toAsciiBytes(str)); tigerWriteHandler.write(out); } else { String body = "You have failed miserably in your attempts."; String str = "HTTP/1.1 9000 Failed Miserably\r\n" + "Content-Length: " + body.length() + "\r\n" + "\r\n"; out.write(StringUtils.toAsciiBytes(str)); out.write(StringUtils.toAsciiBytes(body)); } } private void send(OutputStream out, int start, int stop) throws IOException { LOG.debug("starting to send data "+start+"-"+stop); totalAmountToUpload += stop - start; //Write header, stolen from NormalUploadState.writeHeader() long t0 = System.currentTimeMillis(); if(minPollTime > 0) assertGreaterThan("queued downloader responded too quick by "+ (minPollTime-t0)+" mS", minPollTime, t0); if(maxPollTime > 0) assertLessThan("queued downloader responded too late, by "+ (t0-maxPollTime) +" mS", maxPollTime, t0); String httpValue = respondWithHTTP11 ? "HTTP/1.1" : "HTTP/1.0"; String str = httpValue + " " + (busy || queue || partial==1 ? "503 Service Unavailable\r\n" : "200 OK \r\n"); out.write(StringUtils.toAsciiBytes(str)); if(busy && retryAfter != -1) { str = "Retry-After: " + retryAfter + "\r\n"; out.write(StringUtils.toAsciiBytes(str)); } if(queue) { LOG.debug("Upload Queued"); str = "X-Queue: position="+queuePos+ ", pollMin=" + MIN_POLL/1000 + ", pollMax=" + MAX_POLL/1000 + "\r\n"; out.write(StringUtils.toAsciiBytes(str)); str = "\r\n"; out.write(StringUtils.toAsciiBytes(str)); out.flush();//don't close socket long t = System.currentTimeMillis(); minPollTime = t+MIN_POLL; maxPollTime = t+MAX_POLL; return; } if(partial > 0) { switch(partial) { case 1: str="X-Available-Ranges: ByTes 50000-400000, 500000-600000,800000-900000\r\n"; break; case 2: str="X-Available-Ranges: bytes 50000-900000\r\n"; break; default: str="X-Available-Ranges: bytes 50000-150000\r\n"; } out.write(StringUtils.toAsciiBytes(str)); out.flush(); partial++; if(partial==2) {//was 1 until last statement str="\r\n"; out.write(StringUtils.toAsciiBytes(str)); out.flush(); return; } } if(sendContentLength) { str = "Content-Length:"+ (stop - start) + "\r\n"; out.write(StringUtils.toAsciiBytes(str)); } if (start != 0 || (stop - start != TestFile.length())) { //Note that HTTP stop values are INCLUSIVE. Our internal values //are EXCLUSIVE. Hence the -1. str = "Content-range: bytes " + start + "-" + (stop-1) + "/" + TestFile.length() + "\r\n"; out.write(StringUtils.toAsciiBytes(str)); } if(storedGoodLocs != null && storedGoodLocs.hasAlternateLocations()) { LOG.debug("Writing alternate location header:\n"+storedGoodLocs+"\n"); TestUploader.writeHeader(HTTPHeaderName.ALT_LOCATION, storedGoodLocs, out); } else { LOG.debug("Did not write alt locs:\n"+storedGoodLocs+"\n"); } if(storedBadLocs != null && storedBadLocs.hasAlternateLocations()) { LOG.debug("Writing alternate location header:\n"+storedBadLocs+"\n"); TestUploader.writeHeader(HTTPHeaderName.NALTS, storedBadLocs, out); } else { LOG.debug("Did not write alt locs:\n"+storedBadLocs+"\n"); } if(creationTime != null) { LOG.debug("Writing out Creation Time."); TestUploader.writeHeader(HTTPHeaderName.CREATION_TIME, ""+creationTime, out); } if(sendThexTreeHeader) { TestUploader.writeHeader(HTTPHeaderName.THEX_URI, TestFile.tree(), out); } if(interestedInFalts) { FeaturesWriter featuresWriter = new FeaturesWriter(networkManager); Set<HTTPHeaderValue> features = featuresWriter.getFeaturesValue(); // Write X-Features header. if (features.size() > 0) { TestUploader.writeHeader(HTTPHeaderName.FEATURES, new HTTPHeaderValueCollection( features), out); } } if (isFirewalled && _proxiesString!=null) { TestUploader.writeHeader(HTTPHeaderName.PROXIES,_proxiesString,out); if (FWTPort != -1) { TestUploader.writeHeader(HTTPHeaderName.FWTPORT, FWTPort, out); } } out.flush(); if (stallHeaders) { LOG.debug("stalling as requested"); try { Thread.sleep(100000000); } catch (InterruptedException end) { return; } } str = "\r\n"; out.write(StringUtils.toAsciiBytes(str)); out.flush(); if (busy) { //turn busy off for the next time if necessary if( connects >= timesBusy ) busy = false; out.close(); return; } int length = stop-start; int okLength = length - (int)(length * corruptPercentage); amountThisRequest = 0; boolean sentCorrupt = false; for (int i=start; i<stop; ) { //1 second write cycle if (stopAfter > -1){ assertLessThanOrEquals( "total "+fullRequestsUploaded+" this "+amountThisRequest+" stop "+stopAfter, stopAfter, fullRequestsUploaded + amountThisRequest); if (fullRequestsUploaded + amountThisRequest == stopAfter) { stopped=true; synchronized (stoppedLock) { stoppedLock.notifyAll(); } out.flush(); LOG.debug(name+" stopped at "+(fullRequestsUploaded + amountThisRequest)); throw new IOException(); } } throttle.request(1); if(sendCorrupt && (i-start) >= okLength) { sentCorrupt = true; out.write(TestFile.getByte(i)+(byte)1); } else out.write(TestFile.getByte(i)); amountThisRequest++; i++; } if (sentCorrupt) LOG.debug("corrupt data sent"); out.flush(); // only if the flush didn't throw do we add to fullRequestsUploaded fullRequestsUploaded += amountThisRequest; amountThisRequest = 0; } /** * Returns start (inclusive) and stop range (exclusive) from the given Range * header. In other words, "Range; 2-5" will return [2, 6). Extracted from * HTTPUploader. * * @exception IndexOutOfBoundsException parse error * @exception NumberFormatException parse error * @exception IOException socket closed */ private IntPair parseRange(String str) throws IndexOutOfBoundsException, NumberFormatException, IOException { int start=0; int stop=TestFile.length(); //Set 'sub' to the value after the "bytes=" or "bytes ". Note //that we don't validate the data between "Range:" and the //bytes. String sub; String second; try { int i=str.indexOf("bytes"); //TODO: use constant if (i<0) throw new IOException(); i+=6; //TODO: use constant sub = str.substring(i); } catch (IndexOutOfBoundsException e) { throw new IOException(); } // remove the white space sub = sub.trim(); char c; // get the first character try { c = sub.charAt(0); } catch (IndexOutOfBoundsException e) { throw new IOException(); } // - n if (c == '-') { // String second; try { second = sub.substring(1); } catch (IndexOutOfBoundsException e) { throw new IOException(); } second = second.trim(); try { //A range request for "-3" means return the last 3 bytes //of the file. (LW used to incorrectly return bytes //0-3.) start = Math.max(0, TestFile.length()-Integer.parseInt(second)); } catch (NumberFormatException e) { throw new IOException(); } } else { // m - n or 0 - int dash = sub.indexOf("-"); String first; try { first = sub.substring(0, dash); } catch (IndexOutOfBoundsException e) { throw new IOException(); } first = first.trim(); try { start = java.lang.Integer.parseInt(first); } catch (NumberFormatException e) { throw new IOException(); } try { second = sub.substring(dash+1); } catch (IndexOutOfBoundsException e) { throw new IOException(); } second = second.trim(); if (!second.equals("")) try { stop = java.lang.Integer.parseInt(second)+1; //sic } catch (NumberFormatException e) { throw new IOException(); } } //check that the downloader made a correct range request if we are //partial and this is the second iteration. if(partial==3) {//check this before checking partial2 -- we set it there assertEquals("downloader picked incorrect start range in iteration", 150010, start); assertEquals("downloader pick incorrect stop range in iteration", 250020, stop); } if(partial==2) { assertEquals("invalid start range", 50000, start); assertEquals("invalid stop range", 150010, stop); } return new IntPair(start, stop); } /** * Reads alternate location header. The header can contain only one * alternate location, or it can contain many in the same header. * This method adds them all to the <tt>FileDesc</tt> for this * uploader. This will not allow more than 20 alternate locations * for a single file. * * @param altHeader the full alternate locations header * @param alc the <tt>AlternateLocationCollector</tt> that read alternate * locations should be added to */ private void readAlternateLocations (String altHeader, final boolean good) { String alternateLocations=HttpTestUtils.extractHeaderValue(altHeader); // this very purposely uses TestAlternateLocationFactory, so that it doesn't retain all // the state of the rest of the code AltLocUtils.parseAlternateLocations(_sha1, alternateLocations, true, new TestAlternateLocationFactory(), new Function<AlternateLocation, Void>() { public Void apply(AlternateLocation location) { if (good) incomingGoodAltLocs.add(location); else incomingBadAltLocs.add(location); return null; } }, true); } /** * This method parses the "X-Gnutella-Content-URN" header, as specified * in HUGE v0.93. This assigns the requested urn value for this * upload, which otherwise remains null. * * @param contentUrnStr the string containing the header * @return a new <tt>URN</tt> instance for the request line, or * <tt>null</tt> if there was any problem creating it */ private static URN readContentUrn(final String contentUrnStr) { String urnStr = HttpTestUtils.extractHeaderValue(contentUrnStr); // return null if the header value could not be extracted if(urnStr == null) return null; try { return URN.createSHA1Urn(urnStr); } catch(IOException e) { // this will be thrown if the URN string was invalid for any // reason -- just return null return null; } } public void waitForUploaderToStop() { synchronized (stoppedLock) { while (!stopped) { try { stoppedLock.wait(); } catch (InterruptedException ie) { } } } } private static final RoundRobinQueue rr = new RoundRobinQueue(); private int amountThisRequest; private class SocketHandler implements Runnable { private final Socket mySocket; public SocketHandler(Socket s) { mySocket=s; } @SuppressWarnings("unchecked") public void run() { LOG.debug(name+" starting to upload.. "); try { while(http11 && !stopped) { handleRequest(mySocket); if (queue) { mySocket.setSoTimeout(MAX_POLL); if(unqueue) // second time give slot queue = false; handleRequest(mySocket); } mySocket.setSoTimeout(8000); } } catch (IOException e) { if(fullRequestsUploaded < totalAmountToUpload) killedByDownloader = true; LOG.debug("Exception in uploader (" + name + ")", e); } catch(Throwable t) { ErrorService.error(t); } finally { synchronized(rr) { rr.remove(this); if (rr.size() > 0){ Object next = rr.next(); synchronized(next){ next.notify(); } } } try { mySocket.close(); } catch (IOException e) { return; } }//end of finally } } public void setUnqueue(boolean unqueue) { this.unqueue = unqueue; } public boolean getKilledByDownloader() { return killedByDownloader; } public List<AlternateLocation> getIncomingGoodAltLocs() { return incomingGoodAltLocs; } public List<AlternateLocation> getIncomingBadAltLocs() { return incomingBadAltLocs; } public void setQueuePos(int queuePos) { this.queuePos = queuePos; } /** * Create a single http header String with the specified header name * and the specified header value. * * @param name the <tt>HTTPHeaderName</tt> instance containing the * header name * @param valueStr the value of the header, generally the httpStringValue * or a HttpHeaderValue, or just a String. */ public static String createHeader(HTTPHeaderName name, String valueStr) { return HTTPUtils.createHeader(name.httpStringValue(), valueStr); } /** * Utility method for writing a header with an integer value. This removes * the burden to the caller of converting integer HTTP values to strings. * * @param name the <tt>HTTPHeaderName</tt> of the header to write * @param value the int value of the header * @param stream the <tt>OutputStream</tt> instance to write the header to * @throws IOException if an IO error occurs during the write */ public static void writeHeader(HTTPHeaderName name, int value, OutputStream stream) throws IOException { writeHeader(name, String.valueOf(value), stream); } /** * Writes an single http header to the specified * <tt>OutputStream</tt> instance, with the specified header name * and the specified header value. * * @param name the <tt>HTTPHeaderName</tt> instance containing the * header name to write to the stream * @param name the <tt>HTTPHeaderValue</tt> instance containing the * header value to write to the stream * @param os the <tt>OutputStream</tt> instance to write to */ public static void writeHeader(HTTPHeaderName name, HTTPHeaderValue value, OutputStream os) throws IOException { if(name == null) { throw new NullPointerException("null name in writing http header"); } else if(value == null) { throw new NullPointerException("null value in writing http header: "+ name); } else if(os == null) { throw new NullPointerException("null os in writing http header: "+ name); } String header = TestUploader.createHeader(name, value.httpStringValue()); os.write(StringUtils.toAsciiBytes(header)); } /** * Writes an single http header to the specified * <tt>OutputStream</tt> instance, with the specified header name * and the specified header value. * * @param name the <tt>HTTPHeaderName</tt> instance containing the * header name to write to the stream * @param value the <tt>String</tt> instance containing the * header value to write to the stream * @param os the <tt>OutputStream</tt> instance to write to */ public static void writeHeader(HTTPHeaderName name, String value, OutputStream os) throws IOException { if(name == null) { throw new NullPointerException("null name in writing http header"); } else if(value == null) { throw new NullPointerException("null value in writing http header: "+ name); } else if(os == null) { throw new NullPointerException("null os in writing http header: "+ name); } String header = TestUploader.createHeader(name, value); os.write(StringUtils.toAsciiBytes(header)); } // a factory to create alternate locations w/o sharing any state with the rest of the program. private static class TestAlternateLocationFactory implements AlternateLocationFactory { private final AlternateLocationFactory delegate; public TestAlternateLocationFactory() { this.delegate = new AlternateLocationFactoryImpl( null, // network manager new PushEndpointFactoryImpl( //push endpoint factory Providers.of((PushEndpointCache)new PECache()), null, new NIUtils(), Providers.of((IPFilter)new AbstractIPFilter() { @Override protected boolean allowImpl(IP ip) { return true; } @Override public boolean hasBlacklistedHosts() { return false; } @Override public void refreshHosts(LoadCallback callback) { } @Override public void refreshHosts() { } })), new AppServices(), // application services null, // connection services new NIUtils(), // network instance utils null); // ipportforself } @Override public AlternateLocation create(URN urn) { throw new IllegalStateException(); } @Override public AlternateLocation create(RemoteFileDesc rfd) throws IOException { throw new IllegalStateException(); } @Override public AlternateLocation create(String location, URN urn, boolean tlsCapable) throws IOException { return delegate.create(location, urn, tlsCapable); } @Override public AlternateLocation create(String location, URN urn) throws IOException { throw new IllegalStateException(); } @Override public AlternateLocation createDirectAltLoc(IpPort ipp, URN urn) throws IOException { throw new IllegalStateException(); } @Override public AlternateLocation createDirectDHTAltLoc(IpPort ipp, URN urn, long fileSize, byte[] ttroot) throws IOException { throw new IllegalStateException(); } @Override public AlternateLocation createPushAltLoc(PushEndpoint pe, URN urn) { throw new IllegalStateException(); } private static class NIUtils implements NetworkInstanceUtils { @Override public boolean isMe(String host, int port) { throw new IllegalStateException(); } @Override public boolean isMe(byte[] address, int port) { throw new IllegalStateException(); } @Override public boolean isMe(IpPort me) { return false; } @Override public boolean isPrivate() { throw new IllegalStateException(); } @Override public boolean isPrivateAddress(InetAddress address) { return false; } @Override public boolean isPrivateAddress(byte[] address) { throw new IllegalStateException(); } @Override public boolean isPrivateAddress(String address) { throw new IllegalStateException(); } @Override public boolean isPrivateAddress(SocketAddress address) { throw new IllegalStateException(); } @Override public boolean isValidExternalIpPort(IpPort addr) { return true; } @Override public boolean isVeryCloseIP(byte[] addr0, byte[] addr1) { throw new IllegalStateException(); } @Override public boolean isVeryCloseIP(InetAddress addr) { throw new IllegalStateException(); } @Override public boolean isVeryCloseIP(byte[] addr) { throw new IllegalStateException(); } } private static class AppServices implements ApplicationServices { @Override public byte[] getMyBTGUID() { throw new IllegalStateException(); } @Override public byte[] getMyGUID() { return new byte[16]; } @Override public void setFullPower(boolean newValue) { throw new IllegalStateException(); } } private static class PECache implements PushEndpointCache { @Override public void clear() { throw new IllegalStateException(); } @Override public PushEndpoint getCached(GUID guid) { return null; } @Override public void overwriteProxies(byte[] guid, String httpString) { throw new IllegalStateException(); } @Override public void overwriteProxies(byte[] guid, Set<? extends IpPort> newSet) { throw new IllegalStateException(); } @Override public void removePushProxy(byte[] bytes, IpPort pushProxy) { throw new IllegalStateException(); } @Override public void setAddr(byte[] guid, IpPort addr) { throw new IllegalStateException(); } @Override public void setFWTVersionSupported(byte[] guid, int version) { throw new IllegalStateException(); } @Override public GUID updateProxiesFor(GUID guid, PushEndpoint pushEndpoint, boolean valid) { throw new IllegalStateException(); } @Override public void findPushEndpoint(GUID guid, SearchListener<PushEndpoint> listener) { throw new IllegalStateException(); } @Override public PushEndpoint getPushEndpoint(GUID guid) { throw new IllegalStateException(); } } } }