package freenet.clients.http;
import java.io.IOException;
import java.net.URI;
import java.util.Comparator;
import java.util.HashMap;
import freenet.client.HighLevelSimpleClient;
import freenet.l10n.NodeL10n;
import freenet.node.DarknetPeerNode;
import freenet.node.DarknetPeerNode.FRIEND_TRUST;
import freenet.node.DarknetPeerNode.FRIEND_VISIBILITY;
import freenet.node.DarknetPeerNodeStatus;
import freenet.node.Node;
import freenet.node.NodeClientCore;
import freenet.node.PeerManager;
import freenet.node.PeerNodeStatus;
import freenet.support.HTMLNode;
import freenet.support.Logger;
import freenet.support.MultiValueTable;
import freenet.support.SimpleFieldSet;
import freenet.support.api.HTTPRequest;
import freenet.support.io.FileUtil;
public class DarknetConnectionsToadlet extends ConnectionsToadlet {
DarknetConnectionsToadlet(Node n, NodeClientCore core, HighLevelSimpleClient client) {
super(n, core, client);
}
private static String l10n(String string) {
return NodeL10n.getBase().getString("DarknetConnectionsToadlet."+string);
}
protected class DarknetComparator extends ComparatorByStatus {
DarknetComparator(String sortBy, boolean reversed) {
super(sortBy, reversed);
}
@Override
protected int customCompare(PeerNodeStatus firstNode, PeerNodeStatus secondNode, String sortBy) {
if(sortBy.equals("name")) {
return ((DarknetPeerNodeStatus)firstNode).getName().compareToIgnoreCase(((DarknetPeerNodeStatus)secondNode).getName());
}else if(sortBy.equals("privnote")){
return ((DarknetPeerNodeStatus)firstNode).getPrivateDarknetCommentNote().compareToIgnoreCase(((DarknetPeerNodeStatus)secondNode).getPrivateDarknetCommentNote());
} else if(sortBy.equals("trust")){
return ((DarknetPeerNodeStatus)firstNode).getTrustLevel().compareTo(((DarknetPeerNodeStatus)secondNode).getTrustLevel());
} else if(sortBy.equals("visibility")){
int ret = ((DarknetPeerNodeStatus)firstNode).getOurVisibility().compareTo(((DarknetPeerNodeStatus)secondNode).getOurVisibility());
if(ret != 0) return ret;
return ((DarknetPeerNodeStatus)firstNode).getTheirVisibility().compareTo(((DarknetPeerNodeStatus)secondNode).getTheirVisibility());
} else
return super.customCompare(firstNode, secondNode, sortBy);
}
/** Default comparison, after taking into account status */
@Override
protected int lastResortCompare(PeerNodeStatus firstNode, PeerNodeStatus secondNode) {
return ((DarknetPeerNodeStatus)firstNode).getName().compareToIgnoreCase(((DarknetPeerNodeStatus)secondNode).getName());
}
}
@Override
protected Comparator<PeerNodeStatus> comparator(String sortBy, boolean reversed) {
return new DarknetComparator(sortBy, reversed);
}
@Override
protected boolean hasNameColumn() {
return true;
}
@Override
protected void drawNameColumn(HTMLNode peerRow, PeerNodeStatus peerNodeStatus, boolean advanced) {
// name column
HTMLNode cell = peerRow.addChild("td", "class", "peer-name");
cell.addChild("a", "href", "/send_n2ntm/?peernode_hashcode=" + peerNodeStatus.hashCode(), ((DarknetPeerNodeStatus)peerNodeStatus).getName());
if(advanced && peerNodeStatus.hasFullNoderef) {
cell.addChild("#", " (");
cell.addChild("a", "href", path()+"friend-"+peerNodeStatus.hashCode()+".fref", l10n("noderefLink"));
cell.addChild("#", ")");
}
}
@Override
protected boolean hasTrustColumn() {
return true;
}
@Override
protected void drawTrustColumn(HTMLNode peerRow, PeerNodeStatus peerNodeStatus) {
peerRow.addChild("td", "class", "peer-trust").addChild("#", ((DarknetPeerNodeStatus)peerNodeStatus).getTrustLevel().name());
}
@Override
protected boolean hasVisibilityColumn() {
return true;
}
@Override
protected void drawVisibilityColumn(HTMLNode peerRow, PeerNodeStatus peerNodeStatus, boolean advancedModeEnabled) {
String content = ((DarknetPeerNodeStatus)peerNodeStatus).getOurVisibility().name();
if(advancedModeEnabled)
content += " ("+((DarknetPeerNodeStatus)peerNodeStatus).getTheirVisibility().name()+")";
peerRow.addChild("td", "class", "peer-trust").addChild("#", content);
}
@Override
protected boolean hasPrivateNoteColumn() {
return true;
}
@Override
protected void drawPrivateNoteColumn(HTMLNode peerRow, PeerNodeStatus peerNodeStatus, boolean fProxyJavascriptEnabled) {
// private darknet node comment note column
DarknetPeerNodeStatus status = (DarknetPeerNodeStatus) peerNodeStatus;
if(fProxyJavascriptEnabled) {
peerRow.addChild("td", "class", "peer-private-darknet-comment-note").addChild("input", new String[] { "type", "name", "size", "maxlength", "onBlur", "onChange", "value" }, new String[] { "text", "peerPrivateNote_" + peerNodeStatus.hashCode(), "16", "250", "peerNoteBlur();", "peerNoteChange();", status.getPrivateDarknetCommentNote() });
} else {
peerRow.addChild("td", "class", "peer-private-darknet-comment-note").addChild("input", new String[] { "type", "name", "size", "maxlength", "value" }, new String[] { "text", "peerPrivateNote_" + peerNodeStatus.hashCode(), "16", "250", status.getPrivateDarknetCommentNote() });
}
}
@Override
protected SimpleFieldSet getNoderef() {
return node.exportDarknetPublicFieldSet();
}
@Override
protected PeerNodeStatus[] getPeerNodeStatuses(boolean noHeavy) {
return node.peers.getDarknetPeerNodeStatuses(noHeavy);
}
@Override
protected String getPageTitle(String titleCountString) {
return NodeL10n.getBase().getString("DarknetConnectionsToadlet.fullTitle",
new String[] {"counts"}, new String[] {titleCountString} );
}
@Override
protected boolean shouldDrawNoderefBox(boolean advancedModeEnabled) {
return advancedModeEnabled; // Convenient for advanced users, but normally we will use the "Add a friend" box.
}
@Override
protected boolean showPeerActionsBox() {
return true;
}
@Override
protected void drawPeerActionSelectBox(HTMLNode peerForm, boolean advancedModeEnabled) {
HTMLNode actionSelect = peerForm.addChild("select", new String[] { "id", "name" }, new String[] { "action", "action" });
actionSelect.addChild("option", "value", "", l10n("selectAction"));
actionSelect.addChild("option", "value", "send_n2ntm", l10n("sendMessageToPeers"));
actionSelect.addChild("option", "value", "update_notes", l10n("updateChangedPrivnotes"));
if(advancedModeEnabled) {
actionSelect.addChild("option", "value", "enable", l10n("peersEnable"));
actionSelect.addChild("option", "value", "disable", l10n("peersDisable"));
actionSelect.addChild("option", "value", "set_burst_only", l10n("peersSetBurstOnly"));
actionSelect.addChild("option", "value", "clear_burst_only", l10n("peersClearBurstOnly"));
actionSelect.addChild("option", "value", "set_listen_only", l10n("peersSetListenOnly"));
actionSelect.addChild("option", "value", "clear_listen_only", l10n("peersClearListenOnly"));
actionSelect.addChild("option", "value", "set_allow_local", l10n("peersSetAllowLocal"));
actionSelect.addChild("option", "value", "clear_allow_local", l10n("peersClearAllowLocal"));
actionSelect.addChild("option", "value", "set_ignore_source_port", l10n("peersSetIgnoreSourcePort"));
actionSelect.addChild("option", "value", "clear_ignore_source_port", l10n("peersClearIgnoreSourcePort"));
actionSelect.addChild("option", "value", "set_dont_route", l10n("peersSetDontRoute"));
actionSelect.addChild("option", "value", "clear_dont_route", l10n("peersClearDontRoute"));
}
actionSelect.addChild("option", "value", "", l10n("separator"));
actionSelect.addChild("option", "value", "remove", l10n("removePeers"));
peerForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "doAction", l10n("go") });
peerForm.addChild("br");
peerForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "doChangeTrust", l10n("changeTrustButton") });
HTMLNode changeTrustLevelSelect = peerForm.addChild("select", new String[] { "id", "name" }, new String[] { "changeTrust", "changeTrust" });
for(FRIEND_TRUST trust : FRIEND_TRUST.valuesBackwards()) {
changeTrustLevelSelect.addChild("option", "value", trust.name(), l10n("peerTrust."+trust.name()));
}
peerForm.addChild("br");
peerForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "doChangeVisibility", l10n("changeVisibilityButton") });
HTMLNode changeVisibilitySelect = peerForm.addChild("select", new String[] { "id", "name" }, new String[] { "changeVisibility", "changeVisibility" });
for(FRIEND_VISIBILITY trust : FRIEND_VISIBILITY.values()) {
changeVisibilitySelect.addChild("option", "value", trust.name(), l10n("peerVisibility."+trust.name()));
}
}
@Override
protected String getPeerListTitle() {
return l10n("myFriends");
}
@Override
protected boolean acceptRefPosts() {
return true;
}
@Override
protected String defaultRedirectLocation() {
return "/friends/"; // FIXME
}
/**
* Implement other post actions than adding nodes.
* @throws IOException
* @throws ToadletContextClosedException
* @throws RedirectException
*/
@Override
protected void handleAltPost(URI uri, HTTPRequest request, ToadletContext ctx, boolean logMINOR) throws ToadletContextClosedException, IOException, RedirectException {
if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("send_n2ntm")) {
PageNode page = ctx.getPageMaker().getPageNode(l10n("sendMessageTitle"), ctx);
HTMLNode pageNode = page.outer;
HTMLNode contentNode = page.content;
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
HashMap<String, String> peers = new HashMap<String, String>();
for(DarknetPeerNode pn : peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
String peer_name = pn.getName();
String peer_hash = String.valueOf(pn.hashCode());
if(!peers.containsKey(peer_hash)) {
peers.put(peer_hash, peer_name);
}
}
}
N2NTMToadlet.createN2NTMSendForm( pageNode, ctx.isAdvancedModeEnabled(), contentNode, ctx, peers);
writeHTMLReply(ctx, 200, "OK", pageNode.generate());
return;
} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("update_notes")) {
//int hashcode = Integer.decode(request.getParam("node")).intValue();
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("peerPrivateNote_"+pn.hashCode())) {
if(!request.getPartAsStringFailsafe("peerPrivateNote_"+pn.hashCode(),250).equals(pn.getPrivateDarknetCommentNote())) {
pn.setPrivateDarknetCommentNote(request.getPartAsStringFailsafe("peerPrivateNote_"+pn.hashCode(),250));
}
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("enable")) {
//int hashcode = Integer.decode(request.getParam("node")).intValue();
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
pn.enablePeer();
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("disable")) {
//int hashcode = Integer.decode(request.getParam("node")).intValue();
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
pn.disablePeer();
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("set_burst_only")) {
//int hashcode = Integer.decode(request.getParam("node")).intValue();
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
pn.setBurstOnly(true);
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("clear_burst_only")) {
//int hashcode = Integer.decode(request.getParam("node")).intValue();
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
pn.setBurstOnly(false);
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("set_ignore_source_port")) {
//int hashcode = Integer.decode(request.getParam("node")).intValue();
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
pn.setIgnoreSourcePort(true);
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("clear_ignore_source_port")) {
//int hashcode = Integer.decode(request.getParam("node")).intValue();
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
pn.setIgnoreSourcePort(false);
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("clear_dont_route")) {
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
pn.setRoutingStatus(true, true);
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("set_dont_route")) {
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if(request.isPartSet("node_" + pn.hashCode())) {
pn.setRoutingStatus(false, true);
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("set_listen_only")) {
//int hashcode = Integer.decode(request.getParam("node")).intValue();
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
pn.setListenOnly(true);
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("clear_listen_only")) {
//int hashcode = Integer.decode(request.getParam("node")).intValue();
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
pn.setListenOnly(false);
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("set_allow_local")) {
//int hashcode = Integer.decode(request.getParam("node")).intValue();
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
pn.setAllowLocalAddresses(true);
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("clear_allow_local")) {
//int hashcode = Integer.decode(request.getParam("node")).intValue();
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
pn.setAllowLocalAddresses(false);
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("changeTrust") && request.isPartSet("doChangeTrust")) {
FRIEND_TRUST trust = FRIEND_TRUST.valueOf(request.getPartAsStringFailsafe("changeTrust", 10));
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
pn.setTrustLevel(trust);
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("changeVisibility") && request.isPartSet("doChangeVisibility")) {
FRIEND_VISIBILITY trust = FRIEND_VISIBILITY.valueOf(request.getPartAsStringFailsafe("changeVisibility", 10));
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
pn.setVisibility(trust);
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("remove") || (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("remove"))) {
if(logMINOR) Logger.minor(this, "Remove node");
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
if((pn.timeLastConnectionCompleted() < (System.currentTimeMillis() - 1000*60*60*24*7) /* one week */) || (pn.peerNodeStatus == PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED) || request.isPartSet("forceit")){
this.node.removePeerConnection(pn);
if(logMINOR) Logger.minor(this, "Removed node: node_"+pn.hashCode());
}else{
if(logMINOR) Logger.minor(this, "Refusing to remove : node_"+pn.hashCode()+" (trying to prevent network churn) : let's display the warning message.");
PageNode page = ctx.getPageMaker().getPageNode(l10n("confirmRemoveNodeTitle"), ctx);
HTMLNode pageNode = page.outer;
HTMLNode contentNode = page.content;
HTMLNode content =ctx.getPageMaker().getInfobox("infobox-warning", l10n("confirmRemoveNodeWarningTitle"), contentNode, "darknet-remove-node", true);
content.addChild("p").addChild("#",
NodeL10n.getBase().getString("DarknetConnectionsToadlet.confirmRemoveNode", new String[] { "name" }, new String[] { pn.getName() }));
HTMLNode removeForm = ctx.addFormChild(content, "/friends/", "removeConfirmForm");
removeForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "node_"+pn.hashCode(), "remove" });
removeForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "cancel", NodeL10n.getBase().getString("Toadlet.cancel") });
removeForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "remove", l10n("remove") });
removeForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "forceit", l10n("forceRemove") });
writeHTMLReply(ctx, 200, "OK", pageNode.generate());
return; // FIXME: maybe it breaks multi-node removing
}
} else {
if(logMINOR) Logger.minor(this, "Part not set: node_"+pn.hashCode());
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("acceptTransfer")) {
// FIXME this is ugly, should probably move both this code and the PeerNode code somewhere.
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
long id = Long.parseLong(request.getPartAsStringFailsafe("id", 32)); // FIXME handle NumberFormatException
pn.acceptTransfer(id);
break;
}
}
redirectHere(ctx);
return;
} else if (request.isPartSet("rejectTransfer")) {
// FIXME this is ugly, should probably move both this code and the PeerNode code somewhere.
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
if (request.isPartSet("node_"+pn.hashCode())) {
long id = Long.parseLong(request.getPartAsStringFailsafe("id", 32)); // FIXME handle NumberFormatException
pn.rejectTransfer(id);
break;
}
}
redirectHere(ctx);
return;
} else {
this.handleMethodGET(uri, new HTTPRequestImpl(uri, "GET"), ctx);
}
}
private void redirectHere(ToadletContext ctx) throws ToadletContextClosedException, IOException {
MultiValueTable<String, String> headers = new MultiValueTable<String, String>();
headers.put("Location", "/friends/");
ctx.sendReplyHeaders(302, "Found", headers, null, 0);
}
@Override
protected boolean isOpennet() {
return false;
}
@Override
SimpleColumn[] endColumnHeaders(boolean advancedMode) {
return null;
}
@Override
public String path() {
return "/friends/";
}
@Override
public void handleMethodGET(URI uri, HTTPRequest request, ToadletContext ctx) throws ToadletContextClosedException, IOException, RedirectException {
if(tryHandlePeerNoderef(uri, request, ctx)) return;
super.handleMethodGET(uri, request, ctx);
}
private boolean tryHandlePeerNoderef(URI uri, HTTPRequest request,
ToadletContext ctx) throws ToadletContextClosedException, IOException {
String path = uri.getPath();
if(path.endsWith(".fref") && path.startsWith(path()+"friend-")) {
// Get noderef for a peer
String input_hashcode_string = path.substring((path()+"friend-").length());
input_hashcode_string = input_hashcode_string.substring(0, input_hashcode_string.length() - ".fref".length());
int input_hashcode;
try {
input_hashcode = Integer.parseInt(input_hashcode_string);
} catch (NumberFormatException e) {
// ignore here, handle below
return false;
}
String peernode_name = null;
SimpleFieldSet fs = null;
if (input_hashcode != -1) {
DarknetPeerNode[] peerNodes = node.getDarknetConnections();
for(DarknetPeerNode pn: peerNodes) {
int peer_hashcode = pn.hashCode();
if (peer_hashcode == input_hashcode) {
peernode_name = pn.getName();
fs = pn.getFullNoderef();
break;
}
}
}
if(fs == null) return false;
String filename = FileUtil.sanitizeFileNameWithExtras(peernode_name+".fref", "\" ");
String content = fs.toString();
MultiValueTable<String, String> extraHeaders = new MultiValueTable<String, String>();
// Force download to disk
extraHeaders.put("Content-Disposition", "attachment; filename="+filename);
this.writeReply(ctx, 200, "application/x-freenet-reference", "OK", extraHeaders, content);
return true;
} else return false;
}
@Override
public void handleMethodPOST(URI uri, HTTPRequest request, ToadletContext ctx) throws ToadletContextClosedException, IOException, RedirectException {
super.handleMethodPOST(uri, request, ctx);
}
}