Author: toad
Date: 2007-06-09 17:12:22 +0000 (Sat, 09 Jun 2007)
New Revision: 13498
Modified:
trunk/freenet/src/freenet/clients/http/DarknetConnectionsToadlet.java
trunk/freenet/src/freenet/clients/http/SimpleToadletServer.java
trunk/freenet/src/freenet/clients/http/ToadletContainer.java
trunk/freenet/src/freenet/clients/http/ToadletContextImpl.java
trunk/freenet/src/freenet/io/comm/RetrievalException.java
trunk/freenet/src/freenet/io/xfer/PartiallyReceivedBulk.java
trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties
trunk/freenet/src/freenet/node/Node.java
trunk/freenet/src/freenet/node/PeerNode.java
trunk/freenet/src/freenet/support/io/RandomAccessFileWrapper.java
trunk/freenet/src/freenet/support/io/RandomAccessThing.java
Log:
Fix F2F file transfer to not be a DoS risk: Ask the user whether they want the
file, let them either accept or reject it (new support for rejection).
Also fix the incoming related messages being persisted as N2NTMs - they
shouldn't be. In future we may persist the transfers themselves, but not today.
Modified: trunk/freenet/src/freenet/clients/http/DarknetConnectionsToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/DarknetConnectionsToadlet.java
2007-06-08 18:42:22 UTC (rev 13497)
+++ trunk/freenet/src/freenet/clients/http/DarknetConnectionsToadlet.java
2007-06-09 17:12:22 UTC (rev 13498)
@@ -919,6 +919,34 @@
headers.put("Location", "/friends/");
ctx.sendReplyHeaders(302, "Found", headers, null, 0);
return;
+ } else if (request.isPartSet("acceptTransfer")) {
+ // FIXME this is ugly, should probably move both this
code and the PeerNode code somewhere.
+ PeerNode[] peerNodes = node.getDarknetConnections();
+ for(int i = 0; i < peerNodes.length; i++) {
+ if
(request.isPartSet("node_"+peerNodes[i].hashCode())) {
+ long id =
Long.parseLong(request.getPartAsString("id", 32)); // FIXME handle
NumberFormatException
+ peerNodes[i].acceptTransfer(id);
+ break;
+ }
+ }
+ MultiValueTable headers = new MultiValueTable();
+ headers.put("Location", "/friends/");
+ ctx.sendReplyHeaders(302, "Found", headers, null, 0);
+ return;
+ } else if (request.isPartSet("rejectTransfer")) {
+ // FIXME this is ugly, should probably move both this
code and the PeerNode code somewhere.
+ PeerNode[] peerNodes = node.getDarknetConnections();
+ for(int i = 0; i < peerNodes.length; i++) {
+ if
(request.isPartSet("node_"+peerNodes[i].hashCode())) {
+ long id =
Long.parseLong(request.getPartAsString("id", 32)); // FIXME handle
NumberFormatException
+ peerNodes[i].rejectTransfer(id);
+ break;
+ }
+ }
+ MultiValueTable headers = new MultiValueTable();
+ headers.put("Location", "/friends/");
+ ctx.sendReplyHeaders(302, "Found", headers, null, 0);
+ return;
} else {
this.handleGet(uri, new HTTPRequestImpl(uri), ctx);
}
Modified: trunk/freenet/src/freenet/clients/http/SimpleToadletServer.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/SimpleToadletServer.java
2007-06-08 18:42:22 UTC (rev 13497)
+++ trunk/freenet/src/freenet/clients/http/SimpleToadletServer.java
2007-06-09 17:12:22 UTC (rev 13498)
@@ -29,6 +29,7 @@
import freenet.io.NetworkInterface;
import freenet.l10n.L10n;
import freenet.node.NodeClientCore;
+import freenet.support.HTMLNode;
import freenet.support.Logger;
import freenet.support.OOMHandler;
import freenet.support.StringArray;
@@ -419,7 +420,7 @@
}
void start() {
- Thread t = new Thread(this,
"SimpleToadletServer$SocketHandler");
+ Thread t = new Thread(this,
"SimpleToadletServer$SocketHandler@"+hashCode());
t.setDaemon(true);
t.start();
}
@@ -482,4 +483,14 @@
return L10n.getString("SimpleToadletServer."+key);
}
+ public HTMLNode addFormChild(HTMLNode parentNode, String target, String
name) {
+ HTMLNode formNode =
+ parentNode.addChild("form", new String[] { "action",
"method", "enctype", "id", "name", "accept-charset" },
+ new String[] { target, "post",
"multipart/form-data", name, name, "utf-8"} );
+ formNode.addChild("input", new String[] { "type", "name",
"value" },
+ new String[] { "hidden", "formPassword",
getFormPassword() });
+
+ return formNode;
+ }
+
}
Modified: trunk/freenet/src/freenet/clients/http/ToadletContainer.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/ToadletContainer.java
2007-06-08 18:42:22 UTC (rev 13497)
+++ trunk/freenet/src/freenet/clients/http/ToadletContainer.java
2007-06-09 17:12:22 UTC (rev 13498)
@@ -6,6 +6,8 @@
import java.net.InetAddress;
import java.net.URI;
+import freenet.support.HTMLNode;
+
/** Interface for toadlet containers. Toadlets should register here. */
public interface ToadletContainer {
@@ -36,4 +38,6 @@
/** Whether to tell spiders to go away */
public boolean doRobots();
+
+ public HTMLNode addFormChild(HTMLNode parentNode, String target, String
name);
}
Modified: trunk/freenet/src/freenet/clients/http/ToadletContextImpl.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/ToadletContextImpl.java
2007-06-08 18:42:22 UTC (rev 13497)
+++ trunk/freenet/src/freenet/clients/http/ToadletContextImpl.java
2007-06-09 17:12:22 UTC (rev 13498)
@@ -372,6 +372,8 @@
} catch (ToadletContextClosedException e) {
Logger.error(ToadletContextImpl.class,
"ToadletContextClosedException while handling connection!");
return;
+ } catch (Throwable t) {
+ Logger.error(ToadletContextImpl.class, "Caught error:
"+t+" handling socket", t);
}
}
@@ -425,13 +427,7 @@
}
public HTMLNode addFormChild(HTMLNode parentNode, String target, String
name) {
- HTMLNode formNode =
- parentNode.addChild("form", new String[] { "action",
"method", "enctype", "id", "name", "accept-charset" },
- new String[] { target, "post",
"multipart/form-data", name, name, "utf-8"} );
- formNode.addChild("input", new String[] { "type", "name",
"value" },
- new String[] { "hidden", "formPassword",
container.getFormPassword() });
-
- return formNode;
+ return container.addFormChild(parentNode, target, name);
}
public boolean isAllowedFullAccess() {
Modified: trunk/freenet/src/freenet/io/comm/RetrievalException.java
===================================================================
--- trunk/freenet/src/freenet/io/comm/RetrievalException.java 2007-06-08
18:42:22 UTC (rev 13497)
+++ trunk/freenet/src/freenet/io/comm/RetrievalException.java 2007-06-09
17:12:22 UTC (rev 13498)
@@ -39,6 +39,7 @@
public static final int ALREADY_CACHED = 6;
public static final int SENDER_DISCONNECTED = 7;
public static final int NO_DATAINSERT = 8;
+ public static final int CANCELLED_BY_RECEIVER = 9;
int _reason;
String _cause;
Modified: trunk/freenet/src/freenet/io/xfer/PartiallyReceivedBulk.java
===================================================================
--- trunk/freenet/src/freenet/io/xfer/PartiallyReceivedBulk.java
2007-06-08 18:42:22 UTC (rev 13497)
+++ trunk/freenet/src/freenet/io/xfer/PartiallyReceivedBulk.java
2007-06-09 17:12:22 UTC (rev 13498)
@@ -129,7 +129,7 @@
}
}
- void abort(int errCode, String why) {
+ public void abort(int errCode, String why) {
BulkTransmitter[] notifyBTs;
BulkReceiver notifyBR;
synchronized(this) {
@@ -146,6 +146,7 @@
}
if(notifyBR != null)
notifyBR.onAborted();
+ raf.close();
}
public synchronized boolean isAborted() {
Modified: trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties
===================================================================
--- trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties 2007-06-08
18:42:22 UTC (rev 13497)
+++ trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties 2007-06-09
17:12:22 UTC (rev 13498)
@@ -325,6 +325,15 @@
FetchException.shortError.7=Too many metadata levels
FetchException.shortError.8=Too many archive restarts
FetchException.shortError.9=Too much recursion
+FileOfferUserAlert.title=Direct file transfer
+FileOfferUserAlert.offeredFileHeader=The node ${name} has offered a file:
+FileOfferUserAlert.fileLabel=File:
+FileOfferUserAlert.senderLabel=Sender:
+FileOfferUserAlert.commentLabel=Comment:
+FileOfferUserAlert.mimeLabel=MIME Type:
+FileOfferUserAlert.sizeLabel=Size:
+FileOfferUserAlert.acceptTransferButton=Accept Transfer
+FileOfferUserAlert.rejectTransferButton=Reject Transfer
GIFFilter.invalidHeader=The file does not contain a valid GIF header.
GIFFilter.invalidHeaderTitle=Invalid header
GIFFilter.notGif=The file you tried to fetch is not a GIF. It might be some
other file format, and your browser may do something dangerous with it,
therefore we have blocked it.
Modified: trunk/freenet/src/freenet/node/Node.java
===================================================================
--- trunk/freenet/src/freenet/node/Node.java 2007-06-08 18:42:22 UTC (rev
13497)
+++ trunk/freenet/src/freenet/node/Node.java 2007-06-09 17:12:22 UTC (rev
13498)
@@ -424,6 +424,8 @@
public static final int N2N_TEXT_MESSAGE_TYPE_FILE_OFFER = 2;
/** Identifier within fproxy messages for accepting an offer to
transfer a file */
public static final int N2N_TEXT_MESSAGE_TYPE_FILE_OFFER_ACCEPTED = 3;
+ /** Identifier within fproxy messages for rejecting an offer to
transfer a file */
+ public static final int N2N_TEXT_MESSAGE_TYPE_FILE_OFFER_REJECTED = 4;
public static final int EXTRA_PEER_DATA_TYPE_N2NTM = 1;
public static final int EXTRA_PEER_DATA_TYPE_PEER_NOTE = 2;
public static final int EXTRA_PEER_DATA_TYPE_QUEUED_TO_SEND_N2NTM = 3;
@@ -2681,6 +2683,8 @@
source.handleFproxyFileOffer(fs, fileNumber);
} else if(type ==
Node.N2N_TEXT_MESSAGE_TYPE_FILE_OFFER_ACCEPTED) {
source.handleFproxyFileOfferAccepted(fs, fileNumber);
+ } else if(type ==
Node.N2N_TEXT_MESSAGE_TYPE_FILE_OFFER_REJECTED) {
+ source.handleFproxyFileOfferRejected(fs, fileNumber);
} else {
Logger.error(this, "Received unknown fproxy node to
node message sub-type '"+type+"' from "+source.getPeer());
}
Modified: trunk/freenet/src/freenet/node/PeerNode.java
===================================================================
--- trunk/freenet/src/freenet/node/PeerNode.java 2007-06-08 18:42:22 UTC
(rev 13497)
+++ trunk/freenet/src/freenet/node/PeerNode.java 2007-06-09 17:12:22 UTC
(rev 13498)
@@ -53,6 +53,7 @@
import freenet.io.comm.PeerContext;
import freenet.io.comm.PeerParseException;
import freenet.io.comm.ReferenceSignatureVerificationException;
+import freenet.io.comm.RetrievalException;
import freenet.io.xfer.BulkReceiver;
import freenet.io.xfer.BulkTransmitter;
import freenet.io.xfer.PacketThrottle;
@@ -60,14 +61,18 @@
import freenet.keys.ClientSSK;
import freenet.keys.FreenetURI;
import freenet.keys.USK;
+import freenet.l10n.L10n;
import freenet.node.useralerts.N2NTMUserAlert;
+import freenet.node.useralerts.UserAlert;
import freenet.support.Base64;
import freenet.support.Fields;
+import freenet.support.HTMLNode;
import freenet.support.HexUtil;
import freenet.support.IllegalBase64Exception;
import freenet.support.LRUHashtable;
import freenet.support.Logger;
import freenet.support.SimpleFieldSet;
+import freenet.support.SizeUtil;
import freenet.support.WouldBlockException;
import freenet.support.io.FileUtil;
import freenet.support.io.RandomAccessFileWrapper;
@@ -3208,6 +3213,8 @@
private PartiallyReceivedBulk prb;
private BulkTransmitter transmitter;
private BulkReceiver receiver;
+ /** True if the offer has either been accepted or rejected */
+ private boolean acceptedOrRejected;
FileOffer(long uid, RandomAccessThing data, String filename,
String mimeType, String comment) throws IOException {
this.uid = uid;
@@ -3237,6 +3244,7 @@
}
public void accept() {
+ acceptedOrRejected = true;
File dest = new File(node.clientCore.downloadDir,
"direct-"+FileUtil.sanitize(getName())+"-"+filename);
try {
data = new RandomAccessFileWrapper(dest, "rw");
@@ -3266,6 +3274,7 @@
}, "Receiver for bulk transfer "+uid+":"+filename);
t.setDaemon(true);
t.start();
+ if(logMINOR) Logger.minor(this, "Receiving on "+t);
sendFileOfferAccepted(uid);
}
@@ -3294,6 +3303,17 @@
t.setDaemon(true);
t.start();
}
+
+ public void reject() {
+ acceptedOrRejected = true;
+ sendFileOfferRejected(uid);
+ }
+
+ public void onRejected() {
+ transmitter.cancel();
+ // FIXME prb's can't be shared, right? Well they aren't
here...
+ prb.abort(RetrievalException.CANCELLED_BY_RECEIVER,
"Cancelled by receiver");
+ }
}
public int sendTextMessage(String message) {
@@ -3359,6 +3379,39 @@
}
}
+ public int sendFileOfferRejected(long uid) {
+ storeOffers();
+ long now = System.currentTimeMillis();
+ SimpleFieldSet fs = new SimpleFieldSet(true);
+ fs.put("n2nType", Node.N2N_MESSAGE_TYPE_FPROXY);
+ fs.put("type", Node.N2N_TEXT_MESSAGE_TYPE_FILE_OFFER_REJECTED);
+ try {
+ fs.putSingle("source_nodename",
Base64.encode(node.getMyName().getBytes("UTF-8")));
+ fs.putSingle("target_nodename",
Base64.encode(getName().getBytes("UTF-8")));
+ fs.put("composedTime", now);
+ fs.put("sentTime", now);
+ fs.put("uid", uid);
+ if(logMINOR)
+ Logger.minor(this, "Sending node to node
message (file offer accepted):\n"+fs);
+ Message n2ntm;
+ n2ntm = DMT.createNodeToNodeMessage(
+ Node.N2N_MESSAGE_TYPE_FPROXY, fs
+
.toString().getBytes("UTF-8"));
+ try {
+ sendAsync(n2ntm, null, 0, null);
+ } catch (NotConnectedException e) {
+ fs.removeValue("sentTime");
+ queueN2NTM(fs);
+ setPeerNodeStatus(System.currentTimeMillis());
+ return getPeerNodeStatus();
+ }
+ this.setPeerNodeStatus(System.currentTimeMillis());
+ return getPeerNodeStatus();
+ } catch (UnsupportedEncodingException e) {
+ throw new Error("Impossible: "+e, e);
+ }
+ }
+
public int sendFileOffer(File filename, String message) throws
IOException {
String fnam = filename.getName();
String mime = DefaultMIMETypes.guessMIMEType(fnam, false);
@@ -3423,7 +3476,7 @@
}
public void handleFproxyFileOffer(SimpleFieldSet fs, int fileNumber) {
- FileOffer offer;
+ final FileOffer offer;
try {
offer = new FileOffer(fs, false);
} catch (FSParseException e) {
@@ -3435,11 +3488,149 @@
if(hisFileOffersByUID.containsKey(u)) return; // Ignore
re-advertisement
hisFileOffersByUID.put(u, offer);
}
- // Auto-accept : FIXME
- offer.accept();
+
+ // Don't persist for now - FIXME
+ this.deleteExtraPeerDataFile(fileNumber);
+
+ UserAlert alert = new UserAlert() {
+ public String dismissButtonText() {
+ return null; // Cannot hide, but can reject
+ }
+ public HTMLNode getHTMLText() {
+ HTMLNode div = new HTMLNode("div");
+
+ // FIXME localise!!!
+
+ div.addChild("p", l10n("offeredFileHeader",
"name", getName()));
+
+ // Descriptive table
+
+ HTMLNode table = div.addChild("table",
"border", "0");
+ HTMLNode row = table.addChild("tr");
+ row.addChild("td").addChild("#",
l10n("fileLabel"));
+ row.addChild("td").addChild("#",
offer.filename);
+ row = table.addChild("tr");
+ row.addChild("td").addChild("#",
l10n("sizeLabel"));
+ row.addChild("td").addChild("#",
SizeUtil.formatSize(offer.size));
+ row = table.addChild("tr");
+ row.addChild("td").addChild("#",
l10n("mimeLabel"));
+ row.addChild("td").addChild("#",
offer.mimeType);
+ row = table.addChild("tr");
+ row.addChild("td").addChild("#",
l10n("senderLabel"));
+ row.addChild("td").addChild("#", getName());
+ row = table.addChild("tr");
+ if(offer.comment != null &&
offer.comment.length() > 0) {
+ row.addChild("td").addChild("#",
l10n("commentLabel"));
+ row.addChild("td").addChild("#",
offer.comment);
+ }
+
+ // Accept/reject form
+
+ // Hopefully we will have a container when this
function is called!
+ HTMLNode form =
node.clientCore.getToadletContainer().addFormChild(div, "/friends/",
"f2fFileOfferAcceptForm");
+
+ // FIXME node_ is inefficient
+ form.addChild("input", new String[] { "type",
"name" },
+ new String[] { "hidden",
"node_"+PeerNode.this.hashCode() });
+
+ form.addChild("input", new String[] { "type",
"name", "value" },
+ new String[] { "hidden", "id",
Long.toString(offer.uid) });
+
+ form.addChild("input", new String[] { "type",
"name", "value" },
+ new String[] { "submit",
"acceptTransfer", l10n("acceptTransferButton") });
+
+ form.addChild("input", new String[] { "type",
"name", "value" },
+ new String[] { "submit",
"rejectTransfer", l10n("rejectTransferButton") });
+
+ return div;
+ }
+ public short getPriorityClass() {
+ return UserAlert.MINOR;
+ }
+ public String getText() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(l10n("offeredFileHeader", "name",
getName()));
+ sb.append('\n');
+ sb.append(l10n("fileLabel"));
+ sb.append(' ');
+ sb.append(offer.filename);
+ sb.append('\n');
+ sb.append(l10n("sizeLabel"));
+ sb.append(' ');
+ sb.append(SizeUtil.formatSize(offer.size));
+ sb.append('\n');
+ sb.append(l10n("mimeLabel"));
+ sb.append(' ');
+ sb.append(offer.mimeType);
+ sb.append('\n');
+ sb.append(l10n("senderLabel"));
+ sb.append(' ');
+ sb.append(getName());
+ sb.append('\n');
+ if(offer.comment != null &&
offer.comment.length() > 0) {
+ sb.append(l10n("commentLabel"));
+ sb.append(' ');
+ sb.append(offer.comment);
+ }
+ return sb.toString();
+ }
+ public String getTitle() {
+ return l10n("title");
+ }
+
+ private String l10n(String key) {
+ return
L10n.getString("FileOfferUserAlert."+key);
+ }
+ private String l10n(String key, String pattern, String
value) {
+ return
L10n.getString("FileOfferUserAlert."+key, pattern, value);
+ }
+ public boolean isValid() {
+ if(offer.acceptedOrRejected) {
+ node.clientCore.alerts.unregister(this);
+ return false;
+ }
+ return true;
+ }
+ public void isValid(boolean validity) {
+ // Ignore
+ }
+ public void onDismiss() {
+ // Ignore
+ }
+ public boolean shouldUnregisterOnDismiss() {
+ return false;
+ }
+
+ public boolean userCanDismiss() {
+ return false; // should accept or reject
+ }
+ };
+
+ node.clientCore.alerts.register(alert);
}
+ public void acceptTransfer(long id) {
+ if(logMINOR)
+ Logger.minor(this, "Accepting transfer "+id+" on
"+this);
+ FileOffer fo;
+ synchronized(this) {
+ fo = (FileOffer) hisFileOffersByUID.get(new Long(id));
+ }
+ fo.accept();
+ }
+
+ public void rejectTransfer(long id) {
+ FileOffer fo;
+ synchronized(this) {
+ fo = (FileOffer) hisFileOffersByUID.remove(new
Long(id));
+ }
+ fo.reject();
+ }
+
public void handleFproxyFileOfferAccepted(SimpleFieldSet fs, int
fileNumber) {
+ // Don't persist for now - FIXME
+ this.deleteExtraPeerDataFile(fileNumber);
+
long uid;
try {
uid = fs.getLong("uid");
@@ -3469,4 +3660,23 @@
Logger.error(this, "Cannot send because node
disconnected: "+e+" for "+uid+":"+fo.filename, e);
}
}
+
+ public void handleFproxyFileOfferRejected(SimpleFieldSet fs, int
fileNumber) {
+ // Don't persist for now - FIXME
+ this.deleteExtraPeerDataFile(fileNumber);
+
+ long uid;
+ try {
+ uid = fs.getLong("uid");
+ } catch (FSParseException e) {
+ Logger.error(this, "Could not parse offer rejected:
"+e+" on "+this+" :\n"+fs, e);
+ return;
+ }
+
+ FileOffer fo;
+ synchronized(this) {
+ fo = (FileOffer) myFileOffersByUID.remove(new
Long(uid));
+ }
+ fo.onRejected();
+ }
}
Modified: trunk/freenet/src/freenet/support/io/RandomAccessFileWrapper.java
===================================================================
--- trunk/freenet/src/freenet/support/io/RandomAccessFileWrapper.java
2007-06-08 18:42:22 UTC (rev 13497)
+++ trunk/freenet/src/freenet/support/io/RandomAccessFileWrapper.java
2007-06-09 17:12:22 UTC (rev 13498)
@@ -5,6 +5,8 @@
import java.io.IOException;
import java.io.RandomAccessFile;
+import freenet.support.Logger;
+
public class RandomAccessFileWrapper implements RandomAccessThing {
// FIXME maybe we should avoid opening these until we are ready to use
them
@@ -38,4 +40,12 @@
return raf.length();
}
+ public void close() {
+ try {
+ raf.close();
+ } catch (IOException e) {
+ Logger.error(this, "Could not close "+raf+" : "+e+" for
"+this, e);
+ }
+ }
+
}
Modified: trunk/freenet/src/freenet/support/io/RandomAccessThing.java
===================================================================
--- trunk/freenet/src/freenet/support/io/RandomAccessThing.java 2007-06-08
18:42:22 UTC (rev 13497)
+++ trunk/freenet/src/freenet/support/io/RandomAccessThing.java 2007-06-09
17:12:22 UTC (rev 13498)
@@ -16,5 +16,7 @@
public void pread(long fileOffset, byte[] buf, int bufOffset, int
length) throws IOException;
public void pwrite(long fileOffset, byte[] buf, int bufOffset, int
length) throws IOException;
+
+ public void close();
}