package org.limewire.net;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.StringTokenizer;
import org.limewire.io.IP;
import org.limewire.io.NetworkUtils;
import org.limewire.io.IOUtils;
import org.limewire.util.StringUtils;
/**
* Represents a WHOIS request. The request value will be
* sent to the appropriate whois server, and the response
* will be parsed for name:value pairs.
* <p>
* Some whois servers merely report the authoritative
* server for a given domain, and we support ONE level of
* forwarding here.
*
*/
public class WhoIsRequestImpl implements WhoIsRequest {
/**
* Map of whois servers keyed on the tld and "0" (zero)
* for IP addresses.
*/
protected Map<String,String> servers;
/**
* The exact query value.
*/
protected String name;
/**
* All parsable values in the reply for this request.
*/
protected Map<String,String> values;
/**
* Name of the whois server that was used to fulfill
* this request (assuming that the request has already
* been fulfilled. Null until then.
*/
protected String whoisServer;
/**
* To prevent us from getting caught in any annoying
* infinite loops, use this value to make sure that
* we only follow a whois referral once.
*/
protected Set<String> referrals;
/**
* The socket manager, as you may have guessed.
*/
protected SocketsManager socketsManager;
/**
* Creates a new request object with a request name.
* It is this request name that will be handed off to
* the whois server and the request can be either for
* a DNS name or an IP address.
*
* @param name request name (ie, "apple.com" or
* "17.149.160.49").
*/
public WhoIsRequestImpl(String name, SocketsManager socketsManager, Map<String,String> defaultServers) {
if (name == null)
throw new NullPointerException();
if (name.length() == 0)
throw new IllegalArgumentException("Zero-length name is not allowed.");
// if there are no periods in the request name,
// then it is definitely not a valid DNS name nor
// an IP address.
//
// if the period is at the end of the request name,
// then it is similarly invalid.
//
{
int ndx = name.lastIndexOf(".");
if (ndx == -1 || ndx == name.length() - 1)
throw new IllegalArgumentException("Invalid request name.");
}
this.name = name;
this.values = new HashMap<String,String>();
this.socketsManager = socketsManager;
this.referrals = new HashSet<String>();
this.servers = defaultServers;
}
/**
* Connects to the whois server, sends the request and
* then reads and parses the reply. Parsed values are
* accessible via getValue().
* <p>
* In most circumstances, this should return in well
* under one second, but with sockets there is no such
* guarantee. Don't block in the UI on a call to this.
*
*/
public void doRequest() throws IOException {
// only parse and find a whois server if there is
// not already one defined.
//
// if this is a second call to doRequest(), based
// on following a referral from a previous request,
// then the whois server will have already been
// defined for us.
//
if (this.whoisServer == null)
this.whoisServer = this.getWhoIsServer();
// no server, no luck
if (this.whoisServer == null)
throw new IllegalArgumentException("Unsupported request name, '" + this.name + "'");
// store this whois server so that when following
// referrals, we do not ever enter a loop.
//
this.referrals.add(this.whoisServer);
// there really isn't a standard whois format,
// other than to say that there is a rough
// adherence to a "name:value" format, where the
// value portion may be on the same line or the
// next line, or it may be multiple lines.
//
// you might say that the value extends until
// the start of the next "name:value" pair.
//
// we will store all names in upper-case.
//
// 1. if a value starts on the same line as its
// corresponding name, then the value is also
// wholly contained on that one line.
//
// 2. names are always followed by colons.
//
// 3. names are sometimes followed by colon-space.
//
// 4. arbitrary whitespace before, after and
// between names and values must be supported.
//
// 5. if a name is re-used, only the last value is
// retained.
//
{
StringTokenizer st = new StringTokenizer(doRequestGetWhoIsResponse(), "\r\n");
String name = null;
StringBuilder value = new StringBuilder(100);
boolean skipTillRegistrant = false;
// we're splitting on new-lines only.
while (st.hasMoreTokens()) {
int index = 0;
String line = st.nextToken().trim();
// some text is often included with the
// whois result, and we want to skip that
// text. the start of final portion of the
// data we want to parse appears to always
// be "Registrant:". look for that point to
// continue parsing, if we find that we are
// in a block of text that we don't care
// about.
//
if (skipTillRegistrant) {
if (-1 == line.indexOf("Registrant:"))
continue;
}
else if (-1 != line.indexOf(">>>") || -1 != line.indexOf("NOTICE:") || -1 != line.indexOf("TERMS OF USE:")) {
skipTillRegistrant = true;
continue;
}
else if (line.startsWith("#")) {
continue;
}
// if we are already parsing a value....
if (name != null) {
// if this is the start of a new name:value pair....
if (-1 != (index = line.indexOf(":"))) {
this.setValue(name.toUpperCase(Locale.ENGLISH), value.toString());
name = line.substring(0, index);
value = new StringBuilder(100);
// if this is a one-line name:value pair...
if ((line = line.substring(index+1).trim()).length() > 0) {
if (line.startsWith("//")) {
name = null;
continue;
}
this.setValue(name.toUpperCase(Locale.ENGLISH), line);
name = null;
}
}
// if this is a continuation of a value....
else if (value.length() != 0) {
value.append(" ");
value.append(line);
}
else {
value.append(line);
}
}
// if this is the start of a new name:value pair....
else if (-1 != (index = line.indexOf(":"))) {
name = line.substring(0, index);
// if this is a one-line name:value pair...
if ((line = line.substring(index+1).trim()).length() > 0) {
if (line.startsWith("//")) {
name = null;
continue;
}
this.setValue(name.toUpperCase(Locale.ENGLISH), line);
name = null;
}
}
// this is garbage text in the reply that we don't care about....
else {
}
}
if (this.name != null)
this.setValue(this.name.toUpperCase(Locale.ENGLISH), value.toString());
}
// if this whois response contains the key "WHOIS
// SERVER", then we should refer to the target
// server specified there. only do this once,
// however.
//
// don't follow the referral if the server we are
// presently talking to is the server mentioned in
// "WHOIS SERVER".
//
{
String referral = this.values.get("WHOIS SERVER");
if (referral != null && referral.length() != 0 &&
!this.referrals.contains(referral)) {
this.whoisServer = referral;
this.doRequest();
}
}
}
/**
* Get an arbitrary value by name.
*
* @param name the name of the value that you want.
* @return the value associated with the given name.
*/
public String getValue(String name) {
return values.get(name);
}
/**
* Set a value for the given name.
*
* @param name key name for reply value
* @param value value to store for the key
*/
protected void setValue(String name, String value) {
values.put(name, value);
}
/**
* Returns the net range of the ip address, or null if
* there is none.
*/
public IP getNetRange () {
String range = this.getValue("CIDR");
if (range != null)
return new IP(range);
return null;
}
/**
* Contacts the whois server, sends the request, reads
* and returns the reply.
*
* @return Verbatim reply from the whois server.
* @throws IOException
*/
protected String doRequestGetWhoIsResponse() throws IOException {
StringBuilder sb = new StringBuilder(1000);
Socket socket = null;
// connect, send the request, read the reply, close
// the socket.
//
try {
socket = socketsManager.connect(new InetSocketAddress(this.whoisServer, 43), 0);
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
byte[] bytes = new byte[1000];
int read = 0;
os.write(StringUtils.toAsciiBytes(this.name+"\r\n"));
// read until we hit EOF or the socket closes.
try {
while (0 < (read = is.read(bytes, 0, 1000))) {
if (read > 0)
sb.append(StringUtils.getASCIIString(bytes, 0, read));
}
}
catch (IOException e2) {
// this can occur when the socket closes.
}
}
catch (IOException e) {
throw e;
}
finally {
IOUtils.close(socket);
}
return sb.toString();
}
/**
* Returns the appropriate whois server based on the
* server list for the request name.
*
* @return Appropriate whois server host name.
*/
public String getWhoIsServer() {
String server = null;
if (NetworkUtils.isDottedIPV4(this.name))
server = this.servers.get("0");
else
server = this.servers.get(this.name.substring(this.name.lastIndexOf(".")+1).toLowerCase(Locale.ENGLISH));
return server;
}
}