package com.limegroup.gnutella.messages; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.xml.sax.SAXException; import com.limegroup.gnutella.ByteOrder; import com.limegroup.gnutella.ErrorService; import com.limegroup.gnutella.FileManager; import com.limegroup.gnutella.GUID; import com.limegroup.gnutella.MediaType; import com.limegroup.gnutella.RouterService; import com.limegroup.gnutella.UDPService; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.UrnType; import com.limegroup.gnutella.guess.QueryKey; import com.limegroup.gnutella.settings.SearchSettings; import com.limegroup.gnutella.statistics.DroppedSentMessageStatHandler; import com.limegroup.gnutella.statistics.ReceivedErrorStat; import com.limegroup.gnutella.statistics.SentMessageStatHandler; import com.limegroup.gnutella.util.CommonUtils; import com.limegroup.gnutella.util.I18NConvert; import com.limegroup.gnutella.util.StringUtils; import com.limegroup.gnutella.xml.LimeXMLDocument; import com.limegroup.gnutella.xml.SchemaNotFoundException; /** * This class creates Gnutella query messages, either from scratch, or * from data read from the network. Queries can contain query strings, * XML query strings, URNs, etc. The minimum speed field is now used * for bit flags to indicate such things as the firewalled status of * the querier.<p> * * This class also has factory constructors for requeries originated * from this LimeWire. These requeries have specially marked GUIDs * that allow us to identify them as requeries. */ public class QueryRequest extends Message implements Serializable{ // these specs may seem backwards, but they are not - ByteOrder.short2leb // puts the low-order byte first, so over the network 0x0080 would look // like 0x8000 public static final int SPECIAL_MINSPEED_MASK = 0x0080; public static final int SPECIAL_FIREWALL_MASK = 0x0040; public static final int SPECIAL_XML_MASK = 0x0020; public static final int SPECIAL_OUTOFBAND_MASK = 0x0004; public static final int SPECIAL_FWTRANS_MASK = 0x0002; /** Mask for audio queries - input 0 | AUDIO_MASK | .... to specify * audio responses. */ public static final int AUDIO_MASK = 0x0004; /** Mask for video queries - input 0 | VIDEO_MASK | .... to specify * video responses. */ public static final int VIDEO_MASK = 0x0008; /** Mask for document queries - input 0 | DOC_MASK | .... to specify * document responses. */ public static final int DOC_MASK = 0x0010; /** Mask for image queries - input 0 | IMAGE_MASK | .... to specify * image responses. */ public static final int IMAGE_MASK = 0x0020; /** Mask for windows programs/packages queries - input 0 | WIN_PROG_MASK * | .... to specify windows programs/packages responses. */ public static final int WIN_PROG_MASK = 0x0040; /** Mask for linux/osx programs/packages queries - input 0 | LIN_PROG_MASK * | .... to specify linux/osx programs/packages responses. */ public static final int LIN_PROG_MASK = 0x0080; public static final String WHAT_IS_NEW_QUERY_STRING = "WhatIsNewXOXO"; /** * The payload for the query -- includes the query string, the * XML query, any URNs, GGEP, etc. */ private final byte[] PAYLOAD; /** * The "min speed" field. This was originally used to specify * a minimum speed for returned results, but it was never really * used this way. As of LimeWire 3.0 (02/2003), the bits of * this field were changed to specify things like the firewall * status of the querier. */ private final int MIN_SPEED; /** * The query string. */ private final String QUERY; /** * The LimeXMLDocument of the rich query. */ private final LimeXMLDocument XML_DOC; /** * The feature that this query is. */ private int _featureSelector = 0; /** * Whether or not the GGEP header for Do Not Proxy was found. */ private boolean _doNotProxy = false; // HUGE v0.93 fields /** * The types of requested URNs. */ private final Set /* of UrnType */ REQUESTED_URN_TYPES; /** * Specific URNs requested. */ private final Set /* of URN */ QUERY_URNS; /** * The Query Key associated with this query -- can be null. */ private final QueryKey QUERY_KEY; /** * The flag in the 'M' GGEP extension - if non-null, the query is requesting * only certain types. */ private Integer _metaMask = null; /** * If we're re-originated this query for a leaf. This can be set/read * after creation. */ private boolean originated = false; /** * Cached hash code for this instance. */ private volatile int _hashCode = 0; /** * Constant for an empty, unmodifiable <tt>Set</tt>. This is necessary * because Collections.EMPTY_SET is not serializable in the collections * 1.1 implementation. */ private static final Set EMPTY_SET = Collections.unmodifiableSet(new HashSet()); /** * Constant for the default query TTL. */ private static final byte DEFAULT_TTL = 6; /** * Cached illegal characters in search strings. */ private static final char[] ILLEGAL_CHARS = SearchSettings.ILLEGAL_CHARS.getValue(); /** * Cache the maximum length for queries, in bytes. */ private static final int MAX_QUERY_LENGTH = SearchSettings.MAX_QUERY_LENGTH.getValue(); /** * Cache the maximum length for XML queries, in bytes. */ private static final int MAX_XML_QUERY_LENGTH = SearchSettings.MAX_XML_QUERY_LENGTH.getValue(); /** * The meaningless query string we put in URN queries. Needed because * LimeWire's drop empty queries.... */ private static final String DEFAULT_URN_QUERY = "\\"; /** * Creates a new requery for the specified SHA1 value. * * @param sha1 the <tt>URN</tt> of the file to search for * @return a new <tt>QueryRequest</tt> for the specified SHA1 value * @throws <tt>NullPointerException</tt> if the <tt>sha1</tt> argument * is <tt>null</tt> */ public static QueryRequest createRequery(URN sha1) { if(sha1 == null) { throw new NullPointerException("null sha1"); } Set sha1Set = new HashSet(); sha1Set.add(sha1); return new QueryRequest(newQueryGUID(true), DEFAULT_TTL, DEFAULT_URN_QUERY, "", UrnType.SHA1_SET, sha1Set, null, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, false, 0, false, 0); } /** * Creates a new query for the specified SHA1 value. * * @param sha1 the <tt>URN</tt> of the file to search for * @return a new <tt>QueryRequest</tt> for the specified SHA1 value * @throws <tt>NullPointerException</tt> if the <tt>sha1</tt> argument * is <tt>null</tt> */ public static QueryRequest createQuery(URN sha1) { if(sha1 == null) { throw new NullPointerException("null sha1"); } Set sha1Set = new HashSet(); sha1Set.add(sha1); return new QueryRequest(newQueryGUID(false), DEFAULT_TTL, DEFAULT_URN_QUERY, "", UrnType.SHA1_SET, sha1Set, null, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, false, 0, false, 0); } /** * Creates a new requery for the specified SHA1 value with file name * thrown in for good measure (or at least until \ works as a query). * * @param sha1 the <tt>URN</tt> of the file to search for * @return a new <tt>QueryRequest</tt> for the specified SHA1 value * @throws <tt>NullPointerException</tt> if the <tt>sha1</tt> argument * is <tt>null</tt> */ public static QueryRequest createRequery(URN sha1, String filename) { if(sha1 == null) { throw new NullPointerException("null sha1"); } if(filename == null) { throw new NullPointerException("null query"); } if(filename.length() == 0) { filename = DEFAULT_URN_QUERY; } Set sha1Set = new HashSet(); sha1Set.add(sha1); return new QueryRequest(newQueryGUID(true), DEFAULT_TTL, filename, "", UrnType.SHA1_SET, sha1Set, null, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, false, 0, false, 0); } /** * Creates a new query for the specified SHA1 value with file name * thrown in for good measure (or at least until \ works as a query). * * @param sha1 the <tt>URN</tt> of the file to search for * @return a new <tt>QueryRequest</tt> for the specified SHA1 value * @throws <tt>NullPointerException</tt> if the <tt>sha1</tt> argument * is <tt>null</tt> */ public static QueryRequest createQuery(URN sha1, String filename) { if(sha1 == null) { throw new NullPointerException("null sha1"); } if(filename == null) { throw new NullPointerException("null query"); } if(filename.length() == 0) { filename = DEFAULT_URN_QUERY; } Set sha1Set = new HashSet(); sha1Set.add(sha1); return new QueryRequest(newQueryGUID(false), DEFAULT_TTL, filename, "", UrnType.SHA1_SET, sha1Set, null, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, false, 0, false, 0); } /** * Creates a new requery for the specified SHA1 value and the specified * firewall boolean. * * @param sha1 the <tt>URN</tt> of the file to search for * @param ttl the time to live (ttl) of the query * @return a new <tt>QueryRequest</tt> for the specified SHA1 value * @throws <tt>NullPointerException</tt> if the <tt>sha1</tt> argument * is <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the ttl value is * negative or greater than the maximum allowed value */ public static QueryRequest createRequery(URN sha1, byte ttl) { if(sha1 == null) { throw new NullPointerException("null sha1"); } if(ttl <= 0 || ttl > 6) { throw new IllegalArgumentException("invalid TTL: "+ttl); } Set sha1Set = new HashSet(); sha1Set.add(sha1); return new QueryRequest(newQueryGUID(true), ttl, DEFAULT_URN_QUERY, "", UrnType.SHA1_SET, sha1Set, null, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, false, 0, false, 0); } /** * Creates a new query for the specified UrnType set and URN set. * * @param urnTypeSet the <tt>Set</tt> of <tt>UrnType</tt>s to request. * @param urnSet the <tt>Set</tt> of <tt>URNs</tt>s to request. * @return a new <tt>QueryRequest</tt> for the specied UrnTypes and URNs * @throws <tt>NullPointerException</tt> if either sets are null. */ public static QueryRequest createQuery(Set urnTypeSet, Set urnSet) { if(urnSet == null) throw new NullPointerException("null urnSet"); if(urnTypeSet == null) throw new NullPointerException("null urnTypeSet"); return new QueryRequest(newQueryGUID(false), DEFAULT_TTL, DEFAULT_URN_QUERY, "", urnTypeSet, urnSet, null, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, false, 0, false, 0); } /** * Creates a requery for when we don't know the hash of the file -- * we don't know the hash. * * @param query the query string * @return a new <tt>QueryRequest</tt> for the specified query * @throws <tt>NullPointerException</tt> if the <tt>query</tt> argument * is <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the <tt>query</tt> * argument is zero-length (empty) */ public static QueryRequest createRequery(String query) { if(query == null) { throw new NullPointerException("null query"); } if(query.length() == 0) { throw new IllegalArgumentException("empty query"); } return new QueryRequest(newQueryGUID(true), query); } /** * Creates a new query for the specified file name, with no XML. * * @param query the file name to search for * @return a new <tt>QueryRequest</tt> for the specified query * @throws <tt>NullPointerException</tt> if the <tt>query</tt> argument * is <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the <tt>query</tt> * argument is zero-length (empty) */ public static QueryRequest createQuery(String query) { if(query == null) { throw new NullPointerException("null query"); } if(query.length() == 0) { throw new IllegalArgumentException("empty query"); } return new QueryRequest(newQueryGUID(false), query); } /** * Creates a new query for the specified file name and the designated XML. * * @param query the file name to search for * @param guid I trust that this is a address encoded guid. Your loss if * it isn't.... * @return a new <tt>QueryRequest</tt> for the specified query that has * encoded the input ip and port into the GUID and appropriate marked the * query to signify out of band support. * @throws <tt>NullPointerException</tt> if the <tt>query</tt> argument * is <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the <tt>query</tt> * argument is zero-length (empty) */ public static QueryRequest createOutOfBandQuery(byte[] guid, String query, String xmlQuery) { query = I18NConvert.instance().getNorm(query); if(query == null) { throw new NullPointerException("null query"); } if(xmlQuery == null) { throw new NullPointerException("null xml query"); } if(query.length() == 0 && xmlQuery.length() == 0) { throw new IllegalArgumentException("empty query"); } if(xmlQuery.length() != 0 && !xmlQuery.startsWith("<?xml")) { throw new IllegalArgumentException("invalid XML"); } return new QueryRequest(guid, DEFAULT_TTL, query, xmlQuery, true); } /** * Creates a new query for the specified file name and the designated XML. * * @param query the file name to search for * @param guid I trust that this is a address encoded guid. Your loss if * it isn't.... * @param type can be null - the type of results you want. * @return a new <tt>QueryRequest</tt> for the specified query that has * encoded the input ip and port into the GUID and appropriate marked the * query to signify out of band support AND specifies a file type category. * @throws <tt>NullPointerException</tt> if the <tt>query</tt> argument * is <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the <tt>query</tt> * argument is zero-length (empty) */ public static QueryRequest createOutOfBandQuery(byte[] guid, String query, String xmlQuery, MediaType type) { query = I18NConvert.instance().getNorm(query); if(query == null) { throw new NullPointerException("null query"); } if(xmlQuery == null) { throw new NullPointerException("null xml query"); } if(query.length() == 0 && xmlQuery.length() == 0) { throw new IllegalArgumentException("empty query"); } if(xmlQuery.length() != 0 && !xmlQuery.startsWith("<?xml")) { throw new IllegalArgumentException("invalid XML"); } return new QueryRequest(guid, DEFAULT_TTL, query, xmlQuery, true, type); } /** * Creates a new query for the specified file name, with no XML. * * @param query the file name to search for * @return a new <tt>QueryRequest</tt> for the specified query that has * encoded the input ip and port into the GUID and appropriate marked the * query to signify out of band support. * @throws <tt>NullPointerException</tt> if the <tt>query</tt> argument * is <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the <tt>query</tt> * argument is zero-length (empty) */ public static QueryRequest createOutOfBandQuery(String query, byte[] ip, int port) { byte[] guid = GUID.makeAddressEncodedGuid(ip, port); return QueryRequest.createOutOfBandQuery(guid, query, ""); } /** * Creates a new 'What is new'? query with the specified guid and ttl. * @param ttl the desired ttl of the query. * @param guid the desired guid of the query. */ public static QueryRequest createWhatIsNewQuery(byte[] guid, byte ttl) { return createWhatIsNewQuery(guid, ttl, null); } /** * Creates a new 'What is new'? query with the specified guid and ttl. * @param ttl the desired ttl of the query. * @param guid the desired guid of the query. */ public static QueryRequest createWhatIsNewQuery(byte[] guid, byte ttl, MediaType type) { if (ttl < 1) throw new IllegalArgumentException("Bad TTL."); return new QueryRequest(guid, ttl, WHAT_IS_NEW_QUERY_STRING, "", null, null, null, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, false, FeatureSearchData.WHAT_IS_NEW, false, getMetaFlag(type)); } /** * Creates a new 'What is new'? OOB query with the specified guid and ttl. * @param ttl the desired ttl of the query. * @param guid the desired guid of the query. */ public static QueryRequest createWhatIsNewOOBQuery(byte[] guid, byte ttl) { return createWhatIsNewOOBQuery(guid, ttl, null); } /** * Creates a new 'What is new'? OOB query with the specified guid and ttl. * @param ttl the desired ttl of the query. * @param guid the desired guid of the query. */ public static QueryRequest createWhatIsNewOOBQuery(byte[] guid, byte ttl, MediaType type) { if (ttl < 1) throw new IllegalArgumentException("Bad TTL."); return new QueryRequest(guid, ttl, WHAT_IS_NEW_QUERY_STRING, "", null, null, null, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, true, FeatureSearchData.WHAT_IS_NEW, false, getMetaFlag(type)); } /** * Creates a new query for the specified file name, with no XML. * * @param query the file name to search for * @return a new <tt>QueryRequest</tt> for the specified query * @throws <tt>NullPointerException</tt> if the <tt>query</tt> argument * is <tt>null</tt> or if the <tt>xmlQuery</tt> argument is * <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the <tt>query</tt> * argument and the xml query are both zero-length (empty) */ public static QueryRequest createQuery(String query, String xmlQuery) { if(query == null) { throw new NullPointerException("null query"); } if(xmlQuery == null) { throw new NullPointerException("null xml query"); } if(query.length() == 0 && xmlQuery.length() == 0) { throw new IllegalArgumentException("empty query"); } if(xmlQuery.length() != 0 && !xmlQuery.startsWith("<?xml")) { throw new IllegalArgumentException("invalid XML"); } return new QueryRequest(newQueryGUID(false), query, xmlQuery); } /** * Creates a new query for the specified file name, with no XML. * * @param query the file name to search for * @param ttl the time to live (ttl) of the query * @return a new <tt>QueryRequest</tt> for the specified query and ttl * @throws <tt>NullPointerException</tt> if the <tt>query</tt> argument * is <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the <tt>query</tt> * argument is zero-length (empty) * @throws <tt>IllegalArgumentException</tt> if the ttl value is * negative or greater than the maximum allowed value */ public static QueryRequest createQuery(String query, byte ttl) { if(query == null) { throw new NullPointerException("null query"); } if(query.length() == 0) { throw new IllegalArgumentException("empty query"); } if(ttl <= 0 || ttl > 6) { throw new IllegalArgumentException("invalid TTL: "+ttl); } return new QueryRequest(newQueryGUID(false), ttl, query); } /** * Creates a new query with the specified guid, query string, and * xml query string. * * @param guid the message GUID for the query * @param query the query string * @param xmlQuery the xml query string * @return a new <tt>QueryRequest</tt> for the specified query, xml * query, and guid * @throws <tt>NullPointerException</tt> if the <tt>query</tt> argument * is <tt>null</tt>, if the <tt>xmlQuery</tt> argument is <tt>null</tt>, * or if the <tt>guid</tt> argument is <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the guid length is * not 16, if both the query strings are empty, or if the XML does * not appear to be valid */ public static QueryRequest createQuery(byte[] guid, String query, String xmlQuery) { query = I18NConvert.instance().getNorm(query); if(guid == null) { throw new NullPointerException("null guid"); } if(guid.length != 16) { throw new IllegalArgumentException("invalid guid length"); } if(query == null) { throw new NullPointerException("null query"); } if(xmlQuery == null) { throw new NullPointerException("null xml query"); } if(query.length() == 0 && xmlQuery.length() == 0) { throw new IllegalArgumentException("empty query"); } if(xmlQuery.length() != 0 && !xmlQuery.startsWith("<?xml")) { throw new IllegalArgumentException("invalid XML"); } return new QueryRequest(guid, query, xmlQuery); } /** * Creates a new query with the specified guid, query string, and * xml query string. * * @param guid the message GUID for the query * @param query the query string * @param xmlQuery the xml query string * @return a new <tt>QueryRequest</tt> for the specified query, xml * query, and guid * @throws <tt>NullPointerException</tt> if the <tt>query</tt> argument * is <tt>null</tt>, if the <tt>xmlQuery</tt> argument is <tt>null</tt>, * or if the <tt>guid</tt> argument is <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the guid length is * not 16, if both the query strings are empty, or if the XML does * not appear to be valid */ public static QueryRequest createQuery(byte[] guid, String query, String xmlQuery, MediaType type) { query = I18NConvert.instance().getNorm(query); if(guid == null) { throw new NullPointerException("null guid"); } if(guid.length != 16) { throw new IllegalArgumentException("invalid guid length"); } if(query == null) { throw new NullPointerException("null query"); } if(xmlQuery == null) { throw new NullPointerException("null xml query"); } if(query.length() == 0 && xmlQuery.length() == 0) { throw new IllegalArgumentException("empty query"); } if(xmlQuery.length() != 0 && !xmlQuery.startsWith("<?xml")) { throw new IllegalArgumentException("invalid XML"); } return new QueryRequest(guid, DEFAULT_TTL, query, xmlQuery, type); } /** * Creates a new query from the existing query with the specified * ttl. * * @param qr the <tt>QueryRequest</tt> to copy * @param ttl the new ttl * @return a new <tt>QueryRequest</tt> with the specified ttl */ public static QueryRequest createQuery(QueryRequest qr, byte ttl) { // Construct a query request that is EXACTLY like the other query, // but with a different TTL. try { return createNetworkQuery(qr.getGUID(), ttl, qr.getHops(), qr.PAYLOAD, qr.getNetwork()); } catch(BadPacketException ioe) { throw new IllegalArgumentException(ioe.getMessage()); } } /** * Creates a new OOBquery from the existing query with the specified guid * (which should be address encoded). * * @param qr the <tt>QueryRequest</tt> to copy * @return a new <tt>QueryRequest</tt> with the specified guid that is now * OOB marked. * @throws IllegalArgumentException thrown if guid is not right size of if * query is bad. */ public static QueryRequest createProxyQuery(QueryRequest qr, byte[] guid) { if (guid.length != 16) throw new IllegalArgumentException("bad guid size: " + guid.length); // i can't just call a new constructor, i have to recreate stuff byte[] newPayload = new byte[qr.PAYLOAD.length]; System.arraycopy(qr.PAYLOAD, 0, newPayload, 0, newPayload.length); newPayload[0] |= SPECIAL_OUTOFBAND_MASK; try { return createNetworkQuery(guid, qr.getTTL(), qr.getHops(), newPayload, qr.getNetwork()); } catch (BadPacketException ioe) { throw new IllegalArgumentException(ioe.getMessage()); } } /** * Creates a new query from the existing query and loses the OOB marking. * * @param qr the <tt>QueryRequest</tt> to copy * @return a new <tt>QueryRequest</tt> with no OOB marking */ public static QueryRequest unmarkOOBQuery(QueryRequest qr) { //modify the payload to not be OOB. byte[] newPayload = new byte[qr.PAYLOAD.length]; System.arraycopy(qr.PAYLOAD, 0, newPayload, 0, newPayload.length); newPayload[0] &= ~SPECIAL_OUTOFBAND_MASK; newPayload[0] |= SPECIAL_XML_MASK; try { return createNetworkQuery(qr.getGUID(), qr.getTTL(), qr.getHops(), newPayload, qr.getNetwork()); } catch (BadPacketException ioe) { throw new IllegalArgumentException(ioe.getMessage()); } } /** * Creates a new query with the specified query key for use in * GUESS-style UDP queries. * * @param query the query string * @param key the query key * @return a new <tt>QueryRequest</tt> instance with the specified * query string and query key * @throws <tt>NullPointerException</tt> if the <tt>query</tt> argument * is <tt>null</tt> or if the <tt>key</tt> argument is <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the <tt>query</tt> * argument is zero-length (empty) */ public static QueryRequest createQueryKeyQuery(String query, QueryKey key) { if(query == null) { throw new NullPointerException("null query"); } if(query.length() == 0) { throw new IllegalArgumentException("empty query"); } if(key == null) { throw new NullPointerException("null query key"); } return new QueryRequest(newQueryGUID(false), (byte)1, query, "", UrnType.ANY_TYPE_SET, EMPTY_SET, key, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, false, 0, false, 0); } /** * Creates a new query with the specified query key for use in * GUESS-style UDP queries. * * @param sha1 the URN * @param key the query key * @return a new <tt>QueryRequest</tt> instance with the specified * URN request and query key * @throws <tt>NullPointerException</tt> if the <tt>query</tt> argument * is <tt>null</tt> or if the <tt>key</tt> argument is <tt>null</tt> * @throws <tt>IllegalArgumentException</tt> if the <tt>query</tt> * argument is zero-length (empty) */ public static QueryRequest createQueryKeyQuery(URN sha1, QueryKey key) { if(sha1 == null) { throw new NullPointerException("null sha1"); } if(key == null) { throw new NullPointerException("null query key"); } Set sha1Set = new HashSet(); sha1Set.add(sha1); return new QueryRequest(newQueryGUID(false), (byte) 1, DEFAULT_URN_QUERY, "", UrnType.SHA1_SET, sha1Set, key, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, false, 0, false, 0); } /** * Creates a new <tt>QueryRequest</tt> instance for multicast queries. * This is necessary due to the unique properties of multicast queries, * such as the firewalled bit not being set regardless of whether or * not the node is truly firewalled/NATted to the world outside the * subnet. * * @param qr the <tt>QueryRequest</tt> instance containing all the * data necessary to create a multicast query * @return a new <tt>QueryRequest</tt> instance with bits set for * multicast -- a min speed bit in particular * @throws <tt>NullPointerException</tt> if the <tt>qr</tt> argument * is <tt>null</tt> */ public static QueryRequest createMulticastQuery(QueryRequest qr) { if(qr == null) throw new NullPointerException("null query"); //modify the payload to not be OOB. byte[] newPayload = new byte[qr.PAYLOAD.length]; System.arraycopy(qr.PAYLOAD, 0, newPayload, 0, newPayload.length); newPayload[0] &= ~SPECIAL_OUTOFBAND_MASK; newPayload[0] |= SPECIAL_XML_MASK; try { return createNetworkQuery(qr.getGUID(), (byte)1, qr.getHops(), newPayload, Message.N_MULTICAST); } catch (BadPacketException ioe) { throw new IllegalArgumentException(ioe.getMessage()); } } /** * Creates a new <tt>QueryRequest</tt> that is a copy of the input * query, except that it includes the specified query key. * * @param qr the <tt>QueryRequest</tt> to use * @param key the <tt>QueryKey</tt> to add * @return a new <tt>QueryRequest</tt> from the specified query and * key */ public static QueryRequest createQueryKeyQuery(QueryRequest qr, QueryKey key) { // TODO: Copy the payload verbatim, except add the query-key // into the GGEP section. return new QueryRequest(qr.getGUID(), qr.getTTL(), qr.getQuery(), qr.getRichQueryString(), qr.getRequestedUrnTypes(), qr.getQueryUrns(), key, qr.isFirewalledSource(), Message.N_UNKNOWN, qr.desiresOutOfBandReplies(), qr.getFeatureSelector(), false, qr.getMetaMask()); } /** * Creates a new specialized <tt>QueryRequest</tt> instance for * browse host queries so that <tt>FileManager</tt> can understand them. * * @return a new <tt>QueryRequest</tt> for browse host queries */ public static QueryRequest createBrowseHostQuery() { return new QueryRequest(newQueryGUID(false), (byte)1, FileManager.INDEXING_QUERY, "", UrnType.ANY_TYPE_SET, EMPTY_SET, null, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, false, 0, false, 0); } /** * Specialized constructor used to create a query without the firewalled * bit set. This should primarily be used for testing. * * @param query the query string * @return a new <tt>QueryRequest</tt> with the specified query string * and without the firewalled bit set */ public static QueryRequest createNonFirewalledQuery(String query, byte ttl) { return new QueryRequest(newQueryGUID(false), ttl, query, "", UrnType.ANY_TYPE_SET, EMPTY_SET, null, false, Message.N_UNKNOWN, false, 0, false, 0); } /** * Creates a new query from the network. * * @param guid the GUID of the query * @param ttl the time to live of the query * @param hops the hops of the query * @param payload the query payload * * @return a new <tt>QueryRequest</tt> instance from the specified data */ public static QueryRequest createNetworkQuery(byte[] guid, byte ttl, byte hops, byte[] payload, int network) throws BadPacketException { return new QueryRequest(guid, ttl, hops, payload, network); } /** * Builds a new query from scratch, with no metadata, using the given GUID. * Whether or not this is a repeat query is encoded in guid. GUID must have * been created via newQueryGUID; this allows the caller to match up results */ private QueryRequest(byte[] guid, String query) { this(guid, query, ""); } /** * Builds a new query from scratch, with no metadata, using the given GUID. * Whether or not this is a repeat query is encoded in guid. GUID must have * been created via newQueryGUID; this allows the caller to match up results */ private QueryRequest(byte[] guid, byte ttl, String query) { this(guid, ttl, query, ""); } /** * Builds a new query from scratch, with no metadata, using the given GUID. * Whether or not this is a repeat query is encoded in guid. GUID must have * been created via newQueryGUID; this allows the caller to match up results */ private QueryRequest(byte[] guid, String query, String xmlQuery) { this(guid, DEFAULT_TTL, query, xmlQuery); } /** * Builds a new query from scratch, with metadata, using the given GUID. * Whether or not this is a repeat query is encoded in guid. GUID must have * been created via newQueryGUID; this allows the caller to match up * results. * * @requires 0<=minSpeed<2^16 (i.e., can fit in 2 unsigned bytes) */ private QueryRequest(byte[] guid, byte ttl, String query, String richQuery) { this(guid, ttl, query, richQuery, UrnType.ANY_TYPE_SET, EMPTY_SET, null, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, false, 0, false, 0); } /** * Builds a new query from scratch, with metadata, using the given GUID. * Whether or not this is a repeat query is encoded in guid. GUID must have * been created via newQueryGUID; this allows the caller to match up * results. * * @requires 0<=minSpeed<2^16 (i.e., can fit in 2 unsigned bytes) */ private QueryRequest(byte[] guid, byte ttl, String query, String richQuery, MediaType type) { this(guid, ttl, query, richQuery, UrnType.ANY_TYPE_SET, EMPTY_SET, null, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, false, 0, false, getMetaFlag(type)); } /** * Builds a new query from scratch, with metadata, using the given GUID. * Whether or not this is a repeat query is encoded in guid. GUID must have * been created via newQueryGUID; this allows the caller to match up * results. * * @requires 0<=minSpeed<2^16 (i.e., can fit in 2 unsigned bytes) */ private QueryRequest(byte[] guid, byte ttl, String query, String richQuery, boolean canReceiveOutOfBandReplies) { this(guid, ttl, query, richQuery, UrnType.ANY_TYPE_SET, EMPTY_SET, null, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, canReceiveOutOfBandReplies, 0, false, 0); } /** * Builds a new query from scratch, with metadata, using the given GUID. * Whether or not this is a repeat query is encoded in guid. GUID must have * been created via newQueryGUID; this allows the caller to match up * results. * * @requires 0<=minSpeed<2^16 (i.e., can fit in 2 unsigned bytes) */ private QueryRequest(byte[] guid, byte ttl, String query, String richQuery, boolean canReceiveOutOfBandReplies, MediaType type) { this(guid, ttl, query, richQuery, UrnType.ANY_TYPE_SET, EMPTY_SET, null, !RouterService.acceptedIncomingConnection(), Message.N_UNKNOWN, canReceiveOutOfBandReplies, 0, false, getMetaFlag(type)); } private static int getMetaFlag(MediaType type) { int metaFlag = 0; if (type == null) ; else if (type.getDescriptionKey() == MediaType.AUDIO) metaFlag |= AUDIO_MASK; else if (type.getDescriptionKey() == MediaType.VIDEO) metaFlag |= VIDEO_MASK; else if (type.getDescriptionKey() == MediaType.IMAGES) metaFlag |= IMAGE_MASK; else if (type.getDescriptionKey() == MediaType.DOCUMENTS) metaFlag |= DOC_MASK; else if (type.getDescriptionKey() == MediaType.PROGRAMS) { if (CommonUtils.isLinux() || CommonUtils.isAnyMac()) metaFlag |= LIN_PROG_MASK; else if (CommonUtils.isWindows()) metaFlag |= WIN_PROG_MASK; else // Other OS, search any type of programs metaFlag |= (LIN_PROG_MASK|WIN_PROG_MASK); } return metaFlag; } /** * Builds a new query from scratch but you can flag it as a Requery, if * needed. If you need to make a query that accepts out-of-band results, * be sure to set the guid correctly (see GUID.makeAddressEncodedGUI) and * set canReceiveOutOfBandReplies . * * @requires 0<=minSpeed<2^16 (i.e., can fit in 2 unsigned bytes) * @param requestedUrnTypes <tt>Set</tt> of <tt>UrnType</tt> instances * requested for this query, which may be empty or null if no types were * requested * @param queryUrns <tt>Set</tt> of <tt>URN</tt> instances requested for * this query, which may be empty or null if no URNs were requested * @throws <tt>IllegalArgumentException</tt> if the query string, the xml * query string, and the urns are all empty, or if the feature selector * is bad */ public QueryRequest(byte[] guid, byte ttl, String query, String richQuery, Set requestedUrnTypes, Set queryUrns, QueryKey queryKey, boolean isFirewalled, int network, boolean canReceiveOutOfBandReplies, int featureSelector) { // calls me with the doNotProxy flag set to false this(guid, ttl, query, richQuery, requestedUrnTypes, queryUrns, queryKey, isFirewalled, network, canReceiveOutOfBandReplies, featureSelector, false, 0); } /** * Builds a new query from scratch but you can flag it as a Requery, if * needed. If you need to make a query that accepts out-of-band results, * be sure to set the guid correctly (see GUID.makeAddressEncodedGUI) and * set canReceiveOutOfBandReplies . * * @requires 0<=minSpeed<2^16 (i.e., can fit in 2 unsigned bytes) * @param requestedUrnTypes <tt>Set</tt> of <tt>UrnType</tt> instances * requested for this query, which may be empty or null if no types were * requested * @param queryUrns <tt>Set</tt> of <tt>URN</tt> instances requested for * this query, which may be empty or null if no URNs were requested * @throws <tt>IllegalArgumentException</tt> if the query string, the xml * query string, and the urns are all empty, or if the feature selector * is bad */ public QueryRequest(byte[] guid, byte ttl, String query, String richQuery, Set requestedUrnTypes, Set queryUrns, QueryKey queryKey, boolean isFirewalled, int network, boolean canReceiveOutOfBandReplies, int featureSelector, boolean doNotProxy, int metaFlagMask) { this(guid, ttl, 0, query, richQuery, requestedUrnTypes, queryUrns, queryKey, isFirewalled, network, canReceiveOutOfBandReplies, featureSelector, doNotProxy, metaFlagMask); } /** * Builds a new query from scratch but you can flag it as a Requery, if * needed. If you need to make a query that accepts out-of-band results, * be sure to set the guid correctly (see GUID.makeAddressEncodedGUI) and * set canReceiveOutOfBandReplies . * * @requires 0<=minSpeed<2^16 (i.e., can fit in 2 unsigned bytes) * @param requestedUrnTypes <tt>Set</tt> of <tt>UrnType</tt> instances * requested for this query, which may be empty or null if no types were * requested * @param queryUrns <tt>Set</tt> of <tt>URN</tt> instances requested for * this query, which may be empty or null if no URNs were requested * @throws <tt>IllegalArgumentException</tt> if the query string, the xml * query string, and the urns are all empty, or if the capability selector * is bad */ public QueryRequest(byte[] guid, byte ttl, int minSpeed, String query, String richQuery, Set requestedUrnTypes, Set queryUrns, QueryKey queryKey, boolean isFirewalled, int network, boolean canReceiveOutOfBandReplies, int featureSelector, boolean doNotProxy, int metaFlagMask) { // don't worry about getting the length right at first super(guid, Message.F_QUERY, ttl, /* hops */ (byte)0, /* length */ 0, network); if((query == null || query.length() == 0) && (richQuery == null || richQuery.length() == 0) && (queryUrns == null || queryUrns.size() == 0)) { throw new IllegalArgumentException("cannot create empty query"); } if(query != null && query.length() > MAX_QUERY_LENGTH) { throw new IllegalArgumentException("query too big: " + query); } if(richQuery != null && richQuery.length() > MAX_XML_QUERY_LENGTH) { throw new IllegalArgumentException("xml too big: " + richQuery); } if(query != null && !(queryUrns != null && queryUrns.size() > 0 && query.equals(DEFAULT_URN_QUERY)) && hasIllegalChars(query)) { throw new IllegalArgumentException("illegal chars: " + query); } if (featureSelector < 0) throw new IllegalArgumentException("Bad feature = " + featureSelector); _featureSelector = featureSelector; if ((metaFlagMask > 0) && (metaFlagMask < 4) || (metaFlagMask > 248)) throw new IllegalArgumentException("Bad Meta Flag = " + metaFlagMask); if (metaFlagMask > 0) _metaMask = new Integer(metaFlagMask); // only set the minspeed if none was input...x if (minSpeed == 0) { // the new Min Speed format - looks reversed but // it isn't because of ByteOrder.short2leb minSpeed = SPECIAL_MINSPEED_MASK; // set the firewall bit if i'm firewalled if (isFirewalled && !isMulticast()) minSpeed |= SPECIAL_FIREWALL_MASK; // if i'm firewalled and can do solicited, mark the query for fw // transfer capability. if (isFirewalled && UDPService.instance().canDoFWT()) minSpeed |= SPECIAL_FWTRANS_MASK; // THE DEAL: // if we can NOT receive out of band replies, we want in-band XML - // so set the correct bit. // if we can receive out of band replies, we do not want in-band XML // we'll hope the out-of-band reply guys will provide us all // necessary XML. if (!canReceiveOutOfBandReplies) minSpeed |= SPECIAL_XML_MASK; else // bit 10 flags out-of-band support minSpeed |= SPECIAL_OUTOFBAND_MASK; } MIN_SPEED = minSpeed; if(query == null) { this.QUERY = ""; } else { this.QUERY = query; } if(richQuery == null || richQuery.equals("") ) { this.XML_DOC = null; } else { LimeXMLDocument doc = null; try { doc = new LimeXMLDocument(richQuery); } catch(SAXException ignored) { } catch(SchemaNotFoundException ignored) { } catch(IOException ignored) { } this.XML_DOC = doc; } Set tempRequestedUrnTypes = null; Set tempQueryUrns = null; if(requestedUrnTypes != null) { tempRequestedUrnTypes = new HashSet(requestedUrnTypes); } else { tempRequestedUrnTypes = EMPTY_SET; } if(queryUrns != null) { tempQueryUrns = new HashSet(queryUrns); } else { tempQueryUrns = EMPTY_SET; } this.QUERY_KEY = queryKey; this._doNotProxy = doNotProxy; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { ByteOrder.short2leb((short)MIN_SPEED,baos); // write minspeed baos.write(QUERY.getBytes("UTF-8")); // write query baos.write(0); // null // now write any & all HUGE v0.93 General Extension Mechanism // extensions // this specifies whether or not the extension was successfully // written, meaning that the HUGE GEM delimiter should be // written before the next extension boolean addDelimiterBefore = false; byte[] richQueryBytes = null; if(XML_DOC != null) { richQueryBytes = richQuery.getBytes("UTF-8"); } // add the rich query addDelimiterBefore = writeGemExtension(baos, addDelimiterBefore, richQueryBytes); // add the urns addDelimiterBefore = writeGemExtensions(baos, addDelimiterBefore, tempQueryUrns == null ? null : tempQueryUrns.iterator()); // add the urn types addDelimiterBefore = writeGemExtensions(baos, addDelimiterBefore, tempRequestedUrnTypes == null ? null : tempRequestedUrnTypes.iterator()); // add the GGEP Extension, if necessary.... // *---------------------------- // construct the GGEP block GGEP ggepBlock = new GGEP(false); // do COBS // add the query key? if (this.QUERY_KEY != null) { // get query key in byte form.... ByteArrayOutputStream qkBytes = new ByteArrayOutputStream(); this.QUERY_KEY.write(qkBytes); ggepBlock.put(GGEP.GGEP_HEADER_QUERY_KEY_SUPPORT, qkBytes.toByteArray()); } // add the What Is header if (_featureSelector > 0) ggepBlock.put(GGEP.GGEP_HEADER_FEATURE_QUERY, _featureSelector); // add a GGEP-block if we shouldn't proxy if (doNotProxy) ggepBlock.put(GGEP.GGEP_HEADER_NO_PROXY); // add a meta flag if (_metaMask != null) ggepBlock.put(GGEP.GGEP_HEADER_META, _metaMask.intValue()); // if there are GGEP headers, write them out... if ((this.QUERY_KEY != null) || (_featureSelector > 0) || _doNotProxy || (_metaMask != null)) { ByteArrayOutputStream ggepBytes = new ByteArrayOutputStream(); ggepBlock.write(ggepBytes); // write out GGEP addDelimiterBefore = writeGemExtension(baos, addDelimiterBefore, ggepBytes.toByteArray()); } // ----------------------------* baos.write(0); // final null } catch(UnsupportedEncodingException uee) { //this should never happen from the getBytes("UTF-8") call //but there are UnsupportedEncodingExceptions being reported //with UTF-8. //Is there other information we want to pass in as the message? throw new IllegalArgumentException("could not get UTF-8 bytes for query :" + QUERY + " with richquery :" + richQuery); } catch (IOException e) { ErrorService.error(e); } PAYLOAD = baos.toByteArray(); updateLength(PAYLOAD.length); this.QUERY_URNS = Collections.unmodifiableSet(tempQueryUrns); this.REQUESTED_URN_TYPES = Collections.unmodifiableSet(tempRequestedUrnTypes); } /** * Build a new query with data snatched from network * * @param guid the message guid * @param ttl the time to live of the query * @param hops the hops of the query * @param payload the query payload, containing the query string and any * extension strings * @param network the network that this query came from. * @throws <tt>BadPacketException</tt> if this is not a valid query */ private QueryRequest( byte[] guid, byte ttl, byte hops, byte[] payload, int network) throws BadPacketException { super(guid, Message.F_QUERY, ttl, hops, payload.length, network); if(payload == null) { throw new BadPacketException("no payload"); } PAYLOAD=payload; String tempQuery = ""; String tempRichQuery = ""; int tempMinSpeed = 0; Set tempQueryUrns = null; Set tempRequestedUrnTypes = null; QueryKey tempQueryKey = null; try { ByteArrayInputStream bais = new ByteArrayInputStream(this.PAYLOAD); short sp = ByteOrder.leb2short(bais); tempMinSpeed = ByteOrder.ushort2int(sp); tempQuery = new String(super.readNullTerminatedBytes(bais), "UTF-8"); // handle extensions, which include rich query and URN stuff byte[] extsBytes = super.readNullTerminatedBytes(bais); HUGEExtension huge = new HUGEExtension(extsBytes); GGEP ggep = huge.getGGEP(); if(ggep != null) { try { if (ggep.hasKey(GGEP.GGEP_HEADER_QUERY_KEY_SUPPORT)) { byte[] qkBytes = ggep.getBytes(GGEP.GGEP_HEADER_QUERY_KEY_SUPPORT); tempQueryKey = QueryKey.getQueryKey(qkBytes, false); } if (ggep.hasKey(GGEP.GGEP_HEADER_FEATURE_QUERY)) _featureSelector = ggep.getInt(GGEP.GGEP_HEADER_FEATURE_QUERY); if (ggep.hasKey(GGEP.GGEP_HEADER_NO_PROXY)) _doNotProxy = true; if (ggep.hasKey(GGEP.GGEP_HEADER_META)) { _metaMask = new Integer(ggep.getInt(GGEP.GGEP_HEADER_META)); // if the value is something we can't handle, don't even set it if ((_metaMask.intValue() < 4) || (_metaMask.intValue() > 248)) _metaMask = null; } } catch (BadGGEPPropertyException ignored) {} } tempQueryUrns = huge.getURNS(); tempRequestedUrnTypes = huge.getURNTypes(); for (Iterator iter = huge.getMiscBlocks().iterator(); iter.hasNext() && tempRichQuery.equals(""); ) { String currMiscBlock = (String) iter.next(); if (currMiscBlock.startsWith("<?xml")) tempRichQuery = currMiscBlock; } } catch(UnsupportedEncodingException uee) { //couldn't build query from network due to unsupportedencodingexception //so throw a BadPacketException throw new BadPacketException(uee.getMessage()); } catch (IOException ioe) { ErrorService.error(ioe); } QUERY = tempQuery; LimeXMLDocument tempDoc = null; try { tempDoc = new LimeXMLDocument(tempRichQuery); } catch(SAXException ignored) { } catch(SchemaNotFoundException ignored) { } catch(IOException ignored) { } this.XML_DOC = tempDoc; MIN_SPEED = tempMinSpeed; if(tempQueryUrns == null) { QUERY_URNS = EMPTY_SET; } else { QUERY_URNS = Collections.unmodifiableSet(tempQueryUrns); } if(tempRequestedUrnTypes == null) { REQUESTED_URN_TYPES = EMPTY_SET; } else { REQUESTED_URN_TYPES = Collections.unmodifiableSet(tempRequestedUrnTypes); } QUERY_KEY = tempQueryKey; if(QUERY.length() == 0 && tempRichQuery.length() == 0 && QUERY_URNS.size() == 0) { ReceivedErrorStat.QUERY_EMPTY.incrementStat(); throw new BadPacketException("empty query"); } if(QUERY.length() > MAX_QUERY_LENGTH) { ReceivedErrorStat.QUERY_TOO_LARGE.incrementStat(); //throw BadPacketException.QUERY_TOO_BIG; throw new BadPacketException("query too big: " + QUERY); } if(tempRichQuery.length() > MAX_XML_QUERY_LENGTH) { ReceivedErrorStat.QUERY_XML_TOO_LARGE.incrementStat(); //throw BadPacketException.XML_QUERY_TOO_BIG; throw new BadPacketException("xml too big: " + tempRichQuery); } if(!(QUERY_URNS.size() > 0 && QUERY.equals(DEFAULT_URN_QUERY)) && hasIllegalChars(QUERY)) { ReceivedErrorStat.QUERY_ILLEGAL_CHARS.incrementStat(); //throw BadPacketException.ILLEGAL_CHAR_IN_QUERY; throw new BadPacketException("illegal chars: " + QUERY); } } private static boolean hasIllegalChars(String query) { return StringUtils.containsCharacters(query,ILLEGAL_CHARS); } /** * Returns a new GUID appropriate for query requests. If isRequery, * the GUID query is marked. */ public static byte[] newQueryGUID(boolean isRequery) { return isRequery ? GUID.makeGuidRequery() : GUID.makeGuid(); } protected void writePayload(OutputStream out) throws IOException { out.write(PAYLOAD); SentMessageStatHandler.TCP_QUERY_REQUESTS.addMessage(this); } /** * Accessor fot the payload of the query hit. * * @return the query hit payload */ public byte[] getPayload() { return PAYLOAD; } /** * Returns the query string of this message.<p> * * The caller should not call the getBytes() method on the returned value, * as this seems to cause problems on the Japanese Macintosh. If you need * the raw bytes of the query string, call getQueryByteAt(int). */ public String getQuery() { return QUERY; } /** * Returns the rich query LimeXMLDocument. * * @return the rich query LimeXMLDocument */ public LimeXMLDocument getRichQuery() { return XML_DOC; } /** * Helper method used internally for getting the rich query string. */ private String getRichQueryString() { if( XML_DOC == null ) return null; else return XML_DOC.getXMLString(); } /** * Returns the <tt>Set</tt> of URN types requested for this query. * * @return the <tt>Set</tt> of <tt>UrnType</tt> instances requested for this * query, which may be empty (not null) if no types were requested */ public Set getRequestedUrnTypes() { return REQUESTED_URN_TYPES; } /** * Returns the <tt>Set</tt> of <tt>URN</tt> instances for this query. * * @return the <tt>Set</tt> of <tt>URN</tt> instances for this query, which * may be empty (not null) if no URNs were requested */ public Set getQueryUrns() { return QUERY_URNS; } /** * Returns whether or not this query contains URNs. * * @return <tt>true</tt> if this query contains URNs,<tt>false</tt> otherwise */ public boolean hasQueryUrns() { return !QUERY_URNS.isEmpty(); } /** * Note: the minimum speed can be represented as a 2-byte unsigned * number, but Java shorts are signed. Hence we must use an int. The * value returned is always smaller than 2^16. */ public int getMinSpeed() { return MIN_SPEED; } /** * Returns true if the query source is a firewalled servent. */ public boolean isFirewalledSource() { if ( !isMulticast() ) { if ((MIN_SPEED & SPECIAL_MINSPEED_MASK) > 0) { if ((MIN_SPEED & SPECIAL_FIREWALL_MASK) > 0) return true; } } return false; } /** * Returns true if the query source desires Lime meta-data in responses. */ public boolean desiresXMLResponses() { if ((MIN_SPEED & SPECIAL_MINSPEED_MASK) > 0) { if ((MIN_SPEED & SPECIAL_XML_MASK) > 0) return true; } return false; } /** * Returns true if the query source can do a firewalled transfer. */ public boolean canDoFirewalledTransfer() { if ((MIN_SPEED & SPECIAL_MINSPEED_MASK) > 0) { if ((MIN_SPEED & SPECIAL_FWTRANS_MASK) > 0) return true; } return false; } /** * Returns true if the query source can accept out-of-band replies. Use * getReplyAddress() and getReplyPort() if this is true to know where to * it. Always send XML if you are sending an out-of-band reply. */ public boolean desiresOutOfBandReplies() { if ((MIN_SPEED & SPECIAL_MINSPEED_MASK) > 0) { if ((MIN_SPEED & SPECIAL_OUTOFBAND_MASK) > 0) return true; } return false; } /** * Returns true if the query source does not want you to proxy for it. */ public boolean doNotProxy() { return _doNotProxy; } /** * Returns true if this query is for 'What is new?' content, i.e. usually * the top 3 YOUNGEST files in your library. */ public boolean isWhatIsNewRequest() { return _featureSelector == FeatureSearchData.WHAT_IS_NEW; } /** * Returns true if this is a feature query. */ public boolean isFeatureQuery() { return _featureSelector > 0; } /** * @return whether this is a browse host query */ public boolean isBrowseHostQuery() { return FileManager.INDEXING_QUERY.equals(getQuery()); } /** * Returns 0 if this is not a "feature" query, else it returns the selector * of the feature query, e.g. What Is New returns 1. */ public int getFeatureSelector() { return _featureSelector; } /** Returns the address to send a out-of-band reply to. Only useful * when desiresOutOfBandReplies() == true. */ public String getReplyAddress() { return (new GUID(getGUID())).getIP(); } /** Returns true if the input bytes match the OOB address of this query. */ public boolean matchesReplyAddress(byte[] ip) { return (new GUID(getGUID())).matchesIP(ip); } /** Returns the port to send a out-of-band reply to. Only useful * when desiresOutOfBandReplies() == true. */ public int getReplyPort() { return (new GUID(getGUID())).getPort(); } /** * Accessor for whether or not this is a requery from a LimeWire. * * @return <tt>true</tt> if it is an automated requery from a LimeWire, * otherwise <tt>false</tt> */ public boolean isLimeRequery() { return GUID.isLimeRequeryGUID(getGUID()); } /** * Returns the QueryKey associated with this Request. May very well be * null. Usually only UDP QueryRequests will have non-null QueryKeys. */ public QueryKey getQueryKey() { return QUERY_KEY; } /** @return true if the query has no constraints on the type of results * it wants back. */ public boolean desiresAll() { return (_metaMask == null); } /** @return true if the query desires 'Audio' results back. */ public boolean desiresAudio() { if (_metaMask != null) return ((_metaMask.intValue() & AUDIO_MASK) > 0); return true; } /** @return true if the query desires 'Video' results back. */ public boolean desiresVideo() { if (_metaMask != null) return ((_metaMask.intValue() & VIDEO_MASK) > 0); return true; } /** @return true if the query desires 'Document' results back. */ public boolean desiresDocuments() { if (_metaMask != null) return ((_metaMask.intValue() & DOC_MASK) > 0); return true; } /** @return true if the query desires 'Image' results back. */ public boolean desiresImages() { if (_metaMask != null) return ((_metaMask.intValue() & IMAGE_MASK) > 0); return true; } /** @return true if the query desires 'Programs/Packages' for Windows * results back. */ public boolean desiresWindowsPrograms() { if (_metaMask != null) return ((_metaMask.intValue() & WIN_PROG_MASK) > 0); return true; } /** @return true if the query desires 'Programs/Packages' for Linux/OSX * results back. */ public boolean desiresLinuxOSXPrograms() { if (_metaMask != null) return ((_metaMask.intValue() & LIN_PROG_MASK) > 0); return true; } /** * Returns the mask of allowed programs. */ public int getMetaMask() { if (_metaMask != null) return _metaMask.intValue(); return 0; } // inherit doc comment public void recordDrop() { DroppedSentMessageStatHandler.TCP_QUERY_REQUESTS.addMessage(this); } /** Returns this, because it's always safe to send big queries. */ public Message stripExtendedPayload() { return this; } /** Marks this as being an re-originated query. */ public void originate() { originated = true; } /** Determines if this is an originated query */ public boolean isOriginated() { return originated; } public int hashCode() { if(_hashCode == 0) { int result = 17; result = (37*result) + QUERY.hashCode(); if( XML_DOC != null ) result = (37*result) + XML_DOC.hashCode(); result = (37*result) + REQUESTED_URN_TYPES.hashCode(); result = (37*result) + QUERY_URNS.hashCode(); if(QUERY_KEY != null) { result = (37*result) + QUERY_KEY.hashCode(); } // TODO:: ADD GUID!! _hashCode = result; } return _hashCode; } // overrides Object.toString public boolean equals(Object o) { if(o == this) return true; if(!(o instanceof QueryRequest)) return false; QueryRequest qr = (QueryRequest)o; return (MIN_SPEED == qr.MIN_SPEED && QUERY.equals(qr.QUERY) && (XML_DOC == null ? qr.XML_DOC == null : XML_DOC.equals(qr.XML_DOC)) && REQUESTED_URN_TYPES.equals(qr.REQUESTED_URN_TYPES) && QUERY_URNS.equals(qr.QUERY_URNS) && Arrays.equals(getGUID(), qr.getGUID()) && Arrays.equals(PAYLOAD, qr.PAYLOAD)); } public String toString() { return "<query: \""+getQuery()+"\", "+ "ttl: "+getTTL()+", "+ "hops: "+getHops()+", "+ "meta: \""+getRichQueryString()+"\", "+ "types: "+getRequestedUrnTypes().size()+","+ "urns: "+getQueryUrns().size()+">"; } }