package com.limegroup.gnutella.downloader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.protocol.HTTP;
import org.limewire.http.httpclient.LimeHttpClient;
import org.limewire.io.Address;
import org.limewire.io.Connectable;
import org.limewire.io.ConnectableImpl;
import org.limewire.io.InvalidDataException;
import org.limewire.io.IpPort;
import org.limewire.io.IpPortImpl;
import org.limewire.net.address.AddressFactory;
import org.limewire.util.URIUtils;
import org.xml.sax.SAXException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.limegroup.gnutella.PushEndpoint;
import com.limegroup.gnutella.PushEndpointFactory;
import com.limegroup.gnutella.RemoteFileDesc;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.UrnSet;
import com.limegroup.gnutella.altlocs.AlternateLocation;
import com.limegroup.gnutella.browser.MagnetOptions;
import com.limegroup.gnutella.downloader.serial.RemoteHostMemento;
import com.limegroup.gnutella.util.LimeWireUtils;
import com.limegroup.gnutella.xml.LimeXMLDocument;
import com.limegroup.gnutella.xml.LimeXMLDocumentFactory;
import com.limegroup.gnutella.xml.SchemaNotFoundException;
@Singleton
class RemoteFileDescFactoryImpl implements RemoteFileDescFactory {
private static final int COPY_INDEX = Integer.MAX_VALUE;
private final LimeXMLDocumentFactory limeXMLDocumentFactory;
private final PushEndpointFactory pushEndpointFactory;
private final Provider<LimeHttpClient> httpClientProvider;
private final AddressFactory addressFactory;
private final ConcurrentMap<String, RemoteFileDescDeserializer> deserializers = new ConcurrentHashMap<String, RemoteFileDescDeserializer>();
private final List<RemoteFileDescCreator> creators = new CopyOnWriteArrayList<RemoteFileDescCreator>();
@Inject
public RemoteFileDescFactoryImpl(LimeXMLDocumentFactory limeXMLDocumentFactory,
PushEndpointFactory pushEndpointFactory, Provider<LimeHttpClient> httpClientProvider,
AddressFactory addressFactory) {
this.limeXMLDocumentFactory = limeXMLDocumentFactory;
this.pushEndpointFactory = pushEndpointFactory;
this.httpClientProvider = httpClientProvider;
this.addressFactory = addressFactory;
}
public RemoteFileDesc createRemoteFileDesc(RemoteFileDesc rfd, IpPort ep) {
return createRemoteFileDesc(ep.getAddress(), // host
ep.getPort(), // port
COPY_INDEX, // index (unknown)
rfd.getFileName(), // filename
rfd.getSize(), // filesize
rfd.getClientGUID(), // client GUID
0, // speed
false, // chat capable
2, // quality
false, // browse hostable
rfd.getXMLDocument(), // xml doc
rfd.getUrns(), // urns
false, // reply to MCast
false, // is firewalled
AlternateLocation.ALT_VENDOR, // vendor
IpPort.EMPTY_SET, // push proxies
rfd.getCreationTime(), // creation time
0, // firewalled transfer
null, // no PE cause not firewalled
ep instanceof Connectable ? ((Connectable) ep).isTLSCapable() : false // TLS
// capable
// if
// ep
// is.
);
}
public RemoteFileDesc createRemoteFileDesc(RemoteFileDesc rfd, PushEndpoint pe) {
return createRemoteFileDesc(pe,
COPY_INDEX, // index (unknown)
rfd.getFileName(), // filename
rfd.getSize(), // filesize
pe.getClientGUID(),
rfd.getSpeed(), // speed
rfd.getQuality(), // quality
false, // browse hostable
rfd.getXMLDocument(), // xml doc
rfd.getUrns(), // urns
false, // reply to MCast
AlternateLocation.ALT_VENDOR, // vendor
rfd.getCreationTime()); // creation time
}
public RemoteFileDesc createRemoteFileDesc(String host, int port, long index, String filename,
long size, byte[] clientGUID, int speed, boolean chat, int quality, boolean browseHost,
LimeXMLDocument xmlDoc, Set<? extends URN> urns, boolean replyToMulticast,
boolean firewalled, String vendor, Set<? extends IpPort> proxies, long createTime,
int FWTVersion, boolean tlsCapable) {
return createRemoteFileDesc(host, port, index, filename, size, clientGUID, speed, chat,
quality, browseHost, xmlDoc, urns, replyToMulticast, firewalled, vendor, proxies,
createTime, FWTVersion, null, // this will create a PE to
// house the data if the host is
// firewalled
tlsCapable);
}
public RemoteFileDesc createRemoteFileDesc(String host, int port, long index, String filename,
long size, int speed, boolean chat, int quality, boolean browseHost,
LimeXMLDocument xmlDoc, Set<? extends URN> urns, boolean replyToMulticast,
boolean firewalled, String vendor, long createTime, PushEndpoint pe) {
return createRemoteFileDesc(host, port, index, filename, size, null, speed, chat, quality,
browseHost, xmlDoc, urns, replyToMulticast, firewalled, vendor, null, createTime,
0, pe, false); // use exising pe
}
@Override
public RemoteFileDesc createRemoteFileDesc(Address address, long index, String filename,
long size, byte[] clientGUID, int speed, int quality, boolean browseHost, LimeXMLDocument xmlDoc,
Set<? extends URN> urns, boolean replyToMulticast, String vendor,
long createTime) {
return createRemoteFileDesc(address, index, filename, size, clientGUID, speed, quality, browseHost, xmlDoc, urns, replyToMulticast, vendor, createTime, !urns.isEmpty());
}
private RemoteFileDesc createRemoteFileDesc(String host, int port, long index, String filename,
long size, byte[] clientGUID, int speed, boolean chat, int quality, boolean browseHost,
LimeXMLDocument xmlDoc, Set<? extends URN> urns, boolean replyToMulticast,
boolean firewalled, String vendor, Set<? extends IpPort> proxies, long createTime,
int FWTVersion, PushEndpoint pe, boolean tlsCapable) {
Address address = null;
if (firewalled) {
if (pe == null) {
// Don't allow the bogus_ip in here.
IpPort ipp;
if(!host.equals(RemoteFileDesc.BOGUS_IP)) {
try {
ipp = new IpPortImpl(host, port);
} catch(UnknownHostException uhe) {
throw new IllegalArgumentException(uhe);
}
} else {
ipp = null;
FWTVersion = 0;
}
address = pushEndpointFactory.createPushEndpoint(clientGUID, proxies,
PushEndpoint.PLAIN, FWTVersion, ipp);
}
} else {
assert pe == null;
try {
address = new ConnectableImpl(host, port, tlsCapable);
} catch (UnknownHostException e) {
throw new IllegalArgumentException("invalid host: " + host);
}
}
if (urns == null)
urns = Collections.emptySet();
boolean http11 = !urns.isEmpty();
return new RemoteFileDescImpl(address, index, filename, size, clientGUID, speed, quality,
browseHost, xmlDoc, urns, replyToMulticast, vendor, createTime,
http11, addressFactory);
}
public RemoteFileDesc createUrlRemoteFileDesc(Address address, String filename,
long size, Set<? extends URN> urns, URL url) {
RemoteFileDesc rfd = new UrlRemoteFileDescImpl(address, filename, size, urns, url, addressFactory);
assert !rfd.isHTTP11();
return rfd;
}
public RemoteFileDesc createUrlRemoteFileDesc(URL url, String filename, URN urn, long size)
throws IOException, URISyntaxException, HttpException, InterruptedException {
// Use the URL class to do a little parsing for us.
int port = url.getPort();
if (port < 0)
port = 80; // assume default for HTTP (not 6346)
Set<URN> urns = new UrnSet();
if (urn != null)
urns.add(urn);
URI uri = URIUtils.toURI(url.toExternalForm());
return createUrlRemoteFileDesc(new ConnectableImpl(url.getHost(), port, false), filename != null ? filename
: MagnetOptions.extractFileName(uri), size <= 0 ? contentLength(uri) : size, urns,
url);
}
private long contentLength(URI uri) throws HttpException, IOException, InterruptedException {
HttpHead head = new HttpHead(uri);
head.addHeader("User-Agent", LimeWireUtils.getHttpServer());
HttpResponse response = null;
LimeHttpClient client = httpClientProvider.get();
try {
response = client.execute(head);
// Extract Content-length, but only if the response was 200 OK.
// Generally speaking any 2xx response is ok, but in this situation
// we expect only 200.
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
throw new IOException("Got " + response.getStatusLine().getStatusCode()
+ " instead of 200 for URL: " + uri);
// Head requests are not going to have an entity, so we cannot
// get the content length by looking at the entity.
// Instead, we have to parse the header.
Header contentLength = response.getFirstHeader(HTTP.CONTENT_LEN);
if(contentLength != null) {
try {
long len = Long.parseLong(contentLength.getValue());
if(len < 0) {
throw new IOException("Invalid length: " + len);
} else {
return len;
}
} catch(NumberFormatException nfe) {
throw new IOException("can't parse content length", nfe);
}
} else {
throw new IOException("no content length header");
}
} finally {
client.releaseConnection(response);
}
}
public RemoteFileDesc createFromMemento(RemoteHostMemento remoteHostMemento)
throws InvalidDataException {
try {
RemoteFileDescDeserializer deserializer = deserializers.get(remoteHostMemento.getType());
if (deserializer != null) {
return deserializer.createRemoteFileDesc(remoteHostMemento.getAddress(addressFactory, pushEndpointFactory), remoteHostMemento.getIndex(),
remoteHostMemento.getFileName(),
remoteHostMemento.getSize(), remoteHostMemento.getClientGuid(),
remoteHostMemento.getSpeed(), remoteHostMemento
.getQuality(), xml(remoteHostMemento.getXml()),
remoteHostMemento.getUrns(), remoteHostMemento.getVendor(),
-1L);
}
if (remoteHostMemento.getCustomUrl() != null) {
return createUrlRemoteFileDesc(remoteHostMemento.getAddress(addressFactory, pushEndpointFactory),
remoteHostMemento.getFileName(), remoteHostMemento
.getSize(), remoteHostMemento.getUrns(), remoteHostMemento
.getCustomUrl());
} else {
return createRemoteFileDesc(remoteHostMemento.getAddress(addressFactory, pushEndpointFactory), remoteHostMemento.getIndex(),
remoteHostMemento.getFileName(),
remoteHostMemento.getSize(), remoteHostMemento.getClientGuid(),
remoteHostMemento.getSpeed(), remoteHostMemento
.getQuality(), remoteHostMemento.isBrowseHost(), xml(remoteHostMemento.getXml()),
remoteHostMemento.getUrns(), remoteHostMemento.isReplyToMulticast(),
remoteHostMemento.getVendor(),
-1L);
}
} catch (SAXException e) {
throw new InvalidDataException(e);
} catch (SchemaNotFoundException e) {
throw new InvalidDataException(e);
} catch (IOException e) {
throw new InvalidDataException(e);
}
}
private LimeXMLDocument xml(String xml) throws SAXException, SchemaNotFoundException,
IOException {
if (xml != null)
return limeXMLDocumentFactory.createLimeXMLDocument(xml);
else
return null;
}
@Override
public RemoteFileDesc createRemoteFileDesc(Address address, long index, String filename,
long size, byte[] clientGUID, int speed, int quality, boolean browseHost, LimeXMLDocument xmlDoc,
Set<? extends URN> urns, boolean replyToMulticast, String vendor,
long createTime, boolean http1) {
for (RemoteFileDescCreator creator : creators) {
if (creator.canCreateFor(address)) {
return creator.create(address, index, filename, size, clientGUID, speed, quality, browseHost, xmlDoc, urns, replyToMulticast, vendor, createTime, http1);
}
}
return new RemoteFileDescImpl(address, index, filename, size, clientGUID, speed, quality, browseHost,
xmlDoc, urns, replyToMulticast, vendor, createTime, http1, addressFactory);
}
@Override
public void register(String type, RemoteFileDescDeserializer remoteFileDescDeserializer) {
RemoteFileDescDeserializer other = deserializers.putIfAbsent(type, remoteFileDescDeserializer);
assert other == null : "two deserializers registered for: " + type;
}
public void register(RemoteFileDescCreator creator) {
creators.add(creator);
}
}