package com.limegroup.gnutella.uploader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.File; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.limegroup.gnutella.Assert; import com.limegroup.gnutella.BandwidthTrackerImpl; import com.limegroup.gnutella.ByteReader; import com.limegroup.gnutella.Constants; import com.limegroup.gnutella.FileDesc; import com.limegroup.gnutella.FileManager; import com.limegroup.gnutella.InsufficientDataException; import com.limegroup.gnutella.RouterService; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.UploadManager; import com.limegroup.gnutella.Uploader; import com.limegroup.gnutella.altlocs.AlternateLocation; import com.limegroup.gnutella.altlocs.AlternateLocationCollection; import com.limegroup.gnutella.altlocs.DirectAltLoc; import com.limegroup.gnutella.altlocs.PushAltLoc; import com.limegroup.gnutella.http.HTTPConstants; import com.limegroup.gnutella.http.HTTPHeaderName; import com.limegroup.gnutella.http.HTTPMessage; import com.limegroup.gnutella.http.HTTPRequestMethod; import com.limegroup.gnutella.http.HTTPUtils; import com.limegroup.gnutella.http.ProblemReadingHeaderException; import com.limegroup.gnutella.settings.SharingSettings; import com.limegroup.gnutella.statistics.BandwidthStat; import com.limegroup.gnutella.udpconnect.UDPConnection; import com.limegroup.gnutella.util.CountingOutputStream; import com.limegroup.gnutella.util.MultiRRIterator; import com.limegroup.gnutella.util.NetworkUtils; import com.limegroup.gnutella.util.StringUtils; /** * Maintains state for an HTTP upload request. This class follows the * State Pattern, delegating its writeResponse method to the appropriate * state. All states except for CONNECTING, COMPLETE, and INTERRUPTED * have an associated state class that implements HTTPMessage. * * Care must be taken to call closeFileStreams whenever a chunk of the * transfer is finished, and to call stop when the entire HTTP/1.1 * session is finished. * * A single HTTPUploader should be reused for multiple chunks of a single * file in an HTTP/1.1 session. However, multiple HTTPUploaders * should be used for multiple files in a single HTTP/1.1 session. */ public final class HTTPUploader implements Uploader { private static final Log LOG = LogFactory.getLog(HTTPUploader.class); /** * The outputstream -- a CountingOutputStream so that we can * keep track of the amount of bytes written. * Currently track is only kept for writing a THEX tree, so that * progress of the tree and bandwidth measurement may be done. */ private CountingOutputStream _ostream; private InputStream _fis; private Socket _socket; private int _totalAmountReadBefore; private int _totalAmountRead; private int _amountRead; // useful so we don't have to do _uploadEnd - _uploadBegin everywhere private int _amountRequested; private int _uploadBegin; private int _uploadEnd; private int _fileSize; private final int _index; private String _userAgent; private boolean _headersParsed; private final String _fileName; private final String _hostName; private int _stateNum = CONNECTING; private int _lastTransferStateNum; private HTTPMessage _state; private boolean _firstReply = true; private boolean _containedRangeRequest = false; private boolean _chatEnabled; private boolean _browseEnabled; private boolean _supportsQueueing = false; private final boolean _hadPassword; /** * True if this is a forcibly shared network file. */ private boolean _isForcedShare = false; /** * whether the remote side indicated they want to receive * firewalled altlocs. */ private boolean _wantsFalts = false; /** * the version of the FWT protocol the remote supports. * Non-firewalled hosts should not send this feature. * INVARIANT: if this is greater than 0, _wantsFalts is set. */ private int _FWTVersion = 0; /** * The Watchdog that will kill this uploader if it takes too long. */ private final StalledUploadWatchdog STALLED_WATCHDOG; /** * The URN specified in the X-Gnutella-Content-URN header, if any. */ private URN _requestedURN; /** * The descriptor for the file we're uploading. */ private FileDesc _fileDesc; /** * Indicates that the client to which we are uploading is capable of * accepting Queryreplies in the response. */ private boolean _clientAcceptsXGnutellaQueryreplies = false; /** * The address as described by the "X-Node" header. */ private InetAddress _nodeAddress = null; /** * The port as described by the "X-Node" header. */ private int _nodePort = -1; /** * The parameters passed to the HTTP Request. */ private Map _parameters = null; private BandwidthTrackerImpl bandwidthTracker=null; /** * The alternate locations that have been written out (as good) locations. */ private Set _writtenLocs; /** * The firewalled alternate locations that have been written out as good locations. */ private Set _writtenPushLocs; /** * The maximum number of alts to write per http transfer. */ private static final int MAX_LOCATIONS = 10; /** * The maximum number of firewalled alts to write per http transfer. */ private static final int MAX_PUSH_LOCATIONS = 5; /** * The <tt>HTTPRequestMethod</tt> to use for the upload. */ private HTTPRequestMethod _method; /** * Consructor for a "normal" non-push upload. Note that this can * be a URN get request. * * @param method the <tt>HTTPRequestMethod</tt> for the request * @param fileName the name of the file * @param socket the <tt>Socket</tt> instance to serve the upload over * @param index the index of the file in the set of shared files * @param params the map of parameters in the http request. * @param dog the StalledUploadWatchdog to use for monitor stalls. * @param hadPassword the get line had a matching password. * to initialize this' bandwidth tracker so we have history */ public HTTPUploader(HTTPRequestMethod method, String fileName, Socket socket, int index, Map params, StalledUploadWatchdog dog, boolean hadPassword) { STALLED_WATCHDOG = dog; _socket = socket; _hostName = _socket.getInetAddress().getHostAddress(); _fileName = fileName; _index = index; _writtenLocs = null; _hadPassword = hadPassword; reinitialize(method, params); } /** * Reinitializes this uploader for a new request method. * * @param method the HTTPRequestMethod to change to. * @param params the parameter list to change to. */ public void reinitialize(HTTPRequestMethod method, Map params) { _method = method; _amountRequested = 0; _uploadBegin = 0; _uploadEnd = 0; _headersParsed = false; _stateNum = CONNECTING; _state = null; _nodePort = 0; _supportsQueueing = false; _requestedURN = null; _clientAcceptsXGnutellaQueryreplies = false; _parameters = params; _totalAmountReadBefore = 0; // If this is the first time we are initializing it, // create a new bandwidth tracker and set a few more variables. if( bandwidthTracker == null ) { bandwidthTracker = new BandwidthTrackerImpl(); _totalAmountRead = 0; _amountRead = 0; } // Otherwise, update the amount read. else { _totalAmountRead += _amountRead; _amountRead = 0; } } /** * Sets the FileDesc for this HTTPUploader to use. * * @param fd the <tt>FileDesc</tt> to use * @throws IOException if the file cannot be read from the disk. */ public void setFileDesc(FileDesc fd) throws IOException { if (LOG.isDebugEnabled()) LOG.debug("trying to set the fd for uploader "+this+ " with "+fd); _fileDesc = fd; _fileSize = (int)fd.getFileSize(); // initializd here because we'll only write locs if a FileDesc exists // only initialize once, so we don't write out previously written locs if( _writtenLocs == null ) _writtenLocs = new HashSet(); if( _writtenPushLocs == null ) _writtenPushLocs = new HashSet(); // if there already was an input stream, close it. if( _fis != null ) { if (LOG.isDebugEnabled()) LOG.debug(this+ " had an existing stream"); try { _fis.close(); } catch(IOException ignored) {} } _fis = _fileDesc.createInputStream(); _isForcedShare = FileManager.isForcedShare(_fileDesc); } /** * Initializes the OutputStream for this HTTPUploader to use. * * @throws IOException if the connection was closed. */ public void initializeStreams() throws IOException { _ostream = new CountingOutputStream(_socket.getOutputStream()); } /** * Starts "uploading" the requested file. The behavior of the upload, * however, depends on the current upload state. If the file was not * found, for example, the upload sends a 404 Not Found message, whereas * in the case of a normal upload, the file is transferred as expected.<p> * * This method also handles storing any newly discovered alternate * locations for this file in the corresponding <tt>FileDesc</tt>. The * new alternate locations are discovered through the requesting client's * HTTP headers.<p> * * Implements the <tt>Uploader</tt> interface. */ public void writeResponse() throws IOException { _ostream.setIsCounting(_stateNum == THEX_REQUEST); try { _method.writeHttpResponse(_state, _ostream); } catch (IOException e) { // Only propogate the exception if they did not read // as much as they wanted to. if ( amountUploaded() < getAmountRequested() ) throw e; } _firstReply = false; } /** * Closes the outputstream, inputstream, and socket for this upload * connection if they are not null. * * Implements the <tt>Uploader</tt> interface. */ public void stop() { try { if (_ostream != null) _ostream.close(); } catch (IOException e) {} try { if (_fis != null) _fis.close(); } catch (IOException e) {} try { if (_socket != null) _socket.close(); } catch (IOException e) {} } /** * Close the file input stream. */ public void closeFileStreams() { try { if( _fis != null ) _fis.close(); } catch(IOException e) {} } /** * This method changes the appropriate state class based on * the integer representing the state. I'm not sure if this * is a good idea, since it results in a case statement, that * i was trying to avoid with. * * Implements the <tt>Uploader</tt> interface. */ public void setState(int state) { _stateNum = state; switch (state) { case UPLOADING: _state = new NormalUploadState(this, STALLED_WATCHDOG); break; case QUEUED: int pos=RouterService.getUploadManager().positionInQueue(_socket); _state = new QueuedUploadState(pos,this); break; case NOT_VALIDATED: _state = new LimitReachedUploadState(this, true); break; case LIMIT_REACHED: _state = new LimitReachedUploadState(this); break; case FREELOADER: _state = new FreeloaderUploadState(); break; case BROWSE_HOST: _state = new BrowseHostUploadState(this); break; case BROWSER_CONTROL: _state = new BrowserControlUploadState(this); break; case PUSH_PROXY: _state = new PushProxyUploadState(this); break; case UPDATE_FILE: _state = new UpdateFileState(this); break; case FILE_NOT_FOUND: _state = new FileNotFoundUploadState(); break; case MALFORMED_REQUEST: _state = new MalformedRequestState(); break; case UNAVAILABLE_RANGE: _state = new UnavailableRangeUploadState(this); break; case BANNED_GREEDY: _state = new BannedUploadState(); break; case THEX_REQUEST: _state = new THEXUploadState(this, STALLED_WATCHDOG); break; case COMPLETE: case INTERRUPTED: case CONNECTING: _state = null; break; default: Assert.that(false, "Invalid state: " + state); } if(_state != null) _lastTransferStateNum = state; } /** * Returns the output stream this uploader is writing to. */ OutputStream getOutputStream() { return _ostream; } /** * Returns the FileInputStream this uploader is reading from. */ InputStream getInputStream() { return _fis; } /** * Returns the InetAddress of the socket we're connected to. */ public InetAddress getConnectedHost() { if(_socket == null) return null; else return _socket.getInetAddress(); } /** * Determines if this is uploading to via a UDP transfer. */ boolean isUDPTransfer() { return (_socket instanceof UDPConnection); } /** * Returns whether or not the current state wants * to close the connection. */ public boolean getCloseConnection() { Assert.that(_state != null); return _state.getCloseConnection(); } /** * Returns the current HTTP Request Method. */ public HTTPRequestMethod getMethod() { return _method; } /** * Returns the queued position if queued. */ public int getQueuePosition() { if( _lastTransferStateNum != QUEUED || _stateNum == INTERRUPTED) return -1; else return RouterService.getUploadManager().positionInQueue(_socket); } /** * Sets the number of bytes that have been uploaded for this upload. * This is expected to restart from 0 for each chunk of an HTTP/1.1 * transfer. * * @param amount the number of bytes that have been uploaded */ void setAmountUploaded(int amount) { int newData = amount - _amountRead; if(newData > 0) { if (isForcedShare()) BandwidthStat.HTTP_BODY_UPSTREAM_INNETWORK_BANDWIDTH.addData(newData); else BandwidthStat.HTTP_BODY_UPSTREAM_BANDWIDTH.addData(newData); } _amountRead = amount; } /** * Returns whether or not this upload is in what is considered an "inactive" * state, such as completed or aborted. * * @return <tt>true</tt> if this upload is in an inactive state, * <tt>false</tt> otherwise */ public boolean isInactive() { switch(_stateNum) { case COMPLETE: case INTERRUPTED: return true; default: return false; } } /** * Returns the parameter list of this HTTPUploader. */ Map getParameters() { return _parameters; } /** The byte offset where we should start the upload. */ public int getUploadBegin() {return _uploadBegin;} /** Returns the offset of the last byte to send <b>PLUS ONE</b>. */ public int getUploadEnd() {return _uploadEnd;} /** * Set new upload begin & end values, modifying the amount requested. */ public void setUploadBeginAndEnd(int begin, int end) { _uploadBegin = begin; _uploadEnd = end; _amountRequested = _uploadEnd - _uploadBegin; } /** * Whether or not the last request to this HTTPUploader contained * a 'Range: ' header, so we can truncate the requested range. */ public boolean containedRangeRequest() { return _containedRangeRequest; } // implements the Uploader interface public long getFileSize() { if(_stateNum == THEX_REQUEST) return _fileDesc.getHashTree().getOutputLength(); else return _fileSize; } // implements the Uploader interface public long getAmountRequested() { if(_stateNum == THEX_REQUEST) return _fileDesc.getHashTree().getOutputLength(); else return _amountRequested; } // implements the Uploader interface public int getIndex() {return _index;} // implements the Uploader interface public String getFileName() {return _fileName;} // implements the Uploader interface public int getState() {return _stateNum;} // implements the Uploader interface public int getLastTransferState() { return _lastTransferStateNum; } // implements the Uploader interface public String getHost() {return _hostName;} // implements the Uploader interface public boolean isChatEnabled() {return _chatEnabled;} // implements the Uploader interface public boolean isBrowseHostEnabled() { return _browseEnabled; } // implements the Uploader interface public int getGnutellaPort() {return _nodePort;} //implements the Uploader interface public String getUserAgent() { return _userAgent; } //implements the Uploader interface public boolean isHeaderParsed() { return _headersParsed; } // is a forced network share? public boolean isForcedShare() { return _isForcedShare; } public boolean supportsQueueing() { return _supportsQueueing && isValidQueueingAgent(); } public boolean isTHEXRequest() { return HTTPConstants.NAME_TO_THEX.equals( _parameters.get(UploadManager.SERVICE_ID)); } /** * Returns an AlternateLocationCollection of alternates that * have not been sent out already. */ Set getNextSetOfAltsToSend() { AlternateLocationCollection coll = RouterService.getAltlocManager().getDirect(_fileDesc.getSHA1Urn()); Set ret = null; long now = System.currentTimeMillis(); synchronized(coll) { Iterator iter = coll.iterator(); for(int i = 0; iter.hasNext() && i < MAX_LOCATIONS;) { AlternateLocation al = (AlternateLocation)iter.next(); if(_writtenLocs.contains(al)) continue; if (al.canBeSent(AlternateLocation.MESH_LEGACY)) { _writtenLocs.add(al); if(ret == null) ret = new HashSet(); ret.add(al); i++; al.send(now,AlternateLocation.MESH_LEGACY); } else if (!al.canBeSentAny()) iter.remove(); } } return ret == null ? Collections.EMPTY_SET : ret; } Set getNextSetOfPushAltsToSend() { if (!_wantsFalts) return Collections.EMPTY_SET; AlternateLocationCollection fwt = RouterService.getAltlocManager().getPush(_fileDesc.getSHA1Urn(), true); AlternateLocationCollection push = _FWTVersion > 0 ? AlternateLocationCollection.EMPTY : RouterService.getAltlocManager().getPush(_fileDesc.getSHA1Urn(), false); Set ret = null; long now = System.currentTimeMillis(); synchronized(push) { synchronized (fwt) { Iterator iter = new MultiRRIterator(new Iterator[]{fwt.iterator(),push.iterator()}); for(int i = 0; iter.hasNext() && i < MAX_PUSH_LOCATIONS;) { PushAltLoc al = (PushAltLoc)iter.next(); if(_writtenPushLocs.contains(al)) continue; // it is possible to end up having a PE with all // proxies removed. In that case we remove it explicitly if(al.getPushAddress().getProxies().isEmpty()) { iter.remove(); continue; } if (al.canBeSent(AlternateLocation.MESH_LEGACY)) { al.send(now,AlternateLocation.MESH_LEGACY); _writtenPushLocs.add(al); if(ret == null) ret = new HashSet(); ret.add(al); i++; } else if (!al.canBeSentAny()) iter.remove(); } } } return ret == null ? Collections.EMPTY_SET : ret; } /** * Blocks certain vendors from being queued, because of buggy * downloading implementations on their side. */ private boolean isValidQueueingAgent() { if( _userAgent == null ) return true; return !_userAgent.startsWith("Morpheus 3.0.2"); } protected boolean isFirstReply () { return _firstReply; } public InetAddress getNodeAddress() {return _nodeAddress; } public int getNodePort() {return _nodePort; } /** * The amount of bytes that this upload has transferred. * For HTTP/1.1 transfers, this number is the amount uploaded * for this specific chunk only. Uses getTotalAmountUploaded * for the entire amount uploaded. * * Implements the Uploader interface. */ public long amountUploaded() { if(_stateNum == THEX_REQUEST) { if(_ostream == null) return 0; else return _ostream.getAmountWritten(); } else return _amountRead; } /** * The total amount of bytes that this upload and all previous * uploaders have transferred on this socket in this file-exchange. * * Implements the Uploader interface. */ public long getTotalAmountUploaded() { if(_stateNum == THEX_REQUEST) { if(_ostream == null) return 0; else return _ostream.getAmountWritten(); } else { if ( _totalAmountReadBefore > 0 ) return _totalAmountReadBefore + _amountRead; else return _totalAmountRead + _amountRead; } } /** * Returns the <tt>FileDesc</tt> instance for this uploader. * * @return the <tt>FileDesc</tt> instance for this uploader, or * <tt>null</tt> if the <tt>FileDesc</tt> could not be assigned * from the shared files */ public FileDesc getFileDesc() {return _fileDesc;} boolean getClientAcceptsXGnutellaQueryreplies() { return _clientAcceptsXGnutellaQueryreplies; } /** * Returns the content URN that the client asked for. */ public URN getRequestedURN() { return _requestedURN; } /** * Reads the HTTP header sent by the requesting client -- note that the * 'GET' portion of the request header has already been read. * * @param iStream the input stream to read the headers from. * @throws <tt>IOException</tt> if the connection closes while reading * @throws <tt>ProblemReadingHeaderException</tt> if any header is invalid */ public void readHeader(InputStream iStream) throws IOException { _uploadBegin = 0; _uploadEnd = 0; _containedRangeRequest = false; _clientAcceptsXGnutellaQueryreplies = false; _totalAmountReadBefore = 0; ByteReader br = new ByteReader(iStream); try { while (true) { // read the line in from the socket. String str = br.readLine(); if ( (str==null) || (str.equals("")) ) break; if (isForcedShare()) BandwidthStat.HTTP_HEADER_DOWNSTREAM_INNETWORK_BANDWIDTH.addData(str.length()); else BandwidthStat. HTTP_HEADER_DOWNSTREAM_BANDWIDTH.addData(str.length()); if (LOG.isDebugEnabled()) LOG.debug("HTTPUploader.readHeader(): str = " + str); // break out of the loop if it is null or blank if ( readChatHeader(str) ) ; else if ( readRangeHeader(str) ) ; else if ( readUserAgentHeader(str) ) ; else if ( readContentURNHeader(str) ) ; else if ( readAltLocationHeader(str) ) ; else if ( readNAltLocationHeader(str)) ; else if ( readFAltLocationHeader(str)) ; else if ( readNFAltLocationHeader(str)); else if ( readAcceptHeader(str) ) ; else if ( readQueueVersion(str) ) ; else if ( readNodeHeader(str) ) ; else if ( readFeatureHeader(str) ) ; else if ( readXDownloadedHeader(str) ) ; } } catch(ProblemReadingHeaderException prhe) { // there was a problem reading the header.. gobble up // the rest of the input and rethrow the exception while(true) { String str = br.readLine(); if( str == null || str.equals("") ) break; } // TODO: record stats for this throw prhe; } finally { // we want to ensure these are always set, regardless // of if an exception was thrown. //if invalid end-index, then upload up to the end of file //or mark as unknown to bet when file size is set. if( _uploadEnd <= 0 || _uploadEnd <= _uploadBegin || _uploadEnd > _fileSize) { _uploadEnd = _fileSize; } _amountRequested = _uploadEnd - _uploadBegin; _headersParsed = true; } } /** * Read the chat portion of a header. * @return true if it had a chat header. */ private boolean readChatHeader(String str) throws IOException { if (str.toUpperCase().indexOf("CHAT:") == -1) return false; String sub; try { sub = str.substring(5); } catch (IndexOutOfBoundsException e) { throw new ProblemReadingHeaderException(); } sub = sub.trim(); int colon = sub.indexOf(":"); String host = sub.substring(0,colon); host = host.trim(); String sport = sub.substring(colon+1); sport = sport.trim(); int port; try { port = java.lang.Integer.parseInt(sport); } catch (NumberFormatException e) { throw new ProblemReadingHeaderException(); } _chatEnabled = true; _browseEnabled = true; _nodePort = port; return true; } /** * Look for X-Downloaded header which represents number * of bytes for this file already downloaded by peer * * @return true if it had a X-Downloaded header */ private boolean readXDownloadedHeader(String str) throws IOException { if ( !HTTPHeaderName.DOWNLOADED.matchesStartOfString(str) ) return false; try { str = HTTPUtils.extractHeaderValue(str); if ( str != null ) { _totalAmountReadBefore = Integer.parseInt(str); } } catch (NumberFormatException e) {} return true; } /** * Look for range header of form, "Range: bytes=", "Range:bytes=", * "Range: bytes ", etc. Note that the "=" is required by HTTP, but * old versions of BearShare do not send it. The value following the * bytes unit will be in the form '-n', 'm-n', or 'm-'. * * @return true if it had a Range header */ private boolean readRangeHeader(String str) throws IOException { // was: != 0, is == -1 (that okay?) if ( StringUtils.indexOfIgnoreCase(str, "Range:") == -1 ) return false; _containedRangeRequest = true; //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 ProblemReadingHeaderException( "bytes not present in range"); i+=6; //TODO: use constant sub = str.substring(i); } catch (IndexOutOfBoundsException e) { throw new ProblemReadingHeaderException(); } // remove the white space sub = sub.trim(); char c; // get the first character try { c = sub.charAt(0); } catch (IndexOutOfBoundsException e) { throw new ProblemReadingHeaderException(); } // - n if (c == '-') { // String second; try { second = sub.substring(1); } catch (IndexOutOfBoundsException e) { throw new ProblemReadingHeaderException(); } 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.) _uploadBegin = Math.max(0, _fileSize-Integer.parseInt(second)); _uploadEnd = _fileSize; } catch (NumberFormatException e) { throw new ProblemReadingHeaderException(); } } else { // m - n or 0 - int dash = sub.indexOf("-"); // If the "-" does not exist, the head is incorrectly formatted. if(dash == -1) { throw new ProblemReadingHeaderException(); } String first = sub.substring(0, dash).trim(); try { _uploadBegin = java.lang.Integer.parseInt(first); } catch (NumberFormatException e) { throw new ProblemReadingHeaderException(); } try { second = sub.substring(dash+1); } catch (IndexOutOfBoundsException e) { throw new ProblemReadingHeaderException(); } second = second.trim(); if (!second.equals("")) try { //HTTP range requests are inclusive. So "1-3" means //bytes 1, 2, and 3. But _uploadEnd is an EXCLUSIVE //index, so increment by 1. _uploadEnd = java.lang.Integer.parseInt(second)+1; } catch (NumberFormatException e) { throw new ProblemReadingHeaderException(); } } return true; } /** * Read the User-Agent field of the header * * @return true if the header had a UserAgent field */ private boolean readUserAgentHeader(String str) throws FreeloaderUploadingException { if ( StringUtils.indexOfIgnoreCase(str, "User-Agent:") == -1 ) return false; // check for netscape, internet explorer, // or other free riding downoaders //Allow them to browse the host though if (SharingSettings.ALLOW_BROWSER.getValue() == false && !(_stateNum == BROWSE_HOST) && !(_stateNum == BROWSER_CONTROL) && !(_stateNum == PUSH_PROXY) && !(_fileName.toUpperCase().startsWith("LIMEWIRE"))) { // if we are not supposed to read from them // throw an exception if( (str.indexOf("Mozilla") != -1) || (str.indexOf("Morpheus") != -1) || (str.indexOf("DA") != -1) || (str.indexOf("Download") != -1) || (str.indexOf("FlashGet") != -1) || (str.indexOf("GetRight") != -1) || (str.indexOf("Go!Zilla") != -1) || (str.indexOf("Inet") != -1) || (str.indexOf("MIIxpc") != -1) || (str.indexOf("MSProxy") != -1) || (str.indexOf("Mass") != -1) || (str.indexOf("MLdonkey") != -1) || (str.indexOf("MyGetRight") != -1) || (str.indexOf("NetAnts") != -1) || (str.indexOf("NetZip") != -1) || (str.indexOf("RealDownload") != -1) || (str.indexOf("SmartDownload") != -1) || (str.indexOf("Teleport") != -1) || (str.indexOf("WebDownloader") != -1) ) { if (!_hadPassword) throw new FreeloaderUploadingException(); } } _userAgent = str.substring(11).trim(); return true; } /** * Read the content URN header * * @return true if the header had a contentURN field */ private boolean readContentURNHeader(String str) { if ( ! HTTPHeaderName.GNUTELLA_CONTENT_URN.matchesStartOfString(str) ) return false; _requestedURN = HTTPUploader.parseContentUrn(str); return true; } /** * Read the Alternate Locations header * * @return true if the header had an alternate locations field */ private boolean readAltLocationHeader(String str) { if ( ! HTTPHeaderName.ALT_LOCATION.matchesStartOfString(str) ) return false; if(_fileDesc != null) parseAlternateLocations(str, true); return true; } private boolean readNAltLocationHeader(String str) { if (!HTTPHeaderName.NALTS.matchesStartOfString(str)) return false; if(_fileDesc != null) parseAlternateLocations(str, false); return true; } private boolean readFAltLocationHeader(String str) { if ( ! HTTPHeaderName.FALT_LOCATION.matchesStartOfString(str) ) return false; //also set the interested flag _wantsFalts=true; if(_fileDesc != null) parseAlternateLocations(str, true); return true; } private boolean readNFAltLocationHeader(String str) { if (!HTTPHeaderName.BFALT_LOCATION.matchesStartOfString(str)) return false; //also set the interested flag _wantsFalts=true; if(_fileDesc != null) parseAlternateLocations(str, false); return true; } /** * Reads the Accept heder * * @return true if the header had an accept field */ private boolean readAcceptHeader(String str) { if ( StringUtils.indexOfIgnoreCase(str, "accept:") == -1 ) return false; if(StringUtils.indexOfIgnoreCase(str, Constants.QUERYREPLY_MIME_TYPE) != -1) _clientAcceptsXGnutellaQueryreplies = true; return true; } private boolean readQueueVersion(String str) { if (! HTTPHeaderName.QUEUE_HEADER.matchesStartOfString(str)) return false; //String s = HTTPUtils.extractHeaderValue(str); //we are not interested in the value at this point, the fact that the //header was sent implies that the uploader supports queueing. _supportsQueueing = true; return true; } /** * Reads the X-Node header * * @return true if the header had an node description value */ private boolean readNodeHeader(final String str) { if ( !HTTPHeaderName.NODE.matchesStartOfString(str) ) return false; StringTokenizer st = new StringTokenizer(HTTPUtils.extractHeaderValue(str), ":"); InetAddress tempAddr = null; int tempPort = -1; // we are expecting 2 tokens - only evalute if you see 2 if (st.countTokens() == 2) { try { tempAddr = InetAddress.getByName(st.nextToken().trim()); tempPort = Integer.parseInt(st.nextToken().trim()); if (NetworkUtils.isValidPort(tempPort)) { // everything checks out.... _nodeAddress = tempAddr; _nodePort = tempPort; } } catch (UnknownHostException badHost) { // crappy host } catch (NumberFormatException nfe) {} // crappy port } return true; } /** * Reads the X-Features header * * @return true if the header had an node description value */ private boolean readFeatureHeader(String str) { if ( !HTTPHeaderName.FEATURES.matchesStartOfString(str) ) return false; str = HTTPUtils.extractHeaderValue(str); if (LOG.isDebugEnabled()) LOG.debug("reading feature header: "+str); StringTokenizer tok = new StringTokenizer(str, ","); while (tok.hasMoreTokens()) { String feature = tok.nextToken(); String protocol = ""; int slash = feature.indexOf("/"); if(slash == -1) { protocol = feature.toLowerCase().trim(); } else { protocol = feature.substring(0, slash).toLowerCase().trim(); } // not interested in the version ... if (protocol.equals(HTTPConstants.CHAT_PROTOCOL)) _chatEnabled = true; else if (protocol.equals(HTTPConstants.BROWSE_PROTOCOL)) _browseEnabled = true; else if (protocol.equals(HTTPConstants.QUEUE_PROTOCOL)) _supportsQueueing = true; else if (protocol.equals(HTTPConstants.PUSH_LOCS)) _wantsFalts=true; else if (protocol.equals(HTTPConstants.FW_TRANSFER)){ // for this header we care about the version try { _FWTVersion = (int)HTTPUtils.parseFeatureToken(feature); _wantsFalts=true; }catch(ProblemReadingHeaderException prhe){ continue; } } } return 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 parseContentUrn(final String contentUrnStr) { String urnStr = HTTPUtils.extractHeaderValue(contentUrnStr); if(urnStr == null) return URN.INVALID; try { return URN.createSHA1Urn(urnStr); } catch(IOException e) { return URN.INVALID; } } /** * Parses the alternate location header. The header can contain only one * alternate location, or it can contain many in the same header. * This method will notify DownloadManager of new alternate locations * if the FileDesc is an IncompleteFileDesc. * * @param altHeader the full alternate locations header * @param alc the <tt>AlternateLocationCollector</tt> that reads alternate * locations should be added to */ private void parseAlternateLocations(final String altHeader, boolean isGood) { final String alternateLocations=HTTPUtils.extractHeaderValue(altHeader); URN sha1 =_fileDesc.getSHA1Urn(); // return if the alternate locations could not be properly extracted if(alternateLocations == null) return; StringTokenizer st = new StringTokenizer(alternateLocations, ","); while(st.hasMoreTokens()) { try { // note that the trim method removes any CRLF character // sequences that may be used if the sender is using // continuations. AlternateLocation al = AlternateLocation.create(st.nextToken().trim(), _fileDesc.getSHA1Urn()); Assert.that(al.getSHA1Urn().equals(sha1)); if (al.isMe()) continue; if(al instanceof PushAltLoc) ((PushAltLoc)al).updateProxies(isGood); // Note: if this thread gets preempted at this point, // the AlternateLocationCollectioin may contain a PE // without any proxies. if(isGood) RouterService.getAltlocManager().add(al, null); else RouterService.getAltlocManager().remove(al, null); if (al instanceof DirectAltLoc) _writtenLocs.add(al); else _writtenPushLocs.add(al); // no problem if we add an existing pushloc } catch(IOException e) { // just return without adding it. continue; } } } public void measureBandwidth() { int written = _totalAmountRead + _amountRead; if(_ostream != null) written += _ostream.getAmountWritten(); bandwidthTracker.measureBandwidth(written); } public float getMeasuredBandwidth() { float retVal = 0; try { retVal = bandwidthTracker.getMeasuredBandwidth(); } catch (InsufficientDataException ide) { retVal = 0; } return retVal; } public float getAverageBandwidth() { return bandwidthTracker.getAverageBandwidth(); } public boolean wantsFAlts() { return _wantsFalts; } public int wantsFWTAlts() { return _FWTVersion; } // overrides Object.toString public String toString() { return "<"+_hostName+":"+ _index +">"; // return "HTTPUploader:\r\n"+ // "File Name: "+_fileName+"\r\n"+ // "Host Name: "+_hostName+"\r\n"+ // "Port: "+_port+"\r\n"+ // "File Size: "+_fileSize+"\r\n"+ // "State: "+_state; } }