package com.limegroup.gnutella.uploader; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.limewire.collection.BitNumbers; import org.limewire.io.Connectable; import org.limewire.io.IpPort; import org.limewire.io.NetworkInstanceUtils; import org.limewire.io.NetworkUtils; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import com.limegroup.gnutella.ConnectionManager; import com.limegroup.gnutella.NetworkManager; import com.limegroup.gnutella.PushEndpoint; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.altlocs.AltLocManager; import com.limegroup.gnutella.altlocs.DirectAltLoc; import com.limegroup.gnutella.altlocs.PushAltLoc; import com.limegroup.gnutella.http.AltLocTracker; import com.limegroup.gnutella.http.FeaturesWriter; import com.limegroup.gnutella.http.HTTPHeaderName; import com.limegroup.gnutella.http.HTTPHeaderValue; import com.limegroup.gnutella.http.HTTPHeaderValueCollection; import com.limegroup.gnutella.http.HTTPUtils; import com.limegroup.gnutella.library.FileDesc; import com.limegroup.gnutella.library.IncompleteFileDesc; /** * Provides methods to add commonly used headers to {@link HttpResponse}s. */ @Singleton public class HTTPHeaderUtils { private final NetworkManager networkManager; private final FeaturesWriter featuresWriter; private final Provider<ConnectionManager> connectionManager; private final NetworkInstanceUtils networkInstanceUtils; @Inject public HTTPHeaderUtils(FeaturesWriter featuresWriter, NetworkManager networkManager, Provider<ConnectionManager> connectionManager, NetworkInstanceUtils networkInstanceUtils) { this.networkManager = networkManager; this.featuresWriter = featuresWriter; this.connectionManager = connectionManager; this.networkInstanceUtils = networkInstanceUtils; } /** * Adds the <code>X-Available-Ranges</code> header to * <code>response</code> if available. */ public void addRangeHeader(HttpResponse response, HTTPUploader uploader, FileDesc fd) { if (fd instanceof IncompleteFileDesc) { URN sha1 = uploader.getFileDesc().getSHA1Urn(); if (sha1 != null) { IncompleteFileDesc ifd = (IncompleteFileDesc) fd; response.addHeader(HTTPHeaderName.AVAILABLE_RANGES.create(ifd)); } } } /** * Encodes a collection of push proxies as an HTTP value string. Checks if the * push proxies implement {@link Connectable} and encodes their TLS capabilities. * * @param proxies collection of push proxies * @param separator separator between individual proxy entries * @param max the maximum number of push proxies to encode, the first <code>max</code> * push proxies will be encoded * * @throws IllegalArgumentException if collection is empty */ public static String encodePushProxies(Collection<? extends IpPort> proxies, String separator, int max) { if (proxies.isEmpty()) { throw new IllegalArgumentException("Can't encode empty set of proxies"); } StringBuilder buf = new StringBuilder(); int proxiesWritten = 0; BitNumbers bn = getTLSIndices(proxies, max); for(IpPort current : proxies) { if(proxiesWritten >= max) break; buf.append(current.getAddress()) .append(":") .append(current.getPort()) .append(separator); proxiesWritten++; } if(!bn.isEmpty()) buf.insert(0, PushEndpoint.PPTLS_HTTP + "=" + bn.toHexString() + separator); buf.deleteCharAt(buf.length() - 1); return buf.toString(); } /** * Decodes an http value string into a set of push proxies. * * @param httpValue the http value to be decoded * @param separator the separator that was used for encoding it * * @return an empty set if no pushproxies could not be decoded */ public Set<Connectable> decodePushProxies(String httpValue, String separator) { Set<Connectable> newSet = new HashSet<Connectable>(); StringTokenizer tok = new StringTokenizer(httpValue, separator); BitNumbers tlsProxies = null; while(tok.hasMoreTokens()) { String proxy = tok.nextToken().trim(); // only read features when we haven't read proxies yet. if(newSet.size() == 0 && proxy.startsWith(PushEndpoint.PPTLS_HTTP)) { try { String value = HTTPUtils.parseValue(proxy); if(value != null) { try { tlsProxies = new BitNumbers(value); } catch(IllegalArgumentException ignored) {} } } catch(IOException invalid) {} continue; } boolean tlsCapable = tlsProxies != null && tlsProxies.isSet(newSet.size()); try { Connectable ipp = NetworkUtils.parseIpPort(proxy, tlsCapable); if(!networkInstanceUtils.isPrivateAddress(ipp.getInetAddress())) newSet.add(ipp); } catch(IOException ohWell){ tlsProxies = null; // unset, since index may be off. } } return newSet; } /** * Returns a bit numbers object that encodes the indices of * of {@link IpPort}s in <code>ipPorts</code> that support TLS. */ public static BitNumbers getTLSIndices(Collection<? extends IpPort> ipPorts) { return getTLSIndices(ipPorts, ipPorts.size()); } /** * Returns a bit numbers object that encodes the indices of * of {@link IpPort}s in <code>ipPorts</code> that support TLS. * * @param max stop encoding indices after max elements have been seen */ public static BitNumbers getTLSIndices(Collection<? extends IpPort> ipPorts, int max) { BitNumbers bn = new BitNumbers(max); int i = 0; for (IpPort ipp : ipPorts) { if (i >= max) break; if (ipp instanceof Connectable && ((Connectable) ipp).isTLSCapable()) bn.set(i); i++; } return bn; } /** * Encodes string of push proxies. * * @return null when host is not firewalled. */ private String encodePushProxies() { if (networkManager.acceptedIncomingConnection()) return null; Set<? extends Connectable> proxies = connectionManager.get().getPushProxies(); return !proxies.isEmpty() ? encodePushProxies(proxies, ",", PushEndpoint.MAX_PROXIES) : null; } /** * Returns the http headers used for firewalled transfers, include * push proxies and port for fw-fw transfers. * * @return empty list if this host is not firewalled and there is no * need for push proxies. */ public List<Header> getFirewalledHeaders() { String proxies = encodePushProxies(); if (proxies != null) { Header proxiesHeader = HTTPHeaderName.PROXIES.create(proxies); // write out X-FWPORT if we support firewalled transfers, so the other side gets our port // for future fw-fw transfers if (networkManager.canDoFWT()) { return Arrays.asList(proxiesHeader, HTTPHeaderName.FWTPORT.create(networkManager.getStableUDPPort() + "")); } else { return Arrays.asList(proxiesHeader); } } return Collections.emptyList(); } /** * Writes out the X-Push-Proxies header as specified by section 4.2 of the * Push Proxy proposal, v. 0.7 */ public void addProxyHeader(HttpResponse response) { for (Header header : getFirewalledHeaders()) { response.addHeader(header); } } /** * Adds alternate locations for <code>fd</code> to <code>response</code> * if available. */ public void addAltLocationsHeader(HttpResponse response, AltLocTracker altLocTracker, AltLocManager altLocManager) { response.addHeader(HTTPHeaderName.GNUTELLA_CONTENT_URN.create(altLocTracker.getUrn())); Collection<DirectAltLoc> direct = altLocTracker.getNextSetOfAltsToSend(altLocManager); if (direct.size() > 0) { List<HTTPHeaderValue> ordered = new ArrayList<HTTPHeaderValue>( direct.size()); final BitNumbers bn = new BitNumbers(direct.size()); for (DirectAltLoc al : direct) { IpPort ipp = al.getHost(); if (ipp instanceof Connectable && ((Connectable) ipp).isTLSCapable()) bn.set(ordered.size()); ordered.add(al); } if (!bn.isEmpty()) { ordered.add(0, new HTTPHeaderValue() { public String httpStringValue() { return DirectAltLoc.TLS_IDX + bn.toHexString(); } }); } response.addHeader(HTTPHeaderName.ALT_LOCATION .create(new HTTPHeaderValueCollection(ordered))); } if (altLocTracker.wantsFAlts()) { Collection<PushAltLoc> pushes = altLocTracker.getNextSetOfPushAltsToSend(altLocManager); if (pushes.size() > 0) { response.addHeader(HTTPHeaderName.FALT_LOCATION .create(new HTTPHeaderValueCollection(pushes))); } } } // public void addAltLocationsHeaders(HttpResponse response, HTTPUploader uploader, URN urn) { // response.addHeader(HTTPHeaderName.GNUTELLA_CONTENT_URN.create(urn)); // Collection<? extends AlternateLocation> alts = uploader.getAltLocTracker().getNextSetOfAltsToSend(); // if(alts.size() > 0) { // response.addHeader(HTTPHeaderName.ALT_LOCATION.create(new HTTPHeaderValueCollection(alts))); // } // // if (uploader.getAltLocTracker().wantsFAlts) { // alts = getNextSetOfPushAltsToSend(); // if (alts.size() > 0) { // response.addHeader(HTTPHeaderName.FALT_LOCATION.create(new HTTPHeaderValueCollection(alts))); // } // } // } /** * Adds an <code>X-Features</code> header to <code>response</code>. */ public void addFeatures(HttpResponse response) { Set<HTTPHeaderValue> features = featuresWriter.getFeaturesValue(); if (features.size() > 0) { response.addHeader(HTTPHeaderName.FEATURES.create( new HTTPHeaderValueCollection(features))); } } }