package com.limegroup.gnutella.browser; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; import java.util.ArrayList; import java.util.Arrays; import java.util.StringTokenizer; import java.util.Collection; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.limegroup.gnutella.ActivityCallback; import com.limegroup.gnutella.ByteReader; import com.limegroup.gnutella.ConnectionAcceptor; import com.limegroup.gnutella.Constants; import com.limegroup.gnutella.ErrorService; import com.limegroup.gnutella.MessageService; import com.limegroup.gnutella.RouterService; import com.limegroup.gnutella.SaveLocationException; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.settings.ConnectionSettings; import com.limegroup.gnutella.statistics.HTTPStat; import com.limegroup.gnutella.util.CommonUtils; import com.limegroup.gnutella.util.NetworkUtils; import com.limegroup.gnutella.util.Sockets; public class ExternalControl implements ConnectionAcceptor { private static final Log LOG = LogFactory.getLog(ExternalControl.class); private static final String LOCALHOST = "127.0.0.1"; private static final String HTTP = "http://"; private static boolean initialized = false; private static String enqueuedRequest = null; public static String preprocessArgs(String args[]) { LOG.trace("enter proprocessArgs"); StringBuffer arg = new StringBuffer(); for (int i = 0; i < args.length; i++) { arg.append(args[i]); } return arg.toString(); } private static final ExternalControl INSTANCE = new ExternalControl(); private ExternalControl() { RouterService.getConnectionDispatcher(). addConnectionAcceptor(this, new String[]{"MAGNET"}, true, true); } public void acceptConnection(String word, Socket sock) { HTTPStat.MAGNET_REQUESTS.incrementStat(); fireMagnet(sock); } /** * Uses the magnet infrastructure to check if LimeWire is running. * If it is, it is restored and this instance exits. * Note that the already-running LimeWire is not checked * for 'allow multiple instances' -- only the instance that was just * started. */ public static void checkForActiveLimeWire() { if( testForLimeWire(null) ) { System.exit(0); } } public static void checkForActiveLimeWire(String arg) { if( CommonUtils.isWindows() && testForLimeWire(arg) ) { System.exit(0); } } public static boolean isInitialized() { return initialized; } public static void enqueueMagnetRequest(String arg) { LOG.trace("enter enqueueMagnetRequest"); enqueuedRequest = arg; } public static void runQueuedMagnetRequest() { initialized = true; if ( enqueuedRequest != null ) { String request = enqueuedRequest; enqueuedRequest = null; handleMagnetRequest(request); } } //refactored the download logic into a separate method public static void handleMagnetRequest(String arg) { LOG.trace("enter handleMagnetRequest"); ActivityCallback callback = RouterService.getCallback(); // No reason to make sure connections are active. We don't even know // at this point if the magnet requires a search. // if ( RouterService.getNumInitializedConnections() <= 0 ) // RouterService.connect(); callback.restoreApplication(); callback.showDownloads(); MagnetOptions options[] = MagnetOptions.parseMagnet(arg); if (options.length == 0) { if(LOG.isWarnEnabled()) LOG.warn("Invalid magnet, ignoring: " + arg); return; } // ask callback if it wants to handle the magnets itself if (!callback.handleMagnets(options)) { downloadMagnet(options); } } /** * performs the actual magnet download. This way it is possible to * parse and download the magnet separately (which is what I intend to do in the gui) --zab * @param options the magnet options returned from parseMagnet */ public static void downloadMagnet(MagnetOptions[] options) { if(LOG.isDebugEnabled()) { for(int i = 0; i < options.length; i++) { LOG.debug("Kicking off downloader for option " + i + " " + options[i]); } } for ( int i = 0; i < options.length; i++ ) { MagnetOptions curOpt = options[i]; if (LOG.isDebugEnabled()) { URN urn = curOpt.getSHA1Urn(); LOG.debug("Processing magnet with params:\n" + "urn [" + urn + "]\n" + "options [" + curOpt + "]"); } String msg = curOpt.getErrorMessage(); // Validate that we have something to go with from magnet // If not, report an error. if (!curOpt.isDownloadable()) { if(LOG.isWarnEnabled()) { LOG.warn("Invalid magnet: " + curOpt); } msg = msg != null ? msg : curOpt.toString(); MessageService.showError("ERROR_BAD_MAGNET_LINK", msg); return; } // Warn the user that the link was slightly invalid if( msg != null ) MessageService.showError("ERROR_INVALID_URLS_IN_MAGNET"); try { RouterService.download(curOpt, false); } catch ( IllegalArgumentException il ) { ErrorService.error(il); } catch (SaveLocationException sle) { if (sle.getErrorCode() == SaveLocationException.FILE_ALREADY_EXISTS) { MessageService.showError( "ERROR_ALREADY_EXISTS", sle.getFile().getName()); } else if (sle.getErrorCode() == SaveLocationException.FILE_ALREADY_DOWNLOADING) { MessageService.showError( "ERROR_ALREADY_DOWNLOADING", sle.getFile().getName()); } } } } /** * Handle a Magnet request via a socket (for TCP handling). * Deiconify the application, fire MAGNET request * and return true as a sign that LimeWire is running. */ public static void fireMagnet(Socket socket) { LOG.trace("enter fireMagnet"); Thread.currentThread().setName("IncomingMagnetThread"); try { // Only allow control from localhost if (!NetworkUtils.isLocalHost(socket)) { if(LOG.isWarnEnabled()) LOG.warn("Invalid magnet request from: " + socket.getInetAddress().getHostAddress()); return; } // First read extra parameter socket.setSoTimeout(Constants.TIMEOUT); ByteReader br = new ByteReader(socket.getInputStream()); // read the first line. if null, throw an exception String line = br.readLine(); socket.setSoTimeout(0); BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream()); String s = CommonUtils.getUserName() + "\r\n"; byte[] bytes=s.getBytes(); out.write(bytes); out.flush(); handleMagnetRequest(line); } catch (IOException e) { LOG.warn("Exception while responding to magnet request", e); } finally { try { socket.close(); } catch (IOException e) { } } } /** Check if the client is already running, and if so, pop it up. * Sends the MAGNET message along the given socket. * @returns true if a local LimeWire responded with a true. */ private static boolean testForLimeWire(String arg) { Socket socket = null; int port = ConnectionSettings.PORT.getValue(); // Check to see if the port is valid. // If it is not, revert it to the default value. // This has the side effect of possibly allowing two // LimeWires to start if somehow the existing one // set its port to 0, but that should not happen // in normal program flow. if( !NetworkUtils.isValidPort(port) ) { ConnectionSettings.PORT.revertToDefault(); port = ConnectionSettings.PORT.getValue(); } try { socket = Sockets.connect(LOCALHOST, port, 500); InputStream istream = socket.getInputStream(); socket.setSoTimeout(500); ByteReader byteReader = new ByteReader(istream); OutputStream os = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(os); BufferedWriter out = new BufferedWriter(osw); out.write("MAGNET "+arg+" "); out.write("\r\n"); out.flush(); String str = byteReader.readLine(); return(str != null && str.startsWith(CommonUtils.getUserName())); } catch (IOException e2) { } finally { if(socket != null) { try { socket.close(); } catch (IOException e) { // nothing we can do } } } return false; } /** * Allows multiline parsing of magnet links. * @param magnets * @return array may be empty, but is never <code>null</code> */ public static MagnetOptions[] parseMagnets(String magnets) { ArrayList list = new ArrayList(); StringTokenizer tokens = new StringTokenizer (magnets, System.getProperty("line.separator")); while (tokens.hasMoreTokens()) { String next = tokens.nextToken(); MagnetOptions[] options = MagnetOptions.parseMagnet(next); if (options.length > 0) { list.addAll(Arrays.asList(options)); } } return (MagnetOptions[])list.toArray(new MagnetOptions[0]); } }