Author: toad Date: 2007-06-01 16:45:19 +0000 (Fri, 01 Jun 2007) New Revision: 13445
Added: trunk/freenet/src/freenet/support/io/RandomAccessFileWrapper.java Modified: trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java trunk/freenet/src/freenet/io/comm/DMT.java trunk/freenet/src/freenet/keys/FreenetURI.java trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties trunk/freenet/src/freenet/node/Node.java trunk/freenet/src/freenet/node/NodeDispatcher.java trunk/freenet/src/freenet/node/PeerNode.java trunk/freenet/src/freenet/support/SimpleFieldSet.java trunk/freenet/src/freenet/support/io/FileUtil.java trunk/freenet/src/freenet/support/io/RandomAccessThing.java Log: Direct f2f bulk data transfer, and significant related refactoring to N2NTM related code. Caveats: - Automatically accepts transfers! This is BAD! DO NOT RELEASE! Will be fixed before release. - Untested! - PeerNode now uses 2 different types in N2NTMs: n2nType, which is fproxy vs other apps, and within an fproxy message, type, which differentiates between a useralert and messages related to the bulk transfer mechanism. Modified: trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java =================================================================== --- trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java 2007-06-01 16:45:14 UTC (rev 13444) +++ trunk/freenet/src/freenet/clients/http/N2NTMToadlet.java 2007-06-01 16:45:19 UTC (rev 13445) @@ -3,6 +3,7 @@ * http://www.gnu.org/ for further details of the GPL. */ package freenet.clients.http; +import java.io.File; import java.io.IOException; import java.net.URI; import java.util.HashMap; @@ -154,60 +155,63 @@ HTMLNode contentNode = ctx.getPageMaker().getContentNode(pageNode); HTMLNode peerTableInfobox = contentNode.addChild("div", "class", "infobox infobox-normal"); + PeerNode[] peerNodes = node.getDarknetConnections(); + String fnam = request.getPartAsString("filename", 1024); + File filename = null; + if(fnam != null) { + filename = new File(fnam); + if(!(filename.exists() && filename.canRead())) { + peerTableInfobox.addChild("#", l10n("noSuchFileOrCannotRead")); + Toadlet.addHomepageLink(peerTableInfobox); + this.writeReply(ctx, 400, "text/html", "OK", pageNode.generate()); + return; + } + } HTMLNode peerTable = peerTableInfobox.addChild("table", "class", - "n2ntm-send-statuses"); + "n2ntm-send-statuses"); HTMLNode peerTableHeaderRow = peerTable.addChild("tr"); peerTableHeaderRow.addChild("th", l10n("peerName")); peerTableHeaderRow.addChild("th", l10n("sendStatus")); - PeerNode[] peerNodes = node.getDarknetConnections(); for (int i = 0; i < peerNodes.length; i++) { if (request.isPartSet("node_" + peerNodes[i].hashCode())) { PeerNode pn = peerNodes[i]; + + int status; + + if(filename != null) { + try { + status = pn.sendFileOffer(filename, message); + } catch (IOException e) { + peerTableInfobox.addChild("#", l10n("noSuchFileOrCannotRead")); + Toadlet.addHomepageLink(peerTableInfobox); + this.writeReply(ctx, 400, "text/html", "OK", pageNode.generate()); + return; + } + } else { + status = pn.sendTextMessage(message); + } + String sendStatusShort; String sendStatusLong; String sendStatusClass; - try { - long now = System.currentTimeMillis(); - SimpleFieldSet fs = new SimpleFieldSet(true); - fs.put("type", Node.N2N_TEXT_MESSAGE_TYPE_USERALERT); - fs.putSingle("source_nodename", Base64.encode(node - .getMyName().getBytes())); - fs.putSingle("target_nodename", Base64.encode(pn - .getName().getBytes())); - fs.putSingle("text", Base64.encode(message.getBytes())); - fs.put("composedTime", now); - fs.put("sentTime", now); - Message n2ntm; - n2ntm = DMT.createNodeToNodeMessage( - Node.N2N_TEXT_MESSAGE_TYPE_USERALERT, fs - .toString().getBytes("UTF-8")); - if (!pn.isConnected()) { - sendStatusShort = l10n("queuedTitle"); - sendStatusLong = l10n("queued"); - sendStatusClass = "n2ntm-send-queued"; - fs.removeValue("sentTime"); - pn.queueN2NTM(fs); - Logger.normal(this, "Queued N2NTM to '" - + pn.getName() + "': " + message); - } else if (pn.getPeerNodeStatus() == PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF) { - sendStatusShort = l10n("delayedTitle"); - sendStatusLong = l10n("delayed"); - sendStatusClass = "n2ntm-send-delayed"; - usm.send(pn, n2ntm, null); - Logger.normal(this, "Sent N2NTM to '" - + pn.getName() + "': " + message); - } else { - sendStatusShort = l10n("sentTitle"); - sendStatusLong = l10n("sent"); - sendStatusClass = "n2ntm-send-sent"; - usm.send(pn, n2ntm, null); - Logger.normal(this, "Sent N2NTM to '" - + pn.getName() + "': " + message); - } - } catch (NotConnectedException e) { - sendStatusShort = l10n("failedTitle"); - sendStatusLong = l10n("failed"); - sendStatusClass = "n2ntm-send-failed"; + if(status == PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF) { + sendStatusShort = l10n("delayedTitle"); + sendStatusLong = l10n("delayed"); + sendStatusClass = "n2ntm-send-delayed"; + Logger.normal(this, "Sent N2NTM to '" + + pn.getName() + "': " + message); + } else if(status == PeerManager.PEER_NODE_STATUS_CONNECTED) { + sendStatusShort = l10n("sentTitle"); + sendStatusLong = l10n("sent"); + sendStatusClass = "n2ntm-send-sent"; + Logger.normal(this, "Sent N2NTM to '" + + pn.getName() + "': " + message); + } else { + sendStatusShort = l10n("queuedTitle"); + sendStatusLong = l10n("queued"); + sendStatusClass = "n2ntm-send-queued"; + Logger.normal(this, "Queued N2NTM to '" + + pn.getName() + "': " + message); } HTMLNode peerRow = peerTable.addChild("tr"); peerRow.addChild("td", "class", "peer-name").addChild("#", @@ -267,7 +271,10 @@ messageForm.addChild("textarea", new String[] { "id", "name", "rows", "cols" }, new String[] { "n2ntmtext", "message", "8", "74" }); messageForm.addChild("br"); + messageForm.addChild("#", "You may attach a file:"); messageForm.addChild("input", new String[] { "type", "name", "value" }, + new String[] { "text", "filename", "" }); + messageForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "send", l10n("sendMessageShort") }); } } Modified: trunk/freenet/src/freenet/io/comm/DMT.java =================================================================== --- trunk/freenet/src/freenet/io/comm/DMT.java 2007-06-01 16:45:14 UTC (rev 13444) +++ trunk/freenet/src/freenet/io/comm/DMT.java 2007-06-01 16:45:19 UTC (rev 13445) @@ -425,23 +425,6 @@ return msg; } - // Node-To-Node Instant Message - public static final MessageType nodeToNodeTextMessage = new MessageType("nodeToNodeTextMessage", false) {{ - addField(NODE_TO_NODE_MESSAGE_TYPE, Integer.class); - addField(SOURCE_NODENAME, String.class); - addField(TARGET_NODENAME, String.class); - addField(NODE_TO_NODE_MESSAGE_TEXT, String.class); - }}; - - public static final Message createNodeToNodeTextMessage(int type, String source, String target, String message) { - Message msg = new Message(nodeToNodeTextMessage); - msg.set(NODE_TO_NODE_MESSAGE_TYPE, type); - msg.set(SOURCE_NODENAME, source); - msg.set(TARGET_NODENAME, target); - msg.set(NODE_TO_NODE_MESSAGE_TEXT, message); - return msg; - } - // FNP messages public static final MessageType FNPCHKDataRequest = new MessageType("FNPCHKDataRequest") {{ addField(UID, Long.class); Modified: trunk/freenet/src/freenet/keys/FreenetURI.java =================================================================== --- trunk/freenet/src/freenet/keys/FreenetURI.java 2007-06-01 16:45:14 UTC (rev 13444) +++ trunk/freenet/src/freenet/keys/FreenetURI.java 2007-06-01 16:45:19 UTC (rev 13445) @@ -24,6 +24,7 @@ import freenet.support.URLDecoder; import freenet.support.URLEncodedFormatException; import freenet.support.URLEncoder; +import freenet.support.io.FileUtil; import freenet.client.InsertException; /** @@ -740,7 +741,7 @@ for(int i=0;i<names.size();i++) { String s = (String) names.get(i); if(logMINOR) Logger.minor(this, "name "+i+" = "+s); - s = sanitize(s); + s = FileUtil.sanitize(s); if(logMINOR) Logger.minor(this, "Sanitized name "+i+" = "+s); if(s.length() > 0) { if(out.length() > 0) @@ -759,24 +760,6 @@ return out.toString(); } - private String sanitize(String s) { - StringBuffer sb = new StringBuffer(s.length()); - for(int i=0;i<s.length();i++) { - char c = s.charAt(i); - if((c == '/') || (c == '\\') || (c == '%') || (c == '>') || (c == '<') || (c == ':') || (c == '\'') || (c == '\"')) - continue; - if(Character.isDigit(c)) - sb.append(c); - else if(Character.isLetter(c)) - sb.append(c); - else if(Character.isWhitespace(c)) - sb.append(' '); - else if((c == '-') || (c == '_') || (c == '.')) - sb.append(c); - } - return sb.toString(); - } - public FreenetURI setSuggestedEdition(long newEdition) { return new FreenetURI( keyType, Modified: trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties =================================================================== --- trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties 2007-06-01 16:45:14 UTC (rev 13444) +++ trunk/freenet/src/freenet/l10n/freenet.l10n.en.properties 2007-06-01 16:45:19 UTC (rev 13445) @@ -439,6 +439,7 @@ N2NTMToadlet.failed=Message not sent to peer: peer not connected N2NTMToadlet.failedTitle=Failed N2NTMToadlet.friends=Friends +N2NTMToadlet.noSuchFileOrCannotRead=Unable to send the file: Either it does not exist or it can't be read. N2NTMToadlet.peerName=Peer Name N2NTMToadlet.peerNotFoundTitle=Peer not found N2NTMToadlet.peerNotFoundWithHash=The peer with the hash code \u201c${hash}\u201d could not be found. Modified: trunk/freenet/src/freenet/node/Node.java =================================================================== --- trunk/freenet/src/freenet/node/Node.java 2007-06-01 16:45:14 UTC (rev 13444) +++ trunk/freenet/src/freenet/node/Node.java 2007-06-01 16:45:19 UTC (rev 13445) @@ -415,8 +415,14 @@ public static final int EXIT_TEST_ERROR = 25; public static final int EXIT_BAD_BWLIMIT = 26; - public static final int N2N_MESSAGE_TYPE_FPROXY_USERALERT = 1; - public static final int N2N_TEXT_MESSAGE_TYPE_USERALERT = N2N_MESSAGE_TYPE_FPROXY_USERALERT; // **FIXME** For backwards-compatibility, remove when removing DMT.nodeToNodeTextMessage + /** Type identifier for fproxy node to node messages, as sent on DMT.nodeToNodeMessage's */ + public static final int N2N_MESSAGE_TYPE_FPROXY = 1; + /** Identifier within fproxy messages for simple, short text messages to be displayed on the homepage as useralerts */ + public static final int N2N_TEXT_MESSAGE_TYPE_USERALERT = 1; + /** Identifier within fproxy messages for an offer to transfer a file */ + 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 = 2; 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; @@ -2612,7 +2618,7 @@ public void receivedNodeToNodeMessage(Message m) { PeerNode source = (PeerNode)m.getSource(); int type = ((Integer) m.getObject(DMT.NODE_TO_NODE_MESSAGE_TYPE)).intValue(); - if(type == Node.N2N_MESSAGE_TYPE_FPROXY_USERALERT) { + if(type == Node.N2N_MESSAGE_TYPE_FPROXY) { ShortBuffer messageData = (ShortBuffer) m.getObject(DMT.NODE_TO_NODE_MESSAGE_DATA); Logger.normal(this, "Received N2NM from '"+source.getPeer()+"'"); SimpleFieldSet fs = null; @@ -2651,70 +2657,31 @@ } /** - * Handle a received node to node text message - */ - public void receivedNodeToNodeTextMessage(Message m) { - PeerNode source = (PeerNode)m.getSource(); - int type = ((Integer) m.getObject(DMT.NODE_TO_NODE_MESSAGE_TYPE)).intValue(); - if(type == Node.N2N_TEXT_MESSAGE_TYPE_USERALERT) { - String source_nodename = (String) m.getObject(DMT.SOURCE_NODENAME); - String target_nodename = (String) m.getObject(DMT.TARGET_NODENAME); - String text = (String) m.getObject(DMT.NODE_TO_NODE_MESSAGE_TEXT); - Logger.normal(this, "Received N2NTM from '"+source_nodename+"' to '"+target_nodename+"': "+text); - SimpleFieldSet fs = new SimpleFieldSet(true); - fs.put("type", type); - fs.putSingle("source_nodename", Base64.encode(source_nodename.getBytes())); - fs.putSingle("target_nodename", Base64.encode(target_nodename.getBytes())); - fs.putSingle("text", Base64.encode(text.getBytes())); - fs.put("receivedTime", System.currentTimeMillis()); - fs.putSingle("receivedAs", "nodeToNodeTextMessage"); - int fileNumber = source.writeNewExtraPeerDataFile( fs, EXTRA_PEER_DATA_TYPE_N2NTM); - if( fileNumber == -1 ) { - Logger.error( this, "Failed to write N2NTM to extra peer data file for peer "+source.getPeer()); - } - // Keep track of the fileNumber so we can potentially delete the extra peer data file later, the file is authoritative - try { - handleNodeToNodeTextMessageSimpleFieldSet(fs, source, fileNumber); - } catch (FSParseException e) { - // Shouldn't happen - throw new Error(e); - } - } else { - Logger.error(this, "Received unknown node to node text message type '"+type+"' from "+source.getPeer()); - } - } - - /** * Handle a node to node text message SimpleFieldSet * @throws FSParseException */ public void handleNodeToNodeTextMessageSimpleFieldSet(SimpleFieldSet fs, PeerNode source, int fileNumber) throws FSParseException { - int type = fs.getInt("type"); - if(type == Node.N2N_TEXT_MESSAGE_TYPE_USERALERT) { - String source_nodename = null; - String target_nodename = null; - String text = null; - long composedTime; - long sentTime; - long receivedTime; - try { - source_nodename = new String(Base64.decode(fs.get("source_nodename"))); - target_nodename = new String(Base64.decode(fs.get("target_nodename"))); - text = new String(Base64.decode(fs.get("text"))); - composedTime = fs.getLong("composedTime", -1); - sentTime = fs.getLong("sentTime", -1); - receivedTime = fs.getLong("receivedTime", -1); - } catch (IllegalBase64Exception e) { - Logger.error(this, "Bad Base64 encoding when decoding a N2NTM SimpleFieldSet", e); - return; - } - N2NTMUserAlert userAlert = new N2NTMUserAlert(source, source_nodename, target_nodename, text, fileNumber, composedTime, sentTime, receivedTime); - clientCore.alerts.register(userAlert); + int overallType = fs.getInt("n2nType", 1); // FIXME remove default + if(overallType == Node.N2N_MESSAGE_TYPE_FPROXY) { + handleFproxyNodeToNodeTextMessageSimpleFieldSet(fs, source, fileNumber); } else { - Logger.error(this, "Received unknown node to node message type '"+type+"' from "+source.getPeer()); + Logger.error(this, "Received unknown node to node message type '"+overallType+"' from "+source.getPeer()); } } + private void handleFproxyNodeToNodeTextMessageSimpleFieldSet(SimpleFieldSet fs, PeerNode source, int fileNumber) throws FSParseException { + int type = fs.getInt("type"); + if(type == Node.N2N_TEXT_MESSAGE_TYPE_USERALERT) { + source.handleFproxyN2NTM(fs, fileNumber); + } else if(type == Node.N2N_TEXT_MESSAGE_TYPE_FILE_OFFER) { + source.handleFproxyFileOffer(fs, fileNumber); + } else if(type == Node.N2N_TEXT_MESSAGE_TYPE_FILE_OFFER_ACCEPTED) { + source.handleFproxyFileOfferAccepted(fs, fileNumber); + } else { + Logger.error(this, "Received unknown fproxy node to node message sub-type '"+type+"' from "+source.getPeer()); + } + } + public String getMyName() { return myName; } Modified: trunk/freenet/src/freenet/node/NodeDispatcher.java =================================================================== --- trunk/freenet/src/freenet/node/NodeDispatcher.java 2007-06-01 16:45:14 UTC (rev 13444) +++ trunk/freenet/src/freenet/node/NodeDispatcher.java 2007-06-01 16:45:19 UTC (rev 13445) @@ -85,9 +85,6 @@ } else if(spec == DMT.nodeToNodeMessage) { node.receivedNodeToNodeMessage(m); return true; - } else if(spec == DMT.nodeToNodeTextMessage) { - node.receivedNodeToNodeTextMessage(m); - return true; } if(!source.isRoutable()) return false; Modified: trunk/freenet/src/freenet/node/PeerNode.java =================================================================== --- trunk/freenet/src/freenet/node/PeerNode.java 2007-06-01 16:45:14 UTC (rev 13444) +++ trunk/freenet/src/freenet/node/PeerNode.java 2007-06-01 16:45:19 UTC (rev 13445) @@ -17,6 +17,7 @@ import java.net.MalformedURLException; import java.net.UnknownHostException; import java.util.Arrays; +import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashSet; @@ -27,6 +28,7 @@ import net.i2p.util.NativeBigInteger; +import freenet.client.DefaultMIMETypes; import freenet.client.FetchResult; import freenet.client.async.USKRetriever; import freenet.client.async.USKRetrieverCallback; @@ -51,10 +53,14 @@ import freenet.io.comm.PeerContext; import freenet.io.comm.PeerParseException; import freenet.io.comm.ReferenceSignatureVerificationException; +import freenet.io.xfer.BulkReceiver; +import freenet.io.xfer.BulkTransmitter; import freenet.io.xfer.PacketThrottle; +import freenet.io.xfer.PartiallyReceivedBulk; import freenet.keys.ClientSSK; import freenet.keys.FreenetURI; import freenet.keys.USK; +import freenet.node.useralerts.N2NTMUserAlert; import freenet.support.Base64; import freenet.support.Fields; import freenet.support.HexUtil; @@ -63,6 +69,9 @@ import freenet.support.Logger; import freenet.support.SimpleFieldSet; import freenet.support.WouldBlockException; +import freenet.support.io.FileUtil; +import freenet.support.io.RandomAccessFileWrapper; +import freenet.support.io.RandomAccessThing; import freenet.support.math.RunningAverage; import freenet.support.math.SimpleRunningAverage; import freenet.support.math.TimeDecayingRunningAverage; @@ -2781,6 +2790,8 @@ return false; } else if(extraPeerDataType == Node.EXTRA_PEER_DATA_TYPE_QUEUED_TO_SEND_N2NTM) { boolean sendSuccess = false; + int type = fs.getInt("n2nType", 1); // FIXME remove default + fs.putOverwrite("n2nType", Integer.toString(type)); if(isConnected()) { Message n2ntm; if(fs.get("extraPeerDataType") != null) { @@ -2796,7 +2807,7 @@ fs.putOverwrite("sentTime", Long.toString(System.currentTimeMillis())); try { - n2ntm = DMT.createNodeToNodeMessage(Node.N2N_TEXT_MESSAGE_TYPE_USERALERT, fs.toString().getBytes("UTF-8")); + n2ntm = DMT.createNodeToNodeMessage(type, fs.toString().getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { Logger.error(this, "UnsupportedEncodingException processing extraPeerDataType ("+extraPeerDataTypeString+") in file "+extraPeerDataFile.getPath(), e); return false; @@ -3170,4 +3181,285 @@ public boolean verify(byte[] hash, DSASignature sig) { return DSA.verify(peerPubKey, sig, new NativeBigInteger(1, hash), false); } + + // File transfer offers + // FIXME this should probably be somewhere else, along with the N2NTM stuff... but where? + // FIXME this should be persistent across node restarts + + /** Files I have offered to this peer */ + private final HashMap myFileOffersByUID = new HashMap(); + /** Files this peer has offered to me */ + private final HashMap hisFileOffersByUID = new HashMap(); + + private void storeOffers() { + // FIXME do something + } + + class FileOffer { + final long uid; + final String filename; + final String mimeType; + final String comment; + private RandomAccessThing data; + final long size; + /** Who is offering it? True = I am offering it, False = I am being offered it */ + final boolean amIOffering; + private PartiallyReceivedBulk prb; + private BulkTransmitter transmitter; + private BulkReceiver receiver; + + FileOffer(long uid, RandomAccessThing data, String filename, String mimeType, String comment) throws IOException { + this.uid = uid; + this.data = data; + this.filename = filename; + this.mimeType = mimeType; + this.comment = comment; + size = data.size(); + amIOffering = true; + } + + public FileOffer(SimpleFieldSet fs, boolean amIOffering) throws FSParseException { + uid = fs.getLong("uid"); + size = fs.getLong("size"); + mimeType = fs.get("metadata.contentType"); + filename = FileUtil.sanitize(fs.get("filename"), mimeType); + comment = fs.get("comment"); + this.amIOffering = amIOffering; + } + + public void toFieldSet(SimpleFieldSet fs) { + fs.put("uid", uid); + fs.putSingle("filename", filename); + fs.putSingle("metadata.contentType", mimeType); + fs.putSingle("comment", comment); + fs.put("size", size); + } + + public void accept() { + File dest = new File(node.clientCore.downloadDir, "direct-"+FileUtil.sanitize(getName())+filename); + try { + data = new RandomAccessFileWrapper(dest, "rw"); + } catch (FileNotFoundException e) { + // Impossible + throw new Error("Impossible: FileNotFoundException opening with RAF with rw! "+e, e); + } + prb = new PartiallyReceivedBulk(node.usm, size, Node.PACKET_SIZE, data, false); + receiver = new BulkReceiver(prb, PeerNode.this, uid); + // FIXME make this persistent + Thread t = new Thread(new Runnable() { + public void run() { + if(!receiver.receive()) { + String err = "Failed to receive "+this; + Logger.error(this, err); + System.err.println(err); + } + } + }, "Receiver for bulk transfer "+uid+":"+filename); + t.setDaemon(true); + t.start(); + sendFileOfferAccepted(uid); + } + + public void send() throws DisconnectedException { + prb = new PartiallyReceivedBulk(node.usm, size, Node.PACKET_SIZE, data, true); + transmitter = new BulkTransmitter(prb, PeerNode.this, uid, node.outputThrottle); + Thread t = new Thread(new Runnable() { + public void run() { + if(!transmitter.send()) { + String err = "Failed to send "+this; + Logger.error(this, err); + System.err.println(err); + } + } + }, "Sender for bulk transfer "+uid+":"+filename); + t.setDaemon(true); + t.start(); + } + } + + public int sendTextMessage(String message) { + 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_USERALERT); + try { + fs.putSingle("source_nodename", Base64.encode(node.getMyName().getBytes("UTF-8"))); + fs.putSingle("target_nodename", Base64.encode(getName().getBytes("UTF-8"))); + fs.putSingle("text", Base64.encode(message.getBytes("UTF-8"))); + fs.put("composedTime", now); + fs.put("sentTime", now); + Message n2ntm; + int status = getPeerNodeStatus(); + if(status == PeerManager.PEER_NODE_STATUS_CONNECTED || + status == PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF) { + 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(); + } + } else { + fs.removeValue("sentTime"); + queueN2NTM(fs); + } + return status; + } catch (UnsupportedEncodingException e) { + throw new Error("Impossible: "+e, e); + } + } + + public int sendFileOfferAccepted(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_ACCEPTED); + 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); + Message n2ntm; + int status = getPeerNodeStatus(); + if(status == PeerManager.PEER_NODE_STATUS_CONNECTED || + status == PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF) { + 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(); + } + } else { + fs.removeValue("sentTime"); + queueN2NTM(fs); + } + return status; + } 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); + long uid = node.random.nextLong(); + RandomAccessThing data = new RandomAccessFileWrapper(filename, "r"); + FileOffer fo = new FileOffer(uid, data, fnam, mime, message); + synchronized(this) { + myFileOffersByUID.put(new Long(uid), fo); + } + 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); + 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); + fo.toFieldSet(fs); + Message n2ntm; + int status = getPeerNodeStatus(); + if(status == PeerManager.PEER_NODE_STATUS_CONNECTED || + status == PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF) { + 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(); + } + } else { + fs.removeValue("sentTime"); + queueN2NTM(fs); + } + return status; + } catch (UnsupportedEncodingException e) { + throw new Error("Impossible: "+e, e); + } + } + + public void handleFproxyN2NTM(SimpleFieldSet fs, int fileNumber) { + String source_nodename = null; + String target_nodename = null; + String text = null; + long composedTime; + long sentTime; + long receivedTime; + try { + source_nodename = new String(Base64.decode(fs.get("source_nodename"))); + target_nodename = new String(Base64.decode(fs.get("target_nodename"))); + text = new String(Base64.decode(fs.get("text"))); + composedTime = fs.getLong("composedTime", -1); + sentTime = fs.getLong("sentTime", -1); + receivedTime = fs.getLong("receivedTime", -1); + } catch (IllegalBase64Exception e) { + Logger.error(this, "Bad Base64 encoding when decoding a N2NTM SimpleFieldSet", e); + return; + } + N2NTMUserAlert userAlert = new N2NTMUserAlert(this, source_nodename, target_nodename, text, fileNumber, composedTime, sentTime, receivedTime); + node.clientCore.alerts.register(userAlert); + } + + public void handleFproxyFileOffer(SimpleFieldSet fs, int fileNumber) { + FileOffer offer; + try { + offer = new FileOffer(fs, false); + } catch (FSParseException e) { + Logger.error(this, "Could not parse offer: "+e+" on "+this+" :\n"+fs, e); + return; + } + Long u = new Long(offer.uid); + synchronized(this) { + if(hisFileOffersByUID.containsKey(u)) return; // Ignore re-advertisement + hisFileOffersByUID.put(u, offer); + } + // Auto-accept : FIXME + offer.accept(); + } + + public void handleFproxyFileOfferAccepted(SimpleFieldSet fs, int fileNumber) { + long uid; + try { + uid = fs.getLong("uid"); + } catch (FSParseException e) { + Logger.error(this, "Could not parse offer accepted: "+e+" on "+this+" :\n"+fs, e); + return; + } + Long u = new Long(uid); + FileOffer fo; + synchronized(this) { + fo = (FileOffer) (myFileOffersByUID.get(u)); + } + if(fo == null) { + Logger.error(this, "No such offer: "+uid); + try { + sendAsync(DMT.createFNPBulkSendAborted(uid), null, fileNumber, null); + } catch (NotConnectedException e) { + // Fine by me! + } + return; + } + try { + fo.send(); + } catch (DisconnectedException e) { + Logger.error(this, "Cannot send because node disconnected: "+e+" for "+uid+":"+fo.filename, e); + } + } } Modified: trunk/freenet/src/freenet/support/SimpleFieldSet.java =================================================================== --- trunk/freenet/src/freenet/support/SimpleFieldSet.java 2007-06-01 16:45:14 UTC (rev 13444) +++ trunk/freenet/src/freenet/support/SimpleFieldSet.java 2007-06-01 16:45:19 UTC (rev 13445) @@ -655,6 +655,16 @@ } } + public long getLong(String key) throws FSParseException { + String s = get(key); + if(s == null) throw new FSParseException("No key "+key); + try { + return Long.parseLong(s); + } catch (NumberFormatException e) { + throw new FSParseException("Cannot parse "+s+" for long "+key); + } + } + public boolean getBoolean(String key, boolean def) { return Fields.stringToBool(get(key), def); } Modified: trunk/freenet/src/freenet/support/io/FileUtil.java =================================================================== --- trunk/freenet/src/freenet/support/io/FileUtil.java 2007-06-01 16:45:14 UTC (rev 13444) +++ trunk/freenet/src/freenet/support/io/FileUtil.java 2007-06-01 16:45:19 UTC (rev 13445) @@ -10,6 +10,8 @@ import java.io.IOException; import java.io.InputStreamReader; +import freenet.client.DefaultMIMETypes; + final public class FileUtil { /** Round up a value to the next multiple of a power of 2 */ @@ -98,4 +100,33 @@ } return result.toString(); } + + public static String sanitize(String s) { + StringBuffer sb = new StringBuffer(s.length()); + for(int i=0;i<s.length();i++) { + char c = s.charAt(i); + if((c == '/') || (c == '\\') || (c == '%') || (c == '>') || (c == '<') || (c == ':') || (c == '\'') || (c == '\"')) + continue; + if(Character.isDigit(c)) + sb.append(c); + else if(Character.isLetter(c)) + sb.append(c); + else if(Character.isWhitespace(c)) + sb.append(' '); + else if((c == '-') || (c == '_') || (c == '.')) + sb.append(c); + } + return sb.toString(); + } + + public static String sanitize(String filename, String mimeType) { + filename = sanitize(filename); + if(mimeType == null) return filename; + if(filename.indexOf('.') >= 0) { + String oldExt = filename.substring(filename.lastIndexOf('.')); + if(DefaultMIMETypes.isValidExt(mimeType, oldExt)) return filename; + } + return filename + '.' + DefaultMIMETypes.getExtension(filename); + } + } Added: trunk/freenet/src/freenet/support/io/RandomAccessFileWrapper.java =================================================================== --- trunk/freenet/src/freenet/support/io/RandomAccessFileWrapper.java (rev 0) +++ trunk/freenet/src/freenet/support/io/RandomAccessFileWrapper.java 2007-06-01 16:45:19 UTC (rev 13445) @@ -0,0 +1,41 @@ +package freenet.support.io; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +public class RandomAccessFileWrapper implements RandomAccessThing { + + // FIXME maybe we should avoid opening these until we are ready to use them + final RandomAccessFile raf; + + public RandomAccessFileWrapper(RandomAccessFile raf) { + this.raf = raf; + } + + public RandomAccessFileWrapper(File filename, String mode) throws FileNotFoundException { + raf = new RandomAccessFile(filename, mode); + } + + public void pread(long fileOffset, byte[] buf, int bufOffset, int length) + throws IOException { + synchronized(this) { + raf.seek(fileOffset); + raf.readFully(buf, bufOffset, length); + } + } + + public void pwrite(long fileOffset, byte[] buf, int bufOffset, int length) + throws IOException { + synchronized(this) { + raf.seek(fileOffset); + raf.write(buf, bufOffset, length); + } + } + + public long size() throws IOException { + return raf.length(); + } + +} Modified: trunk/freenet/src/freenet/support/io/RandomAccessThing.java =================================================================== --- trunk/freenet/src/freenet/support/io/RandomAccessThing.java 2007-06-01 16:45:14 UTC (rev 13444) +++ trunk/freenet/src/freenet/support/io/RandomAccessThing.java 2007-06-01 16:45:19 UTC (rev 13445) @@ -11,7 +11,7 @@ */ public interface RandomAccessThing { - public long size(); + public long size() throws IOException; public void pread(long fileOffset, byte[] buf, int bufOffset, int length) throws IOException;
