package org.limewire.facebook.service; import java.util.Arrays; import java.util.concurrent.Callable; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.params.HttpClientParams; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpParams; import org.limewire.concurrent.ExecutorsHelper; import org.limewire.concurrent.ListeningFuture; import org.limewire.concurrent.ThreadPoolListeningExecutor; import org.limewire.facebook.service.livemessage.PresenceHandlerFactory; import org.limewire.facebook.service.settings.FacebookAuthServerUrls; import org.limewire.friend.api.FriendConnection; import org.limewire.friend.api.FriendConnectionConfiguration; import org.limewire.friend.api.FriendConnectionEvent; import org.limewire.friend.api.FriendConnectionFactory; import org.limewire.friend.api.FriendConnectionFactoryRegistry; import org.limewire.friend.api.FriendException; import org.limewire.friend.api.Network; import org.limewire.friend.api.feature.FeatureRegistry; import org.limewire.friend.impl.feature.LimewireFeatureInitializer; import org.limewire.http.httpclient.HttpClientInstanceUtils; import org.limewire.http.httpclient.HttpClientUtils; import org.limewire.inject.EagerSingleton; import org.limewire.lifecycle.Asynchronous; import org.limewire.lifecycle.Service; import org.limewire.lifecycle.ServiceRegistry; import org.limewire.listener.EventListener; import org.limewire.listener.ListenerSupport; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.name.Named; /** * A <code>FriendConnectionFactory</code> for facebook. Facebook communication is done via their API + chat protocol. * The entry point for logging into facebook. Also logs out the <code>FacebookFriendConnection</code> * on limewire shutdown. */ @EagerSingleton class FacebookFriendService implements FriendConnectionFactory, Service { private static Log LOG = LogFactory.getLog(FacebookFriendService.class); private final ThreadPoolListeningExecutor executorService; private final FacebookFriendConnectionFactory connectionFactory; private final PresenceHandlerFactory presenceHandlerFactory; private final FeatureRegistry featureRegistry; private volatile FacebookFriendConnection connection; private final Provider<String[]> authServerUrls; private final HttpClientInstanceUtils httpClientInstanceUtils; private final ClientConnectionManager httpConnectionManager; @Inject FacebookFriendService(FacebookFriendConnectionFactory connectionFactory, PresenceHandlerFactory presenceHandlerFactory, FeatureRegistry featureRegistry, @FacebookAuthServerUrls Provider<String[]> authServerUrls, HttpClientInstanceUtils httpClientInstanceUtils, @Named("sslConnectionManager") ClientConnectionManager httpConnectionManager) { this.connectionFactory = connectionFactory; this.presenceHandlerFactory = presenceHandlerFactory; this.featureRegistry = featureRegistry; this.authServerUrls = authServerUrls; this.httpClientInstanceUtils = httpClientInstanceUtils; this.httpConnectionManager = httpConnectionManager; executorService = ExecutorsHelper.newSingleThreadExecutor(ExecutorsHelper.daemonThreadFactory(getClass().getSimpleName())); } @Inject void register(ServiceRegistry registry) { registry.register(this); } @Inject public void register(ListenerSupport<FriendConnectionEvent> listenerSupport) { listenerSupport.addListener(new EventListener<FriendConnectionEvent>() { @Override public void handleEvent(FriendConnectionEvent event) { switch (event.getType()) { case DISCONNECTED: case CONNECT_FAILED: synchronized (FacebookFriendService.this) { if(connection != null && connection == event.getSource()) { connection = null; } } break; default: } } }); } @Override public void start() { } @Override @Asynchronous public void stop() { logoutImpl(); } @Override public void initialize() { } @Override public String getServiceName() { return getClass().getSimpleName(); } private void logoutImpl() { synchronized (this) { if(connection != null) { connection.logoutImpl(false); connection = null; } } } @Override public ListeningFuture<FriendConnection> login(final FriendConnectionConfiguration configuration) { return executorService.submit(new Callable<FriendConnection>() { @Override public FriendConnection call() throws Exception { return loginImpl((FacebookFriendConnectionConfiguration)configuration); } }); } @Override @Inject public void register(FriendConnectionFactoryRegistry registry) { registry.register(Network.Type.FACEBOOK, this); } FacebookFriendConnection loginImpl(FacebookFriendConnectionConfiguration configuration) throws FriendException { LOG.debug("creating connection"); connection = connectionFactory.create(configuration); presenceHandlerFactory.create(connection); new LimewireFeatureInitializer().register(featureRegistry); LOG.debug("logging in to facebook..."); connection.loginImpl(); LOG.debug("logged in."); return connection; } @Override public ListeningFuture<String> requestLoginUrl(final FriendConnectionConfiguration configuration) { return executorService.submit(new Callable<String>() { @Override public String call() throws Exception { HttpParams params = new BasicHttpParams(); HttpClientParams.setRedirecting(params, false); HttpClient httpClient = new DefaultHttpClient(httpConnectionManager, params); String[] authUrls = authServerUrls.get(); if (LOG.isDebugEnabled()) { LOG.debugf("auth urls to choose from {0}", Arrays.asList(authUrls)); } String authUrl = httpClientInstanceUtils.addClientInfoToUrl(FacebookUtils.getRandomElement(authUrls) + "getlogin/"); LOG.debugf("picked auth url: {0}", authUrl); HttpGet getMethod = new HttpGet(authUrl); HttpResponse response = httpClient.execute(getMethod); int statusCode = response.getStatusLine().getStatusCode(); assert statusCode == 302 : "code: " + statusCode; String url = response.getFirstHeader("Location").getValue(); LOG.debugf("login url: {0}", url); String authToken = parseAuthToken(url); if (authToken != null) { configuration.setAttribute("auth-token", authToken); } HttpClientUtils.releaseConnection(response); return url; } }); } private static String parseAuthToken(String url) { int authTokenIndex = url.indexOf("auth_token="); if(authTokenIndex > -1) { String authTokenPart = url.substring(authTokenIndex + "auth_token=".length()); int nextParamIndex = authTokenPart.indexOf('&'); if (nextParamIndex > -1) { return authTokenPart.substring(0, nextParamIndex); } else { // last param return it all return authTokenPart; } } LOG.debugf("could not parse out auth token: {0}", url); return null; } }