[freenet-cvs] r13498 - in trunk/freenet/src/freenet: clients/http io/comm io/xfer l10n node support/io

toad at freenetproject.org toad at freenetproject.org
Sat Jun 9 17:12:23 UTC 2007


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();
 	
 }




More information about the cvs mailing list