package edu.washington.cs.oneswarm.ui.gwt.server.community;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamSource;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.aelitis.azureus.core.networkmanager.impl.osssl.OneSwarmSslKeyManager;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import edu.uw.cse.netlab.utils.ByteManip;
import edu.washington.cs.oneswarm.community.CommunityConstants;
import edu.washington.cs.oneswarm.ui.gwt.client.newui.friends.wizard.FriendsImportCommunityServer;
import edu.washington.cs.oneswarm.ui.gwt.rpc.CommunityRecord;
import edu.washington.cs.oneswarm.ui.gwt.rpc.FriendInfoLite;
import edu.washington.cs.oneswarm.ui.gwt.rpc.FriendList;
import edu.washington.cs.oneswarm.ui.gwt.server.BackendTaskManager;
import edu.washington.cs.oneswarm.ui.gwt.server.FriendInfoLiteFactory;
/**
* Publishes our local public key to the given community server and processes
* the list of keys in the response.
*/
public class KeyPublishOp extends CommunityServerOperation {
String refreshInterval;
private final String base64Key;
private final boolean polling_refresh;
private final String mOurNickname;
public KeyPublishOp(CommunityRecord inRecord, boolean polling_refresh) {
super(inRecord);
this.polling_refresh = polling_refresh;
mOurNickname = COConfigurationManager.getStringParameter("Computer Name", "OneSwarm user");
base64Key = Base64.encode(
OneSwarmSslKeyManager.getInstance().getOwnPublicKey().getEncoded()).replaceAll(
"\n", "");
}
private static Logger logger = Logger.getLogger(KeyPublishOp.class.getName());
@Override
void doOp() {
/**
* First, check capabilities once per server per execution.
*/
check_server_capabilities();
/**
* Three step: 1) get challenge, 2) send reponse, 3) parse list
*
* Special case is when the challenge returns a not authorized -- in
* this case, try a registration request. If this is successful, it's a
* public community server and we can reissue a challenge. Otherwise,
* it's private and we don't have credentials.
*/
try {
String sep = "?";
if (mRecord.getUrl().indexOf('?') > -1) {
sep = "&";
}
String theURLString = mRecord.getUrl() + sep + CommunityConstants.BASE64_PUBLIC_KEY
+ "=" + URLEncoder.encode(base64Key, "UTF-8");
logger.info("Requesting community friend update: " + theURLString);
URL url = new URL(theURLString);
HttpURLConnection conn = getConnection(url);
if (cancelled) {
return;
}
if (mTask != null) {
mTask.setSummary("Connecting...");
}
if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
logger.fine("Initial request unauthorized to: " + mRecord.getUrl());
if (attempt_registration() == false) {
logger.finest("Registration request unauthorized to: " + mRecord.getUrl());
throw new IOException("Unauthorized request denied (registration failed)");
} else {
logger.fine("Registration request appeared to succeed, continuing...");
conn = getConnection(url);
}
}
if (cancelled) {
return;
}
if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
throw new IOException("Unauthorized request denied (registration failed)");
}
logger.finest("Successful connection to: " + mRecord.getUrl());
if (mTask != null) {
mTask.setSummary("Connected.");
}
BufferedReader in = new BufferedReader(new InputStreamReader(
getConnectionInputStream(conn)));
String l = in.readLine();
if (l != null) {
logger.finest("[" + mRecord.getUrl() + "] got: " + l);
}
if (l.startsWith(CommunityConstants.CHALLENGE)) {
String[] toks = l.split("\\s+");
if (toks.length != 2) {
throw new IOException("Received a malformed challenge");
}
long challenge = Long.parseLong(toks[1]);
logger.finest("[" + mRecord.getUrl() + "] got challenge: " + challenge);
reissueWithResponse(challenge);
} else {
ByteArrayOutputStream read = new ByteArrayOutputStream();
read.write(l.getBytes());
mTask.setSummary("Reading response...");
readLimitedInto(conn, MAX_READ_BYTES, read);
processAsXML(read);
}
if (mTask != null) {
if (!polling_refresh) {
logger.finest("[" + mRecord.getUrl() + "] non-polling refresh done");
// TODO: do something with these results
BackendTaskManager.get().removeTask(mTaskID);
}
}
} catch (Exception e) {
e.printStackTrace();
logger.warning(e.toString());
if (mTask != null) {
mTask.setGood(false);
mTask.setSummary(e.toString());
}
} finally {
// someone should pick up these results in a few seconds of polling.
// since we can't
// rely on the browser to remove things, we need a timeout for these
// results here
if (mTask != null) {
try {
Thread.sleep(15 * 1000); // for now, 7 seconds
} catch (Exception e2) {
}
logger.fine("Removing community server task " + mTaskID + " from manager");
BackendTaskManager.get().removeTask(mTask.getTaskID());
}
}
}
private boolean attempt_registration() {
try {
if (mTask != null) {
mTask.setSummary("Attempting registration...");
}
Map<String, String> requestHeaders = new HashMap<String, String>();
Map<String, String> formParams = new HashMap<String, String>();
formParams.put(CommunityConstants.BASE64_PUBLIC_KEY, base64Key);
formParams.put(CommunityConstants.NICKNAME, mOurNickname);
URL url = new URL(mRecord.getUrl());
HttpURLConnection conn = getConnection(url, "POST");
for (String head : requestHeaders.keySet()) {
conn.setRequestProperty(head, requestHeaders.get(head));
}
// add url form parameters
OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
Iterator<String> params = formParams.keySet().iterator();
while (params.hasNext()) {
String name = params.next();
String value = formParams.get(name);
out.append(URLEncoder.encode(name, "UTF-8") + "="
+ URLEncoder.encode(value, "UTF-8"));
if (params.hasNext()) {
out.append("&");
}
}
out.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(
getConnectionInputStream(conn)));
String line = null;
while ((line = in.readLine()) != null) {
// System.out.println("resp line: " + line);
;
}
in.close();
logger.fine("final status code: " + conn.getResponseCode() + " / "
+ conn.getResponseMessage());
return conn.getResponseCode() == HttpURLConnection.HTTP_OK;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private void processAsXML(ByteArrayOutputStream read) {
ByteArrayInputStream input = new ByteArrayInputStream(read.toByteArray());
try {
TransformerFactory factory = TransformerFactory.newInstance();
Transformer xformer = factory.newTransformer();
Source source = new StreamSource(input);
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = builder.newDocument();
Result result = new DOMResult(doc);
xformer.transform(source, result);
NodeList root = doc.getElementsByTagName(CommunityConstants.RESPONSE_ROOT);
Node response = root.item(0);
NodeList firstLevel = response.getChildNodes();
for (int i = 0; i < firstLevel.getLength(); i++) {
Node kid = firstLevel.item(i);
if (kid.getLocalName().equals(CommunityConstants.REFRESH_INTERVAL)) {
refreshInterval = kid.getTextContent();
logger.finer("got refresh interval: " + refreshInterval);
} else if (kid.getLocalName().equals(CommunityConstants.FRIEND_LIST)) {
// these will appear as friend notifications
List<String[]> parsed = parseFriendList(kid);
if (polling_refresh == false) {
logger.finer("automatic refresh, adding to community manager");
// for( String [] entry : parsed ) {
CommunityServerManager.get().feed(parsed, mRecord);
// }
} else { // these will be requested explicitly by the client
// in the backend task
logger.finer("polling refresh, adding to backend task");
FriendInfoLite[] res = new FriendInfoLite[parsed.size()];
for (int fItr = 0; fItr < res.length; fItr++) {
res[fItr] = FriendInfoLiteFactory.createFromKeyAndNick(
parsed.get(fItr)[0], parsed.get(fItr)[1],
FriendsImportCommunityServer.FRIEND_NETWORK_COMMUNITY_NAME
+ " " + mRecord.getUrl());
res[fItr].setGroup(mRecord.getGroup());
}
FriendList out = new FriendList();
out.setFriendList(CommunityServerManager.get().filter(res));
mTask.setResult(out);
mTask.setGood(true);
mTask.setProgress("100");
mTask.setSummary("Success");
/**
* Should also add this community server to our list if
* it isn't already there.
*/
addOrUpdateCommunityServerToSettings(mRecord);
}
}
}
} catch (ParserConfigurationException e) {
// couldn't even create an empty doc
logger.warning("Exception during XML processing: " + e.toString());
} catch (TransformerException e) {
logger.warning("Exception during XML processing: " + e.toString());
} catch (NullPointerException e) {
// basically means the file had bad structure
e.printStackTrace();
logger.warning("Null pointer exception while processing community server response");
}
}
private void reissueWithResponse(long challenge) {
try {
byte[] encrypted_response = null;
synchronized (OneSwarmSslKeyManager.getInstance()) {
encrypted_response = OneSwarmSslKeyManager.getInstance().sign(
ByteManip.ltob(challenge + 1));
}
if (cancelled) {
return;
}
if (mTask != null) {
mTask.setSummary("Challenge/response...");
}
String sep = "?";
if (mRecord.getUrl().indexOf('?') > -1) {
sep = "&";
}
String urlStr = mRecord.getUrl() + sep + CommunityConstants.BASE64_PUBLIC_KEY + "="
+ URLEncoder.encode(base64Key, "UTF-8") + "&"
+ CommunityConstants.CHALLENGE_RESPONSE + "="
+ URLEncoder.encode(Base64.encode(encrypted_response), "UTF-8");
// System.out.println("url str: " + urlStr);
URL url = new URL(urlStr);
HttpURLConnection conn = getConnection(url);
if (cancelled) {
return;
}
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
mTask.setSummary("Reading response...");
readLimitedInto(conn, MAX_READ_BYTES, bytes);
processAsXML(bytes);
} catch (Exception e) {
e.printStackTrace();
}
}
public String getRefreshInterval() {
return refreshInterval;
}
private List<String[]> parseFriendList(Node kid) {
List<String[]> out = new ArrayList<String[]>();
for (int i = 0; i < kid.getChildNodes().getLength(); i++) {
Node entry = kid.getChildNodes().item(i);
String key = entry.getAttributes().getNamedItem(CommunityConstants.KEY_ATTRIB)
.getTextContent();
String nick = entry.getAttributes().getNamedItem(CommunityConstants.NICK_ATTRIB)
.getTextContent();
logger.finest("parsed: " + key + " / " + nick);
out.add(new String[] { key, nick });
}
return out;
}
}