From 33b7f08d5c5eea26fc33000aa8c58e0504b2701e Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 19 Aug 2014 20:34:46 +0000 Subject: [PATCH] * i2psnark: - Don't filter create torrent form, and fix exception on ':' in file names (ticket #1342) - Don't remap file names on torrents we created, and save remap setting in torrent config file (tickets #571, 771) - Escaping fixes since names may not be remapped - Use better encodePath() from Jetty - Don't say create torrent succeeded when it didn't - Add more sanity checks for torrent creation --- .../src/org/klomp/snark/CompleteListener.java | 1 + .../java/src/org/klomp/snark/MagnetURI.java | 4 +- .../java/src/org/klomp/snark/Snark.java | 9 +- .../src/org/klomp/snark/SnarkManager.java | 64 ++++-- .../java/src/org/klomp/snark/Storage.java | 30 ++- .../src/org/klomp/snark/UpdateRunner.java | 4 + .../src/org/klomp/snark/web/BasicServlet.java | 19 +- .../org/klomp/snark/web/I2PSnarkServlet.java | 130 ++++++++--- .../java/src/org/klomp/snark/web/URIUtil.java | 206 ++++++++++++++++++ history.txt | 11 + .../src/net/i2p/router/RouterVersion.java | 2 +- 11 files changed, 419 insertions(+), 61 deletions(-) create mode 100644 apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java diff --git a/apps/i2psnark/java/src/org/klomp/snark/CompleteListener.java b/apps/i2psnark/java/src/org/klomp/snark/CompleteListener.java index 77fa9c98b..bd2f78e53 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/CompleteListener.java +++ b/apps/i2psnark/java/src/org/klomp/snark/CompleteListener.java @@ -57,4 +57,5 @@ public interface CompleteListener { // not really listeners but the easiest way to get back to an optional SnarkManager public long getSavedTorrentTime(Snark snark); public BitField getSavedTorrentBitField(Snark snark); + public boolean getSavedPreserveNamesSetting(Snark snark); } diff --git a/apps/i2psnark/java/src/org/klomp/snark/MagnetURI.java b/apps/i2psnark/java/src/org/klomp/snark/MagnetURI.java index 9044d62ff..60698a091 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/MagnetURI.java +++ b/apps/i2psnark/java/src/org/klomp/snark/MagnetURI.java @@ -42,7 +42,7 @@ public class MagnetURI { name = util.getString("Magnet") + ' ' + ihash; String dn = getParam("dn", url); if (dn != null) - name += " (" + Storage.filterName(dn) + ')'; + name += " (" + dn + ')'; } else if (url.startsWith(MAGGOT)) { // maggot://0691e40aae02e552cfcb57af1dca56214680c0c5:0b557bbdf8718e95d352fbe994dec3a383e2ede7 ihash = url.substring(MAGGOT.length()).trim(); @@ -82,7 +82,7 @@ public class MagnetURI { } /** - * @return pretty name or null + * @return pretty name or null, NOT HTML escaped */ public String getName() { return _name; diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index 7f0c0305b..e9d0937e6 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -420,14 +420,17 @@ public class Snark try { activity = "Checking storage"; + boolean shouldPreserve = completeListener != null && completeListener.getSavedPreserveNamesSetting(this); if (baseFile == null) { - String base = Storage.filterName(meta.getName()); + String base = meta.getName(); + if (!shouldPreserve) + base = Storage.filterName(base); if (_util.getFilesPublic()) baseFile = new File(rootDataDir, base); else baseFile = new SecureFile(rootDataDir, base); } - storage = new Storage(_util, baseFile, meta, slistener); + storage = new Storage(_util, baseFile, meta, slistener, shouldPreserve); if (completeListener != null) { storage.check(completeListener.getSavedTorrentTime(this), completeListener.getSavedTorrentBitField(this)); @@ -1141,7 +1144,7 @@ public class Snark else baseFile = new SecureFile(rootDataDir, base); // The following two may throw IOE... - storage = new Storage(_util, baseFile, metainfo, this); + storage = new Storage(_util, baseFile, metainfo, this, false); storage.check(); // ... so don't set meta until here meta = metainfo; diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 3e2e8bcf7..b910e32d5 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -91,8 +91,9 @@ public class SnarkManager implements CompleteListener { private static final String PROP_META_BASE = "base"; private static final String PROP_META_BITFIELD = "bitfield"; private static final String PROP_META_PRIORITY = "priority"; - private static final String PROP_META_BITFIELD_SUFFIX = ".bitfield"; - private static final String PROP_META_PRIORITY_SUFFIX = ".priority"; + private static final String PROP_META_PRESERVE_NAMES = "preserveFileNames"; + //private static final String PROP_META_BITFIELD_SUFFIX = ".bitfield"; + //private static final String PROP_META_PRIORITY_SUFFIX = ".priority"; private static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet."; private static final String CONFIG_FILE_SUFFIX = ".config"; @@ -1335,7 +1336,7 @@ public class SnarkManager implements CompleteListener { * This verifies that a torrent with this infohash is not already added. * This may take a LONG time to create or check the storage. * - * Called from servlet. + * Called from servlet. This is only for the 'create torrent' form. * * @param metainfo the metainfo for the torrent * @param bitfield the current completion status of the torrent @@ -1343,18 +1344,20 @@ public class SnarkManager implements CompleteListener { * Must be a filesystem-safe name. * @param baseFile may be null, if so look in rootDataDir * @throws RuntimeException via Snark.fatal() + * @return success * @since 0.8.4 */ - public void addTorrent(MetaInfo metainfo, BitField bitfield, String filename, File baseFile, boolean dontAutoStart) throws IOException { + public boolean addTorrent(MetaInfo metainfo, BitField bitfield, String filename, + File baseFile, boolean dontAutoStart) throws IOException { // prevent interference by DirMonitor synchronized (_snarks) { Snark snark = getTorrentByInfoHash(metainfo.getInfoHash()); if (snark != null) { addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName())); - return; + return false; } // so addTorrent won't recheck - saveTorrentStatus(metainfo, bitfield, null, baseFile); // no file priorities + saveTorrentStatus(metainfo, bitfield, null, baseFile, true); // no file priorities try { locked_writeMetaInfo(metainfo, filename, areFilesPublic()); // hold the lock for a long time @@ -1362,8 +1365,10 @@ public class SnarkManager implements CompleteListener { } catch (IOException ioe) { addMessage(_("Failed to copy torrent file to {0}", filename)); _log.error("Failed to write torrent file", ioe); + return false; } } + return true; } /** @@ -1500,14 +1505,39 @@ public class SnarkManager implements CompleteListener { * @return File or null, doesn't necessarily exist * @since 0.9.11 */ - public File getSavedBaseFile(byte[] ih) { + private File getSavedBaseFile(byte[] ih) { Properties config = getConfig(ih); String base = config.getProperty(PROP_META_BASE); if (base == null) return null; return new File(base); } + + /** + * Get setting for a torrent from the config file. + * @return setting, false if not found + * @since 0.9.15 + */ + public boolean getSavedPreserveNamesSetting(Snark snark) { + Properties config = getConfig(snark); + return Boolean.parseBoolean(config.getProperty(PROP_META_PRESERVE_NAMES)); + } + /** + * Save the completion status of a torrent and other data in the config file + * for that torrent. Does nothing for magnets. + * + * @since 0.9.15 + */ + public void saveTorrentStatus(Snark snark) { + MetaInfo meta = snark.getMetaInfo(); + Storage storage = snark.getStorage(); + if (meta == null || storage == null) + return; + saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities(), + storage.getBase(), storage.getPreserveFileNames()); + } + /** * Save the completion status of a torrent and the current time in the config file * for that torrent. @@ -1519,13 +1549,15 @@ public class SnarkManager implements CompleteListener { * @param priorities may be null * @param base may be null */ - public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, File base) { + private void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, + File base, boolean preserveNames) { synchronized (_configLock) { - locked_saveTorrentStatus(metainfo, bitfield, priorities, base); + locked_saveTorrentStatus(metainfo, bitfield, priorities, base, preserveNames); } } - private void locked_saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, File base) { + private void locked_saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, + File base, boolean preserveNames) { byte[] ih = metainfo.getInfoHash(); String bfs; if (bitfield.complete()) { @@ -1537,6 +1569,7 @@ public class SnarkManager implements CompleteListener { Properties config = getConfig(ih); config.setProperty(PROP_META_STAMP, Long.toString(System.currentTimeMillis())); config.setProperty(PROP_META_BITFIELD, bfs); + config.setProperty(PROP_META_PRESERVE_NAMES, Boolean.toString(preserveNames)); if (base != null) config.setProperty(PROP_META_BASE, base.getAbsolutePath()); @@ -1776,10 +1809,11 @@ public class SnarkManager implements CompleteListener { if (meta == null || storage == null) return; StringBuilder buf = new StringBuilder(256); - buf.append("").append(storage.getBaseName()).append(""); + buf.append("\">").append(base).append(""); addMessageNoEscape(_("Download finished: {0}", buf.toString())); // + " (" + _("size: {0}B", DataHelper.formatSize2(len)) + ')'); updateStatus(snark); } @@ -1791,7 +1825,8 @@ public class SnarkManager implements CompleteListener { MetaInfo meta = snark.getMetaInfo(); Storage storage = snark.getStorage(); if (meta != null && storage != null) - saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities(), storage.getBase()); + saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities(), + storage.getBase(), storage.getPreserveFileNames()); } /** @@ -1813,7 +1848,8 @@ public class SnarkManager implements CompleteListener { snark.stopTorrent(); return null; } - saveTorrentStatus(meta, storage.getBitField(), null, storage.getBase()); // no file priorities + saveTorrentStatus(meta, storage.getBitField(), null, + storage.getBase(), storage.getPreserveFileNames()); // no file priorities // temp for addMessage() in case canonical throws String name = storage.getBaseName(); try { diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java index f0477b478..efae9baad 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java @@ -66,6 +66,7 @@ public class Storage private final int piece_size; private final int pieces; private final long total_length; + private final boolean _preserveFileNames; private boolean changed; private volatile boolean _isChecking; private final AtomicInteger _allocateCount = new AtomicInteger(); @@ -92,8 +93,9 @@ public class Storage * try to create and/or check all needed files in the MetaInfo. * * @param baseFile the torrent data file or dir + * @param preserveFileNames if true, do not remap names to a 'safe' charset */ - public Storage(I2PSnarkUtil util, File baseFile, MetaInfo metainfo, StorageListener listener) + public Storage(I2PSnarkUtil util, File baseFile, MetaInfo metainfo, StorageListener listener, boolean preserveFileNames) { _util = util; _log = util.getContext().logManager().getLog(Storage.class); @@ -108,6 +110,7 @@ public class Storage List> files = metainfo.getFiles(); int sz = files != null ? files.size() : 1; _torrentFiles = new ArrayList(sz); + _preserveFileNames = preserveFileNames; } /** @@ -130,6 +133,7 @@ public class Storage _base = baseFile; _log = util.getContext().logManager().getLog(Storage.class); this.listener = listener; + _preserveFileNames = true; // Create names, rafs and lengths arrays. _torrentFiles = getFiles(baseFile); @@ -469,7 +473,12 @@ public class Storage * @since 0.7.14 */ public String getBaseName() { - return filterName(metainfo.getName()); + return optFilterName(metainfo.getName()); + } + + /** @since 0.9.15 */ + public boolean getPreserveFileNames() { + return _preserveFileNames; } /** @@ -618,6 +627,19 @@ public class Storage 0x2028, 0x2029 }; + /** + * Filter the name, but only if configured to do so. + * We will do so on torrents received from others, but not + * on those we created ourselves, so we do not lose track of files. + * + * @since 0.9.15 + */ + private String optFilterName(String name) { + if (_preserveFileNames) + return name; + return filterName(name); + } + /** * Removes 'suspicious' characters from the given file name. * http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx @@ -674,13 +696,13 @@ public class Storage * * @param names path elements */ - private static File createFileFromNames(File base, List names, boolean areFilesPublic) throws IOException + private File createFileFromNames(File base, List names, boolean areFilesPublic) throws IOException { File f = null; Iterator it = names.iterator(); while (it.hasNext()) { - String name = filterName(it.next()); + String name = optFilterName(it.next()); if (it.hasNext()) { // Another dir in the hierarchy. diff --git a/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java b/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java index 5812fb49c..28fe34ef9 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java +++ b/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java @@ -290,6 +290,10 @@ class UpdateRunner implements UpdateTask, CompleteListener { return _smgr.getSavedTorrentBitField(snark); } + public boolean getSavedPreserveNamesSetting(Snark snark) { + return _smgr.getSavedPreserveNamesSetting(snark); + } + //////// end CompleteListener methods private static String linkify(String url) { diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java index d171a3f18..b3a1a6a5c 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java @@ -559,14 +559,17 @@ class BasicServlet extends HttpServlet /** * Simple version of URIUtil.encodePath() */ - protected static String encodePath(String path) throws MalformedURLException { - try { - URI uri = new URI(null, null, path, null); - return uri.toString(); - } catch (URISyntaxException use) { - // for ease of use, since a USE is not an IOE but a MUE is... - throw new MalformedURLException(use.getMessage()); - } + protected static String encodePath(String path) /* throws MalformedURLException */ { + // Does NOT handle a ':' correctly, throws MUE. + // Can't convert to %3a before hand or the % gets escaped + //try { + // URI uri = new URI(null, null, path, null); + // return uri.toString(); + //} catch (URISyntaxException use) { + // // for ease of use, since a USE is not an IOE but a MUE is... + // throw new MalformedURLException(use.getMessage()); + //} + return URIUtil.encodePath(path); } /** diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index af2e9fa8f..3296ea0f4 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -9,6 +9,7 @@ import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; @@ -73,6 +74,7 @@ public class I2PSnarkServlet extends BasicServlet { _nonce = _context.random().nextLong(); // limited protection against overwriting other config files or directories // in case you named your war "router.war" + // We don't handle bad characters in the context path. Don't do that. String configName = _contextName; if (!configName.equals(DEFAULT_NAME)) configName = DEFAULT_NAME + '_' + _contextName; @@ -942,7 +944,7 @@ public class I2PSnarkServlet extends BasicServlet { if (taction != null) processTrackerForm(taction, req); } else if ("Create".equals(action)) { - String baseData = req.getParameter("baseFile"); + String baseData = req.getParameter("nofilter_baseFile"); if (baseData != null && baseData.trim().length() > 0) { File baseFile = new File(baseData.trim()); if (!baseFile.isAbsolute()) @@ -954,6 +956,40 @@ public class I2PSnarkServlet extends BasicServlet { // announceURL = announceURLOther; if (baseFile.exists()) { + String torrentName = baseFile.getName(); + if (torrentName.toLowerCase(Locale.US).endsWith(".torrent")) { + _manager.addMessage(_("Cannot add a torrent ending in \".torrent\": {0}", baseFile.getAbsolutePath())); + return; + } + Snark snark = _manager.getTorrentByBaseName(torrentName); + if (snark != null) { + _manager.addMessage(_("Torrent with this name is already running: {0}", torrentName)); + return; + } + if (isParentOf(baseFile,_manager.getDataDir()) || + isParentOf(baseFile, _manager.util().getContext().getBaseDir()) || + isParentOf(baseFile, _manager.util().getContext().getConfigDir())) { + _manager.addMessage(_("Cannot add a torrent including an I2P directory: {0}", baseFile.getAbsolutePath())); + return; + } + Collection snarks = _manager.getTorrents(); + for (Snark s : snarks) { + Storage storage = s.getStorage(); + if (storage == null) + continue; + File sbase = storage.getBase(); + if (isParentOf(sbase, baseFile)) { + _manager.addMessage(_("Cannot add torrent {0} inside another torrent: {1}", + baseFile.getAbsolutePath(), sbase)); + return; + } + if (isParentOf(baseFile, sbase)) { + _manager.addMessage(_("Cannot add torrent {0} including another torrent: {1}", + baseFile.getAbsolutePath(), sbase)); + return; + } + } + if (announceURL.equals("none")) announceURL = null; _lastAnnounceURL = announceURL; @@ -1006,7 +1042,9 @@ public class I2PSnarkServlet extends BasicServlet { File torrentFile = new File(_manager.getDataDir(), s.getBaseName() + ".torrent"); // FIXME is the storage going to stay around thanks to the info reference? // now add it, but don't automatically start it - _manager.addTorrent(info, s.getBitField(), torrentFile.getAbsolutePath(), baseFile, true); + boolean ok = _manager.addTorrent(info, s.getBitField(), torrentFile.getAbsolutePath(), baseFile, true); + if (!ok) + return; _manager.addMessage(_("Torrent created for \"{0}\"", baseFile.getName()) + ": " + torrentFile.getAbsolutePath()); if (announceURL != null && !_manager.util().getOpenTrackers().contains(announceURL)) _manager.addMessage(_("Many I2P trackers require you to register new torrents before seeding - please do so before starting \"{0}\"", baseFile.getName())); @@ -1357,7 +1395,7 @@ public class I2PSnarkServlet extends BasicServlet { } } - String encodedBaseName = urlEncode(fullBasename); + String encodedBaseName = encodePath(fullBasename); // File type icon column out.write("\n"); if (isValid) { @@ -1407,7 +1445,7 @@ public class I2PSnarkServlet extends BasicServlet { buf.append("\">"); out.write(buf.toString()); } - out.write(basename); + out.write(DataHelper.escapeHTML(basename)); if (remaining == 0 || isMultiFile) out.write(""); @@ -1786,12 +1824,6 @@ public class I2PSnarkServlet extends BasicServlet { } private void writeSeedForm(PrintWriter out, HttpServletRequest req, List sortedTrackers) throws IOException { - String baseFile = req.getParameter("baseFile"); - if (baseFile == null || baseFile.trim().length() <= 0) - baseFile = ""; - else - baseFile = DataHelper.stripHTML(baseFile); // XSS - out.write("
\n"); // *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file out.write("
\n"); @@ -1808,7 +1840,7 @@ public class I2PSnarkServlet extends BasicServlet { //out.write("From file:
\n"); out.write(_("Data to seed")); out.write(":" - + "").append(display).append(""); return buf.toString(); } - /** @since 0.8.13 */ + /** + * This is for a full URL. For a path only, use encodePath(). + * @since 0.8.13 + */ private static String urlEncode(String s) { return s.replace(";", "%3B").replace("&", "&").replace(" ", "%20") .replace("<", "<").replace(">", ">") @@ -2289,7 +2330,7 @@ public class I2PSnarkServlet extends BasicServlet { * * Get the resource list as a HTML directory listing. * @param xxxr The Resource unused - * @param base The base URL + * @param base The encoded base URL * @param parent True if the parent directory should be included * @param postParams map of POST parameters or null if not a POST * @return String of HTML or null if postParams != null @@ -2298,7 +2339,8 @@ public class I2PSnarkServlet extends BasicServlet { private String getListHTML(File xxxr, String base, boolean parent, Map postParams) throws IOException { - String title = decodePath(base); + String decodedBase = decodePath(base); + String title = decodedBase; String cpath = _contextPath + '/'; if (title.startsWith(cpath)) title = title.substring(cpath.length()); @@ -2351,7 +2393,7 @@ public class I2PSnarkServlet extends BasicServlet { if (title.endsWith("/")) title = title.substring(0, title.length() - 1); String directory = title; - title = _("Torrent") + ": " + title; + title = _("Torrent") + ": " + DataHelper.escapeHTML(title); buf.append(title); buf.append("").append(HEADER_A).append(_themePath).append(HEADER_B).append("" + "\n
") .append(_("Torrent")) .append(": ") - .append(snark.getBaseName()) + .append(DataHelper.escapeHTML(snark.getBaseName())) .append("\n"); String fullPath = snark.getName(); - String baseName = urlEncode((new File(fullPath)).getName()); + String baseName = encodePath((new File(fullPath)).getName()); buf.append("") .append("\"\" ") .append(_("Torrent file")) .append(": ") - .append(fullPath) + .append(DataHelper.escapeHTML(fullPath)) .append("\n"); buf.append("") .append("\"\" ") .append(_("Data location")) .append(": ") - .append(urlEncode(snark.getStorage().getBase().getPath())) + .append(DataHelper.escapeHTML(snark.getStorage().getBase().getPath())) .append("\n"); String announce = null; @@ -2593,7 +2635,7 @@ public class I2PSnarkServlet extends BasicServlet { .append("\">\n"); buf.append("\n\n"); buf.append("\"\" ") .append(_("Up to higher level directory")) .append("\n"); @@ -2604,7 +2646,7 @@ public class I2PSnarkServlet extends BasicServlet { boolean showSaveButton = false; for (int i=0 ; i< ls.length ; i++) { - String encoded = encodePath(ls[i].getName()); + //String encoded = encodePath(ls[i].getName()); // bugfix for I2P - Backport from Jetty 6 (zero file lengths and last-modified times) // http://jira.codehaus.org/browse/JETTY-361?page=com.atlassian.jira.plugin.system.issuetabpanels%3Achangehistory-tabpanel#issue-tabs // See resource.diff attachment @@ -2653,10 +2695,10 @@ public class I2PSnarkServlet extends BasicServlet { } } - String path=addPaths(base,encoded); + String path = addPaths(decodedBase, ls[i].getName()); if (item.isDirectory() && !path.endsWith("/")) path=addPaths(path,"/"); - path = urlEncode(path); + path = encodePath(path); String icon = toIcon(item); buf.append(""); @@ -2677,7 +2719,7 @@ public class I2PSnarkServlet extends BasicServlet { buf.append(""); if (complete) buf.append(""); - buf.append(item.getName().replace("&", "&")); + buf.append(DataHelper.escapeHTML(item.getName())); if (complete) buf.append(""); buf.append(""); @@ -2808,6 +2850,36 @@ public class I2PSnarkServlet extends BasicServlet { } } snark.updatePiecePriorities(); - _manager.saveTorrentStatus(snark.getMetaInfo(), storage.getBitField(), storage.getFilePriorities(), storage.getBase()); + _manager.saveTorrentStatus(snark); + } + + /** + * Is "a" equal to "b", + * or is "a" a directory and a parent of file or directory "b", + * canonically speaking? + * + * @since 0.9.15 + */ + private static boolean isParentOf(File a, File b) { + try { + a = a.getCanonicalFile(); + b = b.getCanonicalFile(); + } catch (IOException ioe) { + return false; + } + if (a.equals(b)) + return true; + if (!a.isDirectory()) + return false; + // easy case + if (!b.getPath().startsWith(a.getPath())) + return false; + // dir by dir + while (!a.equals(b)) { + b = b.getParentFile(); + if (b == null) + return false; + } + return true; } } diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java b/apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java new file mode 100644 index 000000000..3a9c3088e --- /dev/null +++ b/apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java @@ -0,0 +1,206 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.klomp.snark.web; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; + +import net.i2p.data.DataHelper; + + +/** URI Holder. + * This class assists with the decoding and encoding or HTTP URI's. + * It differs from the java.net.URL class as it does not provide + * communications ability, but it does assist with query string + * formatting. + *

UTF-8 encoding is used by default for % encoded characters. This + * may be overridden with the org.eclipse.jetty.util.URI.charset system property. + * see UrlEncoded + * + * I2P modded from Jetty 8.1.15 + * @since 0.9.15 + */ +class URIUtil +{ + + /** Encode a URI path. + * This is the same encoding offered by URLEncoder, except that + * the '/' character is not encoded. + * @param path The path the encode + * @return The encoded path + */ + public static String encodePath(String path) + { + if (path==null || path.length()==0) + return path; + + StringBuilder buf = encodePath(null,path); + return buf==null?path:buf.toString(); + } + + /** Encode a URI path. + * @param path The path the encode + * @param buf StringBuilder to encode path into (or null) + * @return The StringBuilder or null if no substitutions required. + */ + public static StringBuilder encodePath(StringBuilder buf, String path) + { + byte[] bytes=null; + if (buf==null) + { + loop: + for (int i=0;i': + case ' ': + buf=new StringBuilder(path.length()*2); + break loop; + default: + if (c>127) + { + bytes = DataHelper.getUTF8(path); + buf=new StringBuilder(path.length()*2); + break loop; + } + + } + } + if (buf==null) + return null; + } + + //synchronized(buf) + //{ + if (bytes!=null) + { + for (int i=0;i': + buf.append("%3E"); + continue; + case ' ': + buf.append("%20"); + continue; + default: + if (c<0) + { + buf.append('%'); + toHex(c,buf); + } + else + buf.append((char)c); + continue; + } + } + + } + else + { + for (int i=0;i': + buf.append("%3E"); + continue; + case ' ': + buf.append("%20"); + continue; + default: + buf.append(c); + continue; + } + } + } + //} + + return buf; + } + + /** + * Modded from Jetty TypeUtil + */ + private static void toHex(byte b, StringBuilder buf) + { + int d=0xf&((0xF0&b)>>4); + buf.append((char)((d>9?('A'-10):'0')+d)); + d=0xf&b; + buf.append((char)((d>9?('A'-10):'0')+d)); + } +} + + + diff --git a/history.txt b/history.txt index ac9306c56..68e84e941 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,14 @@ +2014-08-19 zzz + * i2psnark: + - Don't filter create torrent form, and + fix exception on ':' in file names (ticket #1342) + - Don't remap file names on torrents we created, and + save remap setting in torrent config file (tickets #571, 771) + - Escaping fixes since names may not be remapped + - Use better encodePath() from Jetty + - Don't say create torrent succeeded when it didn't + - Add more sanity checks for base path of created torrent + 2014-08-18 zzz * i2psnark: - Don't send HTML-only headers for icons diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index c10128fe6..fecba78d6 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 6; + public final static long BUILD = 7; /** for example "-test" */ public final static String EXTRA = "";