package com.limegroup.gnutella.handshaking; import java.io.IOException; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.StringTokenizer; import com.limegroup.gnutella.RouterService; import com.limegroup.gnutella.settings.ApplicationSettings; import com.limegroup.gnutella.settings.ConnectionSettings; import com.limegroup.gnutella.util.CommonUtils; import com.limegroup.gnutella.util.IpPort; /** * This class contains the necessary information to form a response to a * connection handshake. It contains a status code, a status message, and * the headers to use in the response. * * There are only two ways to create a HandshakeResponse. * * 1) Create an instance which defaults the status code and status message to * be "200 OK". Only the headers used in the response need to be passed in. * * 2) Create an instance with a custom status code, status message, and the * headers used in the response. */ public class HandshakeResponse { /** * The "default" status code in a connection handshake indicating that * the handshake was successful and the connection can be established. */ public static final int OK = 200; /** * The "default" status message in a connection handshake indicating that * the handshake was successful and the connection can be established. */ public static final String OK_MESSAGE = "OK"; /** * HTTP response code for the crawler. */ public static final int CRAWLER_CODE = 593; /** * HTTP response message for the crawler. */ public static final String CRAWLER_MESSAGE = "Hi"; /** The error code that a shielded leaf node should give to incoming * connections. */ public static final int SHIELDED = 503; /** The error message that a shielded leaf node should give to incoming * connections. */ public static final String SHIELDED_MESSAGE = "I am a shielded leaf node"; /** The error code that a node with no slots should give to incoming * connections. */ public static final int SLOTS_FULL = 503; /** The error message that a node with no slots should give to incoming * connections. */ public static final String SLOTS_FULL_MESSAGE = "Service unavailable"; /** * Default bad status code to be used while rejecting connections */ public static final int DEFAULT_BAD_STATUS_CODE = 503; /** * Default bad status message to be used while rejecting connections */ public static final String DEFAULT_BAD_STATUS_MESSAGE = "Service Not Available"; /** * ??? TODO: check about this error code... */ public static final int LOCALE_NO_MATCH = 577; public static final String LOCALE_NO_MATCH_MESSAGE = "Service Not Available"; /** * HTTP-like status code used when handshaking (e.g., 200, 401, 503). */ private final int STATUS_CODE; /** * Message used with status code when handshaking (e.g., "OK, "Service Not * Available"). The status message together with the status code make up * the status line (i.e., first line) of an HTTP-like response to a * connection handshake. */ private final String STATUS_MESSAGE; /** * Headers to use in the response to a connection handshake. */ private final Properties HEADERS; /** * is the GGEP header set? */ private Boolean _supportsGGEP; /** * Cached boolean for whether or not this is considered a considered a * "good" leaf connection. */ private final boolean GOOD_LEAF; /** * Cached boolean for whether or not this is considered a considered a * "good" ultrapeer connection. */ private final boolean GOOD_ULTRAPEER; /** * Cached value for the number of Ultrapeers this Ultrapeer attempts * to connect to. */ private final int DEGREE; /** * Cached value for whether or not this is a high degree connection. */ private final boolean HIGH_DEGREE; /** * Cached value for whether or not this is an Ultrapeer connection that * supports Ultrapeer query routing. */ private final boolean ULTRAPEER_QRP; /** * Cached value for the maximum TTL to use along this connection. */ private final byte MAX_TTL; /** * Cached value for whether or not this connection supports dynamic * querying. */ private final boolean DYNAMIC_QUERY; /** * Cached value for whether or not this connection reported * X-Ultrapeer: true in it's handshake headers. */ private final boolean ULTRAPEER; /** * Cached value for whether or not this connection reported * X-Ultrapeer: false in it's handshake headers. */ private final boolean LEAF; /** * Cached value for whether or not the connection reported * Content-Encoding: deflate */ private final boolean DEFLATE_ENCODED; /** * Constant for whether or not this connection supports probe * queries. */ private final boolean PROBE_QUERIES; /** * Constant for whether or not this node supports pong caching. */ private final boolean PONG_CACHING; /** * Constant for whether or not this node supports GUESS. */ private final boolean GUESS_CAPABLE; /** * Constant for whether or not this is a crawler. */ private final boolean IS_CRAWLER; /** * Constant for whether or not this node is a LimeWire (or derivative) */ private final boolean IS_LIMEWIRE; /** * Constant for whether or nor this node is an older limewire. */ private final boolean IS_OLD_LIMEWIRE; /** * Constant for whether or not the client claims to do no requerying. */ private final boolean NO_REQUERYING; /** * Locale */ private final String LOCALE_PREF; /** * Constant for the number of hosts to return in X-Try-Ultrapeer headers. */ private static final int NUM_X_TRY_ULTRAPEER_HOSTS = 10; /** * Creates a <tt>HandshakeResponse</tt> which defaults the status code and * status message to be "200 Ok" and uses the desired headers in the * response. * * @param headers the headers to use in the response. */ protected HandshakeResponse(Properties headers) { this(OK, OK_MESSAGE, headers); } /** * Creates a new <tt>HandshakeResponse</tt> instance with the specified * response code and message and with no extra connection headers. * * @param code the status code for the response * @param message the status message */ protected HandshakeResponse(int code, String message) { this(code, message, new Properties()); } /** * Creates a HandshakeResponse with the desired status code, status message, * and headers to respond with. * @param code the response code to use. * @param message the response message to use. * @param headers the headers to use in the response. */ HandshakeResponse(int code, String message, Properties headers) { STATUS_CODE = code; STATUS_MESSAGE = message; HEADERS = headers; DEGREE = extractIntHeaderValue(HEADERS, HeaderNames.X_DEGREE, 6); HIGH_DEGREE = getNumIntraUltrapeerConnections() >= 15; ULTRAPEER_QRP = isVersionOrHigher(HEADERS, HeaderNames.X_ULTRAPEER_QUERY_ROUTING, 0.1F); MAX_TTL = extractByteHeaderValue(HEADERS, HeaderNames.X_MAX_TTL, (byte)4); DYNAMIC_QUERY = isVersionOrHigher(HEADERS, HeaderNames.X_DYNAMIC_QUERY, 0.1F); PROBE_QUERIES = isVersionOrHigher(HEADERS, HeaderNames.X_PROBE_QUERIES, 0.1F); NO_REQUERYING = isFalseValue(HEADERS, HeaderNames.X_REQUERIES); IS_LIMEWIRE = extractStringHeaderValue(headers, HeaderNames.USER_AGENT). toLowerCase().startsWith("limewire"); GOOD_ULTRAPEER = isHighDegreeConnection() && isUltrapeerQueryRoutingConnection() && (getMaxTTL() < 5) && isDynamicQueryConnection(); GOOD_LEAF = GOOD_ULTRAPEER && (IS_LIMEWIRE || NO_REQUERYING); ULTRAPEER = isTrueValue(HEADERS, HeaderNames.X_ULTRAPEER); LEAF = isFalseValue(HEADERS, HeaderNames.X_ULTRAPEER); DEFLATE_ENCODED = isStringValue(HEADERS, HeaderNames.CONTENT_ENCODING, HeaderNames.DEFLATE_VALUE); PONG_CACHING = isVersionOrHigher(headers, HeaderNames.X_PONG_CACHING, 0.1F); GUESS_CAPABLE = isVersionOrHigher(headers, HeaderNames.X_GUESS, 0.1F); IS_CRAWLER = isVersionOrHigher(headers, HeaderNames.CRAWLER, 0.1F); IS_OLD_LIMEWIRE = IS_LIMEWIRE && oldVersion(extractStringHeaderValue(headers, HeaderNames.USER_AGENT)); String loc = extractStringHeaderValue(headers, HeaderNames.X_LOCALE_PREF); LOCALE_PREF = (loc.equals(""))? ApplicationSettings.DEFAULT_LOCALE.getValue(): loc; } /** * @return true if the version of limewire we are connected to is old */ private boolean oldVersion(String userAgent) { StringTokenizer tok = new StringTokenizer(userAgent,"/."); int major = -1; int minor = -1; boolean ret = false; boolean error = false; if(tok.countTokens() < 3) //not limewire return false; try { String str = tok.nextToken();//"limewire" str = tok.nextToken(); major = Integer.parseInt(str); str = tok.nextToken(); minor = Integer.parseInt(str); } catch (NumberFormatException nfx) { error = true; } if(!error && (major<3 || (major==3 && minor < 4)) ) ret = true; return ret; } private static final HandshakeResponse EMPTY_RESPONSE = new HandshakeResponse(new Properties()); /** * Creates an empty response with no headers. This is useful, for * example, during connection handshaking when we haven't yet read * any headers. * * @return a new, empty <tt>HandshakeResponse</tt> instance */ public static HandshakeResponse createEmptyResponse() { return EMPTY_RESPONSE; } /** * Constructs the response from the other host during connection * handshaking. * * @return a new <tt>HandshakeResponse</tt> instance with the headers * sent by the other host */ public static HandshakeResponse createResponse(Properties headers) { return new HandshakeResponse(headers); } /** * Constructs the response from the other host during connection * handshaking. The returned response contains the connection headers * sent by the remote host. * * @param line the status line received from the connecting host * @param headers the headers received from the other host * @return a new <tt>HandshakeResponse</tt> instance with the headers * sent by the other host * @throws <tt>IOException</tt> if the status line could not be parsed */ public static HandshakeResponse createRemoteResponse(String line,Properties headers) throws IOException { int code = extractCode(line); if(code == -1) throw new IOException("could not parse status code: "+line); String message = extractMessage(line); if(message == null) throw new IOException("could not parse status message: "+line); return new HandshakeResponse(code, message, headers); } /** * Creates a new <tt>HandshakeResponse</tt> instance that accepts the * potential connection. * * @param headers the <tt>Properties</tt> instance containing the headers * to send to the node we're accepting */ static HandshakeResponse createAcceptIncomingResponse(HandshakeResponse response, Properties headers) { return new HandshakeResponse(addXTryHeader(response, headers)); } /** * Creates a new <tt>HandshakeResponse</tt> instance that accepts the outgoing connection -- the final third step * in the handshake. This passes no headers, as all necessary headers have already been exchanged. The only possible * exception is the potential inclusion of X-Ultrapeer: false. * * @param headers the <tt>Properties</tt> instance containing the headers to send to the node we're accepting */ static HandshakeResponse createAcceptOutgoingResponse(Properties headers) { return new HandshakeResponse(headers); } /** * Creates a new <tt>HandshakeResponse</tt> instance that responds to a * special crawler connection with connected leaves and Ultrapeers. See the * Files>>Development section on the GDF. * * @param headers the <tt>Properties</tt> instance containing the headers * to send to the node we're rejecting */ static HandshakeResponse createCrawlerResponse() { Properties headers = new Properties(); // add our user agent headers.put(HeaderNames.USER_AGENT, CommonUtils.getHttpServer()); headers.put(HeaderNames.X_ULTRAPEER, ""+RouterService.isSupernode()); // add any leaves List leaves = RouterService.getConnectionManager(). getInitializedClientConnections(); headers.put(HeaderNames.LEAVES, createEndpointString(leaves, leaves.size())); // add any Ultrapeers List ultrapeers = RouterService.getConnectionManager().getInitializedConnections(); headers.put(HeaderNames.PEERS, createEndpointString(ultrapeers, ultrapeers.size())); return new HandshakeResponse(HandshakeResponse.CRAWLER_CODE, HandshakeResponse.CRAWLER_MESSAGE, headers); } /** * Creates a new <tt>HandshakeResponse</tt> instance that rejects the * potential connection. This includes the X-Try-Ultrapeers header to * tell the remote host about other nodes to connect to. We return the * hosts we most recently knew to have free leaf or ultrapeer connection * slots. * * @param hr the <tt>HandshakeResponse</tt> containing the connection * headers of the connecting host * @return a <tt>HandshakeResponse</tt> with the appropriate response * headers */ static HandshakeResponse createUltrapeerRejectIncomingResponse(HandshakeResponse hr) { return new HandshakeResponse(HandshakeResponse.SLOTS_FULL, HandshakeResponse.SLOTS_FULL_MESSAGE, addXTryHeader(hr, new Properties())); } /** * Creates a new <tt>HandshakeResponse</tt> instance that rejects the * potential connection. The returned <tt>HandshakeResponse</tt> DOES * NOT include the X-Try-Ultrapeers header because this is an outgoing * connection, and we should not send host data that the remote client * does not request. * * @param headers the <tt>Properties</tt> instance containing the headers * to send to the node we're rejecting */ static HandshakeResponse createRejectOutgoingResponse() { return new HandshakeResponse(HandshakeResponse.SLOTS_FULL, HandshakeResponse.SLOTS_FULL_MESSAGE, new Properties()); } /** * Creates a new <tt>HandshakeResponse</tt> instance that rejects the * potential connection to a leaf. We add hosts that we know about with * free connection slots to the X-Try-Ultrapeers header. * * @param headers the <tt>Properties</tt> instance containing the headers * to send to the node we're rejecting * @param hr the <tt>HandshakeResponse</tt> containing the headers of the * remote host * @return a new <tt>HandshakeResponse</tt> instance rejecting the * connection and with the specified connection headers */ static HandshakeResponse createLeafRejectIncomingResponse(HandshakeResponse hr) { return new HandshakeResponse(HandshakeResponse.SLOTS_FULL, HandshakeResponse.SHIELDED_MESSAGE, addXTryHeader(hr, new Properties())); } /** * Creates a new <tt>HandshakeResponse</tt> instance that rejects an * outgoing leaf connection. This occurs when we, as a leaf, reject a * connection on the third stage of the handshake. * * @return a new <tt>HandshakeResponse</tt> instance rejecting the * connection and with no extra headers */ static HandshakeResponse createLeafRejectOutgoingResponse() { return new HandshakeResponse(HandshakeResponse.SLOTS_FULL, HandshakeResponse.SHIELDED_MESSAGE); } static HandshakeResponse createLeafRejectLocaleOutgoingResponse() { return new HandshakeResponse(HandshakeResponse.LOCALE_NO_MATCH, HandshakeResponse.LOCALE_NO_MATCH_MESSAGE); } /** * Creates a new String of hosts, limiting the number of hosts to add to * the default value of 10. This is particularly used for the * X-Try-Ultrapeers header. * * @param iter a <tt>Collection</tt> of <tt>IpPort</tt> instances * @return a string of the form IP:port,IP:port,... from the given list of * hosts */ private static String createEndpointString(Collection hosts) { return createEndpointString(hosts, NUM_X_TRY_ULTRAPEER_HOSTS); } /** * Utility method that takes the specified list of hosts and returns a * string of the form:<p> * * IP:port,IP:port,IP:port * * @param iter a <tt>Collection</tt> of <tt>IpPort</tt> instances * @return a string of the form IP:port,IP:port,... from the given list of * hosts */ private static String createEndpointString(Collection hosts, int limit) { StringBuffer sb = new StringBuffer(); int i = 0; Iterator iter = hosts.iterator(); while(iter.hasNext() && i<limit) { IpPort host = (IpPort)iter.next(); sb.append(host.getAddress()); sb.append(":"); sb.append(host.getPort()); if(iter.hasNext()) { sb.append(","); } i++; } return sb.toString(); } /** * Utility method to extract the connection code from the connect string, * such as "200" in a "200 OK" message. * * @param line the full connection string, such as "200 OK." * @return the status code for the connection string, or -1 if the code * could not be parsed */ private static int extractCode(String line) { //get the status code and message out of the status line int statusMessageIndex = line.indexOf(" "); if(statusMessageIndex == -1) return -1; try { return Integer.parseInt(line.substring(0, statusMessageIndex).trim()); } catch(NumberFormatException e) { return -1; } } /** * Utility method to extract the connection message from the connect string, * such as "OK" in a "200 OK" message. * * @param line the full connection string, such as "200 OK." * @return the status message for the connection string */ private static String extractMessage(String line) { //get the status code and message out of the status line int statusMessageIndex = line.indexOf(" "); if(statusMessageIndex == -1) return null; return line.substring(statusMessageIndex).trim(); } /** * Utility method for creating a set of headers with the X-Try-Ultrapeers * header set according to the headers from the remote host. * * @param hr the <tt>HandshakeResponse</tt> of the incoming request * @return a new <tt>Properties</tt> instance with the X-Try-Ultrapeers * header set according to the incoming headers from the remote host */ private static Properties addXTryHeader(HandshakeResponse hr, Properties headers) { Collection hosts = RouterService.getPreferencedHosts(hr.isUltrapeer(), hr.getLocalePref(), 10); headers.put(HeaderNames.X_TRY_ULTRAPEERS, createEndpointString(hosts)); return headers; } /** * Returns the response code. */ public int getStatusCode() { return STATUS_CODE; } /** * Returns the status message. * @return the status message (e.g. "OK" , "Service Not Available" etc.) */ public String getStatusMessage(){ return STATUS_MESSAGE; } /** * Tells if the status returned was OK or not. * @return true, if the status returned was not the OK status, false * otherwise */ public boolean notOKStatusCode(){ if(STATUS_CODE != OK) return true; else return false; } /** * Returns whether or not this connection was accepted -- whether * or not the connection returned Gnutella/0.6 200 OK * * @return <tt>true</tt> if the server returned Gnutella/0.6 200 OK, * otherwise <tt>false</tt> */ public boolean isAccepted() { return STATUS_CODE == OK; } /** * Returns the status code and status message together used in a * status line. (e.g., "200 OK", "503 Service Not Available") */ public String getStatusLine() { return new String(STATUS_CODE + " " + STATUS_MESSAGE); } /** * Returns the headers as a <tt>Properties</tt> instance. */ public Properties props() { return HEADERS; } /** * Accessor for an individual property. */ public String getProperty(String prop) { return HEADERS.getProperty(prop); } /** Returns the vendor string reported by this connection, i.e., * the USER_AGENT property, or null if it wasn't set. * @return the vendor string, or null if unknown */ public String getUserAgent() { return HEADERS.getProperty(HeaderNames.USER_AGENT); } /** * Returns the maximum TTL that queries originating from us and * sent from this connection should have. If the max TTL header is * not present, the default TTL is assumed. * * @return the maximum TTL that queries sent to this connection * should have -- this will always be 5 or less */ public byte getMaxTTL() { return MAX_TTL; } /** * Accessor for the X-Try-Ultrapeers header. If the header does not * exist or is empty, this returns the emtpy string. * * @return the string of X-Try-Ultrapeer hosts, or the empty string * if they do not exist */ public String getXTryUltrapeers() { return extractStringHeaderValue(HEADERS, HeaderNames.X_TRY_ULTRAPEERS); } /** * This is a convenience method to see if the connection passed * the X-Try-Ultrapeer header. This simply checks the existence of the * header -- if the header was sent but is empty, this still returns * <tt>true</tt>. * * @return <tt>true</tt> if this connection sent the X-Try-Ultrapeer * header, otherwise <tt>false</tt> */ public boolean hasXTryUltrapeers() { return headerExists(HEADERS, HeaderNames.X_TRY_ULTRAPEERS); } /** * Returns whether or not this host included leaf guidance, i.e., * whether or not the host wrote: * * X-Ultrapeer-Needed: false * * @return <tt>true</tt> if the other host returned * X-Ultrapeer-Needed: false, otherwise <tt>false</tt> */ public boolean hasLeafGuidance() { return isFalseValue(HEADERS, HeaderNames.X_ULTRAPEER_NEEDED); } /** * Returns the number of intra-Ultrapeer connections this node maintains. * * @return the number of intra-Ultrapeer connections this node maintains */ public int getNumIntraUltrapeerConnections() { return DEGREE; } // implements ReplyHandler interface -- inherit doc comment public boolean isHighDegreeConnection() { return HIGH_DEGREE; } /** * Returns whether or not we think this connection is from a LimeWire * or a derivative of LimeWire */ public boolean isLimeWire() { return IS_LIMEWIRE; } /** * @return true if we consider this an older version of limewire, false * otherwise */ public boolean isOldLimeWire() { return IS_OLD_LIMEWIRE; } /** * Returns whether or not this is connection passed the headers to be * considered a "good" leaf. * * @return <tt>true</tt> if this is considered a "good" leaf, otherwise * <tt>false</tt> */ public boolean isGoodLeaf() { return GOOD_LEAF; } /** * Returns whether or not this connnection is encoded in deflate. */ public boolean isDeflateEnabled() { //this does NOT check the setting because we have already told the //outgoing side we support encoding, and they're expecting us to use it return DEFLATE_ENCODED; } /** * Returns whether or not this connection accepts deflate as an encoding. */ public boolean isDeflateAccepted() { //Note that we check the ENCODE_DEFLATE setting, and NOT the //ACCEPT_DEFLATE setting. This is a trick to prevent the //HandshakeResponders from thinking they can encode //the via deflate if we do not want to encode in deflate. return ConnectionSettings.ENCODE_DEFLATE.getValue() && containsStringValue(HEADERS, // the headers to look through HeaderNames.ACCEPT_ENCODING,// the header to look for HeaderNames.DEFLATE_VALUE); // the value to look for } /** * Returns whether or not this is connection passed the headers to be * considered a "good" ultrapeer. * * @return <tt>true</tt> if this is considered a "good" ultrapeer, otherwise * <tt>false</tt> */ public boolean isGoodUltrapeer() { return GOOD_ULTRAPEER; } /** * Returns whether or not this connection supports query routing * between Ultrapeers at 1 hop. * * @return <tt>true</tt> if this is an Ultrapeer connection that * exchanges query routing tables with other Ultrapeers at 1 hop, * otherwise <tt>false</tt> */ public boolean isUltrapeerQueryRoutingConnection() { return ULTRAPEER_QRP; } /** Returns true iff this connection wrote "X-Ultrapeer: false". * This does NOT necessarily mean the connection is shielded. */ public boolean isLeaf() { return LEAF; } /** Returns true iff this connection wrote "X-Ultrapeer: true". */ public boolean isUltrapeer() { return ULTRAPEER; } /** * Returns whether or not this connection is to a client supporting * GUESS. * * @return <tt>true</tt> if the node on the other end of this * connection supports GUESS, <tt>false</tt> otherwise */ public boolean isGUESSCapable() { return GUESS_CAPABLE; } /** * Returns whether or not this connection is to a ultrapeer supporting * GUESS. * * @return <tt>true</tt> if the node on the other end of this * Ultrapeer connection supports GUESS, <tt>false</tt> otherwise */ public boolean isGUESSUltrapeer() { return isGUESSCapable() && isUltrapeer(); } /** Returns true iff this connection is a temporary connection as per the headers. */ public boolean isTempConnection() { //get the X-Temp-Connection from either the headers received String value=HEADERS.getProperty(HeaderNames.X_TEMP_CONNECTION); //if X-Temp-Connection header is not received, return false, else //return the value received if(value == null) return false; else return Boolean.valueOf(value).booleanValue(); } /** Returns true if this supports GGEP'ed messages. GGEP'ed messages (e.g., * big pongs) should only be sent along connections for which * supportsGGEP()==true. */ public boolean supportsGGEP() { if (_supportsGGEP==null) { String value = HEADERS.getProperty(HeaderNames.GGEP); //Currently we don't care about the version number. _supportsGGEP = new Boolean(value != null); } return _supportsGGEP.booleanValue(); } /** * Determines whether or not this node supports vendor messages. * * @return <tt>true</tt> if this node supports vendor messages, otherwise * <tt>false</tt> */ public float supportsVendorMessages() { String value = HEADERS.getProperty(HeaderNames.X_VENDOR_MESSAGE); if ((value != null) && !value.equals("")) { try { return Float.parseFloat(value); }catch(NumberFormatException nfe) { return 0; } } return 0; } /** * Returns whether or not this node supports pong caching. * * @return <tt>true</tt> if this node supports pong caching, otherwise * <tt>false</tt> */ public boolean supportsPongCaching() { return PONG_CACHING; } public String getVersion() { return HEADERS.getProperty(HeaderNames.X_VERSION); } /** True if the remote host supports query routing (QRP). This is only * meaningful in the context of leaf-supernode relationships. */ public boolean isQueryRoutingEnabled() { return isVersionOrHigher(HEADERS, HeaderNames.X_QUERY_ROUTING, 0.1F); } /** * Returns whether or not the node on the other end of this connection * uses dynamic querying. * * @return <tt>true</tt> if this node uses dynamic querying, otherwise * <tt>false</tt> */ public boolean isDynamicQueryConnection() { return DYNAMIC_QUERY; } /** * Accessor for whether or not this connection supports TTL=1 probe * queries. These queries are treated separately from other queries. * In particular, if a second query with the same GUID is received, * it is not considered a duplicate. * * @return <tt>true</tt> if this connection supports probe queries, * otherwise <tt>false</tt> */ public boolean supportsProbeQueries() { return PROBE_QUERIES; } /** * Determines whether or not this handshake is from the crawler. * * @return <tt>true</tt> if this handshake is from the crawler, otherwise * <tt>false</tt> */ public boolean isCrawler() { return IS_CRAWLER; } /** * access the locale pref. advertised by the client */ public String getLocalePref() { return LOCALE_PREF; } /** * Convenience method that returns whether or not the given header * exists. * * @return <tt>true</tt> if the header exists, otherwise <tt>false</tt> */ private static boolean headerExists(Properties headers, String headerName) { String value = headers.getProperty(headerName); return value != null; } /** * Utility method for checking whether or not a given header * value is true. * * @param headers the headers to check * @param headerName the header name to look for */ private static boolean isTrueValue(Properties headers, String headerName) { String value = headers.getProperty(headerName); if(value == null) return false; return Boolean.valueOf(value).booleanValue(); } /** * Utility method for checking whether or not a given header * value is false. * * @param headers the headers to check * @param headerName the header name to look for */ private static boolean isFalseValue(Properties headers, String headerName) { String value = headers.getProperty(headerName); if(value == null) return false; return value.equalsIgnoreCase("false"); } /** * Utility method for determing whether or not a given header * is a given string value. Case-insensitive. * * @param headers the headers to check * @param headerName the headerName to look for * @param headerValue the headerValue to check against */ private static boolean isStringValue(Properties headers, String headerName, String headerValue) { String value = headers.getProperty(headerName); if(value == null) return false; return value.equalsIgnoreCase(headerValue); } /** * Utility method for determing whether or not a given header * contains a given string value within a comma-delimited list. * Case-insensitive. * * @param headers the headers to check * @param headerName the headerName to look for * @param headerValue the headerValue to check against */ private static boolean containsStringValue(Properties headers, String headerName, String headerValue) { String value = headers.getProperty(headerName); if(value == null) return false; //As a small optimization, we first check to see if the value //by itself is what we want, so we don't have to create the //StringTokenizer. if(value.equalsIgnoreCase(headerValue)) return true; StringTokenizer st = new StringTokenizer(value, ","); while(st.hasMoreTokens()) { if(st.nextToken().equalsIgnoreCase(headerValue)) return true; } return false; } /** * Utility method that checks the headers to see if the advertised * version for a specified feature is greater than or equal to the version * we require (<tt>minVersion</tt>. * * @param headers the connection headers to evaluate * @param headerName the header name for the feature to check * @param minVersion the minimum version that we require for this feature * * @return <tt>true</tt> if the version number for the specified feature * is greater than or equal to <tt>minVersion</tt>, otherwise * <tt>false</tt>. */ private static boolean isVersionOrHigher(Properties headers, String headerName, float minVersion) { String value = headers.getProperty(headerName); if(value == null) return false; try { Float f = new Float(value); return f.floatValue() >= minVersion; } catch (NumberFormatException e) { return false; } } /** * Helper method for returning an int header value. If the header name * is not found, or if the header value cannot be parsed, the default * value is returned. * * @param headers the connection headers to search through * @param headerName the header name to look for * @param defaultValue the default value to return if the header value * could not be properly parsed * @return the int value for the header */ private static int extractIntHeaderValue(Properties headers, String headerName, int defaultValue) { String value = headers.getProperty(headerName); if(value == null) return defaultValue; try { return Integer.valueOf(value).intValue(); } catch(NumberFormatException e) { return defaultValue; } } /** * Helper method for returning a byte header value. If the header name * is not found, or if the header value cannot be parsed, the default * value is returned. * * @param headers the connection headers to search through * @param headerName the header name to look for * @param defaultValue the default value to return if the header value * could not be properly parsed * @return the byte value for the header */ private static byte extractByteHeaderValue(Properties headers, String headerName, byte defaultValue) { String value = headers.getProperty(headerName); if(value == null) return defaultValue; try { return Byte.valueOf(value).byteValue(); } catch(NumberFormatException e) { return defaultValue; } } /** * Helper method for returning a string header value. If the header name * is not found, or if the header value cannot be parsed, the default * value is returned. * * @param headers the connection headers to search through * @param headerName the header name to look for * @return the string value for the header, or the empty string if * the header could not be found */ private static String extractStringHeaderValue(Properties headers, String headerName) { String value = headers.getProperty(headerName); if(value == null) return ""; return value; } public String toString() { return "<"+STATUS_CODE+", "+STATUS_MESSAGE+">"+HEADERS; } }