* 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
This commit is contained in:
zzz
2014-08-19 20:34:46 +00:00
parent 66bbe21a87
commit 33b7f08d5c
11 changed files with 419 additions and 61 deletions

View File

@ -57,4 +57,5 @@ public interface CompleteListener {
// not really listeners but the easiest way to get back to an optional SnarkManager // not really listeners but the easiest way to get back to an optional SnarkManager
public long getSavedTorrentTime(Snark snark); public long getSavedTorrentTime(Snark snark);
public BitField getSavedTorrentBitField(Snark snark); public BitField getSavedTorrentBitField(Snark snark);
public boolean getSavedPreserveNamesSetting(Snark snark);
} }

View File

@ -42,7 +42,7 @@ public class MagnetURI {
name = util.getString("Magnet") + ' ' + ihash; name = util.getString("Magnet") + ' ' + ihash;
String dn = getParam("dn", url); String dn = getParam("dn", url);
if (dn != null) if (dn != null)
name += " (" + Storage.filterName(dn) + ')'; name += " (" + dn + ')';
} else if (url.startsWith(MAGGOT)) { } else if (url.startsWith(MAGGOT)) {
// maggot://0691e40aae02e552cfcb57af1dca56214680c0c5:0b557bbdf8718e95d352fbe994dec3a383e2ede7 // maggot://0691e40aae02e552cfcb57af1dca56214680c0c5:0b557bbdf8718e95d352fbe994dec3a383e2ede7
ihash = url.substring(MAGGOT.length()).trim(); 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() { public String getName() {
return _name; return _name;

View File

@ -420,14 +420,17 @@ public class Snark
try try
{ {
activity = "Checking storage"; activity = "Checking storage";
boolean shouldPreserve = completeListener != null && completeListener.getSavedPreserveNamesSetting(this);
if (baseFile == null) { if (baseFile == null) {
String base = Storage.filterName(meta.getName()); String base = meta.getName();
if (!shouldPreserve)
base = Storage.filterName(base);
if (_util.getFilesPublic()) if (_util.getFilesPublic())
baseFile = new File(rootDataDir, base); baseFile = new File(rootDataDir, base);
else else
baseFile = new SecureFile(rootDataDir, base); baseFile = new SecureFile(rootDataDir, base);
} }
storage = new Storage(_util, baseFile, meta, slistener); storage = new Storage(_util, baseFile, meta, slistener, shouldPreserve);
if (completeListener != null) { if (completeListener != null) {
storage.check(completeListener.getSavedTorrentTime(this), storage.check(completeListener.getSavedTorrentTime(this),
completeListener.getSavedTorrentBitField(this)); completeListener.getSavedTorrentBitField(this));
@ -1141,7 +1144,7 @@ public class Snark
else else
baseFile = new SecureFile(rootDataDir, base); baseFile = new SecureFile(rootDataDir, base);
// The following two may throw IOE... // The following two may throw IOE...
storage = new Storage(_util, baseFile, metainfo, this); storage = new Storage(_util, baseFile, metainfo, this, false);
storage.check(); storage.check();
// ... so don't set meta until here // ... so don't set meta until here
meta = metainfo; meta = metainfo;

View File

@ -91,8 +91,9 @@ public class SnarkManager implements CompleteListener {
private static final String PROP_META_BASE = "base"; private static final String PROP_META_BASE = "base";
private static final String PROP_META_BITFIELD = "bitfield"; private static final String PROP_META_BITFIELD = "bitfield";
private static final String PROP_META_PRIORITY = "priority"; private static final String PROP_META_PRIORITY = "priority";
private static final String PROP_META_BITFIELD_SUFFIX = ".bitfield"; private static final String PROP_META_PRESERVE_NAMES = "preserveFileNames";
private static final String PROP_META_PRIORITY_SUFFIX = ".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_MAGNET_PREFIX = "i2psnark.magnet."; private static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet.";
private static final String CONFIG_FILE_SUFFIX = ".config"; 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 verifies that a torrent with this infohash is not already added.
* This may take a LONG time to create or check the storage. * 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 metainfo the metainfo for the torrent
* @param bitfield the current completion status of 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. * Must be a filesystem-safe name.
* @param baseFile may be null, if so look in rootDataDir * @param baseFile may be null, if so look in rootDataDir
* @throws RuntimeException via Snark.fatal() * @throws RuntimeException via Snark.fatal()
* @return success
* @since 0.8.4 * @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 // prevent interference by DirMonitor
synchronized (_snarks) { synchronized (_snarks) {
Snark snark = getTorrentByInfoHash(metainfo.getInfoHash()); Snark snark = getTorrentByInfoHash(metainfo.getInfoHash());
if (snark != null) { if (snark != null) {
addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName())); addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName()));
return; return false;
} }
// so addTorrent won't recheck // so addTorrent won't recheck
saveTorrentStatus(metainfo, bitfield, null, baseFile); // no file priorities saveTorrentStatus(metainfo, bitfield, null, baseFile, true); // no file priorities
try { try {
locked_writeMetaInfo(metainfo, filename, areFilesPublic()); locked_writeMetaInfo(metainfo, filename, areFilesPublic());
// hold the lock for a long time // hold the lock for a long time
@ -1362,8 +1365,10 @@ public class SnarkManager implements CompleteListener {
} catch (IOException ioe) { } catch (IOException ioe) {
addMessage(_("Failed to copy torrent file to {0}", filename)); addMessage(_("Failed to copy torrent file to {0}", filename));
_log.error("Failed to write torrent file", ioe); _log.error("Failed to write torrent file", ioe);
return false;
} }
} }
return true;
} }
/** /**
@ -1500,7 +1505,7 @@ public class SnarkManager implements CompleteListener {
* @return File or null, doesn't necessarily exist * @return File or null, doesn't necessarily exist
* @since 0.9.11 * @since 0.9.11
*/ */
public File getSavedBaseFile(byte[] ih) { private File getSavedBaseFile(byte[] ih) {
Properties config = getConfig(ih); Properties config = getConfig(ih);
String base = config.getProperty(PROP_META_BASE); String base = config.getProperty(PROP_META_BASE);
if (base == null) if (base == null)
@ -1508,6 +1513,31 @@ public class SnarkManager implements CompleteListener {
return new File(base); 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 * Save the completion status of a torrent and the current time in the config file
* for that torrent. * for that torrent.
@ -1519,13 +1549,15 @@ public class SnarkManager implements CompleteListener {
* @param priorities may be null * @param priorities may be null
* @param base 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) { 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(); byte[] ih = metainfo.getInfoHash();
String bfs; String bfs;
if (bitfield.complete()) { if (bitfield.complete()) {
@ -1537,6 +1569,7 @@ public class SnarkManager implements CompleteListener {
Properties config = getConfig(ih); Properties config = getConfig(ih);
config.setProperty(PROP_META_STAMP, Long.toString(System.currentTimeMillis())); config.setProperty(PROP_META_STAMP, Long.toString(System.currentTimeMillis()));
config.setProperty(PROP_META_BITFIELD, bfs); config.setProperty(PROP_META_BITFIELD, bfs);
config.setProperty(PROP_META_PRESERVE_NAMES, Boolean.toString(preserveNames));
if (base != null) if (base != null)
config.setProperty(PROP_META_BASE, base.getAbsolutePath()); config.setProperty(PROP_META_BASE, base.getAbsolutePath());
@ -1776,10 +1809,11 @@ public class SnarkManager implements CompleteListener {
if (meta == null || storage == null) if (meta == null || storage == null)
return; return;
StringBuilder buf = new StringBuilder(256); StringBuilder buf = new StringBuilder(256);
buf.append("<a href=\"").append(_contextPath).append('/').append(storage.getBaseName()); String base = DataHelper.escapeHTML(storage.getBaseName());
buf.append("<a href=\"").append(_contextPath).append('/').append(base);
if (meta.getFiles() != null) if (meta.getFiles() != null)
buf.append('/'); buf.append('/');
buf.append("\">").append(storage.getBaseName()).append("</a>"); buf.append("\">").append(base).append("</a>");
addMessageNoEscape(_("Download finished: {0}", buf.toString())); // + " (" + _("size: {0}B", DataHelper.formatSize2(len)) + ')'); addMessageNoEscape(_("Download finished: {0}", buf.toString())); // + " (" + _("size: {0}B", DataHelper.formatSize2(len)) + ')');
updateStatus(snark); updateStatus(snark);
} }
@ -1791,7 +1825,8 @@ public class SnarkManager implements CompleteListener {
MetaInfo meta = snark.getMetaInfo(); MetaInfo meta = snark.getMetaInfo();
Storage storage = snark.getStorage(); Storage storage = snark.getStorage();
if (meta != null && storage != null) 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(); snark.stopTorrent();
return null; 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 // temp for addMessage() in case canonical throws
String name = storage.getBaseName(); String name = storage.getBaseName();
try { try {

View File

@ -66,6 +66,7 @@ public class Storage
private final int piece_size; private final int piece_size;
private final int pieces; private final int pieces;
private final long total_length; private final long total_length;
private final boolean _preserveFileNames;
private boolean changed; private boolean changed;
private volatile boolean _isChecking; private volatile boolean _isChecking;
private final AtomicInteger _allocateCount = new AtomicInteger(); 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. * try to create and/or check all needed files in the MetaInfo.
* *
* @param baseFile the torrent data file or dir * @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; _util = util;
_log = util.getContext().logManager().getLog(Storage.class); _log = util.getContext().logManager().getLog(Storage.class);
@ -108,6 +110,7 @@ public class Storage
List<List<String>> files = metainfo.getFiles(); List<List<String>> files = metainfo.getFiles();
int sz = files != null ? files.size() : 1; int sz = files != null ? files.size() : 1;
_torrentFiles = new ArrayList<TorrentFile>(sz); _torrentFiles = new ArrayList<TorrentFile>(sz);
_preserveFileNames = preserveFileNames;
} }
/** /**
@ -130,6 +133,7 @@ public class Storage
_base = baseFile; _base = baseFile;
_log = util.getContext().logManager().getLog(Storage.class); _log = util.getContext().logManager().getLog(Storage.class);
this.listener = listener; this.listener = listener;
_preserveFileNames = true;
// Create names, rafs and lengths arrays. // Create names, rafs and lengths arrays.
_torrentFiles = getFiles(baseFile); _torrentFiles = getFiles(baseFile);
@ -469,7 +473,12 @@ public class Storage
* @since 0.7.14 * @since 0.7.14
*/ */
public String getBaseName() { 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 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. * Removes 'suspicious' characters from the given file name.
* http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx * http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
@ -674,13 +696,13 @@ public class Storage
* *
* @param names path elements * @param names path elements
*/ */
private static File createFileFromNames(File base, List<String> names, boolean areFilesPublic) throws IOException private File createFileFromNames(File base, List<String> names, boolean areFilesPublic) throws IOException
{ {
File f = null; File f = null;
Iterator<String> it = names.iterator(); Iterator<String> it = names.iterator();
while (it.hasNext()) while (it.hasNext())
{ {
String name = filterName(it.next()); String name = optFilterName(it.next());
if (it.hasNext()) if (it.hasNext())
{ {
// Another dir in the hierarchy. // Another dir in the hierarchy.

View File

@ -290,6 +290,10 @@ class UpdateRunner implements UpdateTask, CompleteListener {
return _smgr.getSavedTorrentBitField(snark); return _smgr.getSavedTorrentBitField(snark);
} }
public boolean getSavedPreserveNamesSetting(Snark snark) {
return _smgr.getSavedPreserveNamesSetting(snark);
}
//////// end CompleteListener methods //////// end CompleteListener methods
private static String linkify(String url) { private static String linkify(String url) {

View File

@ -559,14 +559,17 @@ class BasicServlet extends HttpServlet
/** /**
* Simple version of URIUtil.encodePath() * Simple version of URIUtil.encodePath()
*/ */
protected static String encodePath(String path) throws MalformedURLException { protected static String encodePath(String path) /* throws MalformedURLException */ {
try { // Does NOT handle a ':' correctly, throws MUE.
URI uri = new URI(null, null, path, null); // Can't convert to %3a before hand or the % gets escaped
return uri.toString(); //try {
} catch (URISyntaxException use) { // URI uri = new URI(null, null, path, null);
// for ease of use, since a USE is not an IOE but a MUE is... // return uri.toString();
throw new MalformedURLException(use.getMessage()); //} 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);
} }
/** /**

View File

@ -9,6 +9,7 @@ import java.text.DecimalFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
@ -73,6 +74,7 @@ public class I2PSnarkServlet extends BasicServlet {
_nonce = _context.random().nextLong(); _nonce = _context.random().nextLong();
// limited protection against overwriting other config files or directories // limited protection against overwriting other config files or directories
// in case you named your war "router.war" // 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; String configName = _contextName;
if (!configName.equals(DEFAULT_NAME)) if (!configName.equals(DEFAULT_NAME))
configName = DEFAULT_NAME + '_' + _contextName; configName = DEFAULT_NAME + '_' + _contextName;
@ -942,7 +944,7 @@ public class I2PSnarkServlet extends BasicServlet {
if (taction != null) if (taction != null)
processTrackerForm(taction, req); processTrackerForm(taction, req);
} else if ("Create".equals(action)) { } else if ("Create".equals(action)) {
String baseData = req.getParameter("baseFile"); String baseData = req.getParameter("nofilter_baseFile");
if (baseData != null && baseData.trim().length() > 0) { if (baseData != null && baseData.trim().length() > 0) {
File baseFile = new File(baseData.trim()); File baseFile = new File(baseData.trim());
if (!baseFile.isAbsolute()) if (!baseFile.isAbsolute())
@ -954,6 +956,40 @@ public class I2PSnarkServlet extends BasicServlet {
// announceURL = announceURLOther; // announceURL = announceURLOther;
if (baseFile.exists()) { 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<Snark> 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")) if (announceURL.equals("none"))
announceURL = null; announceURL = null;
_lastAnnounceURL = announceURL; _lastAnnounceURL = announceURL;
@ -1006,7 +1042,9 @@ public class I2PSnarkServlet extends BasicServlet {
File torrentFile = new File(_manager.getDataDir(), s.getBaseName() + ".torrent"); File torrentFile = new File(_manager.getDataDir(), s.getBaseName() + ".torrent");
// FIXME is the storage going to stay around thanks to the info reference? // FIXME is the storage going to stay around thanks to the info reference?
// now add it, but don't automatically start it // 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()); _manager.addMessage(_("Torrent created for \"{0}\"", baseFile.getName()) + ": " + torrentFile.getAbsolutePath());
if (announceURL != null && !_manager.util().getOpenTrackers().contains(announceURL)) 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())); _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 // File type icon column
out.write("</td>\n<td>"); out.write("</td>\n<td>");
if (isValid) { if (isValid) {
@ -1407,7 +1445,7 @@ public class I2PSnarkServlet extends BasicServlet {
buf.append("\">"); buf.append("\">");
out.write(buf.toString()); out.write(buf.toString());
} }
out.write(basename); out.write(DataHelper.escapeHTML(basename));
if (remaining == 0 || isMultiFile) if (remaining == 0 || isMultiFile)
out.write("</a>"); out.write("</a>");
@ -1786,12 +1824,6 @@ public class I2PSnarkServlet extends BasicServlet {
} }
private void writeSeedForm(PrintWriter out, HttpServletRequest req, List<Tracker> sortedTrackers) throws IOException { private void writeSeedForm(PrintWriter out, HttpServletRequest req, List<Tracker> sortedTrackers) throws IOException {
String baseFile = req.getParameter("baseFile");
if (baseFile == null || baseFile.trim().length() <= 0)
baseFile = "";
else
baseFile = DataHelper.stripHTML(baseFile); // XSS
out.write("<a name=\"add\"></a><div class=\"newtorrentsection\"><div class=\"snarkNewTorrent\">\n"); out.write("<a name=\"add\"></a><div class=\"newtorrentsection\"><div class=\"snarkNewTorrent\">\n");
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file // *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
out.write("<form action=\"_post\" method=\"POST\">\n"); out.write("<form action=\"_post\" method=\"POST\">\n");
@ -1808,7 +1840,7 @@ public class I2PSnarkServlet extends BasicServlet {
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br>\n"); //out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br>\n");
out.write(_("Data to seed")); out.write(_("Data to seed"));
out.write(":<td>" out.write(":<td>"
+ "<input type=\"text\" name=\"baseFile\" size=\"58\" value=\"" + baseFile + "<input type=\"text\" name=\"nofilter_baseFile\" size=\"58\" value=\""
+ "\" spellcheck=\"false\" title=\""); + "\" spellcheck=\"false\" title=\"");
out.write(_("File or directory to seed (full path or within the directory {0} )", out.write(_("File or directory to seed (full path or within the directory {0} )",
_manager.getDataDir().getAbsolutePath() + File.separatorChar)); _manager.getDataDir().getAbsolutePath() + File.separatorChar));
@ -2211,26 +2243,35 @@ public class I2PSnarkServlet extends BasicServlet {
return ((bytes + 512*1024*1024)/(1024*1024*1024)) + "&nbsp;GB"; return ((bytes + 512*1024*1024)/(1024*1024*1024)) + "&nbsp;GB";
} }
/** @since 0.7.14 */ /**
* This is for a full URL. For a path only, use encodePath().
* @since 0.7.14
*/
static String urlify(String s) { static String urlify(String s) {
return urlify(s, 100); return urlify(s, 100);
} }
/** @since 0.9 */ /**
* This is for a full URL. For a path only, use encodePath().
* @since 0.9
*/
private static String urlify(String s, int max) { private static String urlify(String s, int max) {
StringBuilder buf = new StringBuilder(256); StringBuilder buf = new StringBuilder(256);
// browsers seem to work without doing this but let's be strict // browsers seem to work without doing this but let's be strict
String link = urlEncode(s); String link = urlEncode(s);
String display; String display;
if (s.length() <= max) if (s.length() <= max)
display = link; display = DataHelper.escapeHTML(link);
else else
display = urlEncode(s.substring(0, max)) + "&hellip;"; display = DataHelper.escapeHTML(s.substring(0, max)) + "&hellip;";
buf.append("<a href=\"").append(link).append("\">").append(display).append("</a>"); buf.append("<a href=\"").append(link).append("\">").append(display).append("</a>");
return buf.toString(); 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) { private static String urlEncode(String s) {
return s.replace(";", "%3B").replace("&", "&amp;").replace(" ", "%20") return s.replace(";", "%3B").replace("&", "&amp;").replace(" ", "%20")
.replace("<", "&lt;").replace(">", "&gt;") .replace("<", "&lt;").replace(">", "&gt;")
@ -2289,7 +2330,7 @@ public class I2PSnarkServlet extends BasicServlet {
* *
* Get the resource list as a HTML directory listing. * Get the resource list as a HTML directory listing.
* @param xxxr The Resource unused * @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 parent True if the parent directory should be included
* @param postParams map of POST parameters or null if not a POST * @param postParams map of POST parameters or null if not a POST
* @return String of HTML or null if postParams != null * @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<String, String[]> postParams) private String getListHTML(File xxxr, String base, boolean parent, Map<String, String[]> postParams)
throws IOException throws IOException
{ {
String title = decodePath(base); String decodedBase = decodePath(base);
String title = decodedBase;
String cpath = _contextPath + '/'; String cpath = _contextPath + '/';
if (title.startsWith(cpath)) if (title.startsWith(cpath))
title = title.substring(cpath.length()); title = title.substring(cpath.length());
@ -2351,7 +2393,7 @@ public class I2PSnarkServlet extends BasicServlet {
if (title.endsWith("/")) if (title.endsWith("/"))
title = title.substring(0, title.length() - 1); title = title.substring(0, title.length() - 1);
String directory = title; String directory = title;
title = _("Torrent") + ": " + title; title = _("Torrent") + ": " + DataHelper.escapeHTML(title);
buf.append(title); buf.append(title);
buf.append("</TITLE>").append(HEADER_A).append(_themePath).append(HEADER_B).append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">" + buf.append("</TITLE>").append(HEADER_A).append(_themePath).append(HEADER_B).append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">" +
"</HEAD><BODY>\n<center><div class=\"snarknavbar\"><a href=\"").append(_contextPath).append("/\" title=\"Torrents\""); "</HEAD><BODY>\n<center><div class=\"snarknavbar\"><a href=\"").append(_contextPath).append("/\" title=\"Torrents\"");
@ -2376,22 +2418,22 @@ public class I2PSnarkServlet extends BasicServlet {
buf.append("<tr><th><b>") buf.append("<tr><th><b>")
.append(_("Torrent")) .append(_("Torrent"))
.append(":</b> ") .append(":</b> ")
.append(snark.getBaseName()) .append(DataHelper.escapeHTML(snark.getBaseName()))
.append("</th></tr>\n"); .append("</th></tr>\n");
String fullPath = snark.getName(); String fullPath = snark.getName();
String baseName = urlEncode((new File(fullPath)).getName()); String baseName = encodePath((new File(fullPath)).getName());
buf.append("<tr><td>") buf.append("<tr><td>")
.append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>") .append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>")
.append(_("Torrent file")) .append(_("Torrent file"))
.append(":</b> <a href=\"").append(_contextPath).append('/').append(baseName).append("\">") .append(":</b> <a href=\"").append(_contextPath).append('/').append(baseName).append("\">")
.append(fullPath) .append(DataHelper.escapeHTML(fullPath))
.append("</a></td></tr>\n"); .append("</a></td></tr>\n");
buf.append("<tr><td>") buf.append("<tr><td>")
.append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>") .append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>")
.append(_("Data location")) .append(_("Data location"))
.append(":</b> ") .append(":</b> ")
.append(urlEncode(snark.getStorage().getBase().getPath())) .append(DataHelper.escapeHTML(snark.getStorage().getBase().getPath()))
.append("</td></tr>\n"); .append("</td></tr>\n");
String announce = null; String announce = null;
@ -2593,7 +2635,7 @@ public class I2PSnarkServlet extends BasicServlet {
.append("\"></th>\n"); .append("\"></th>\n");
buf.append("</tr>\n</thead>\n"); buf.append("</tr>\n</thead>\n");
buf.append("<tr><td colspan=\"" + (showPriority ? '5' : '4') + "\" class=\"ParentDir\"><A HREF=\""); buf.append("<tr><td colspan=\"" + (showPriority ? '5' : '4') + "\" class=\"ParentDir\"><A HREF=\"");
buf.append(addPaths(base,"../")); URIUtil.encodePath(buf, addPaths(decodedBase,"../"));
buf.append("\"><img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("up.png\"> ") buf.append("\"><img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("up.png\"> ")
.append(_("Up to higher level directory")) .append(_("Up to higher level directory"))
.append("</A></td></tr>\n"); .append("</A></td></tr>\n");
@ -2604,7 +2646,7 @@ public class I2PSnarkServlet extends BasicServlet {
boolean showSaveButton = false; boolean showSaveButton = false;
for (int i=0 ; i< ls.length ; i++) 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) // 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 // http://jira.codehaus.org/browse/JETTY-361?page=com.atlassian.jira.plugin.system.issuetabpanels%3Achangehistory-tabpanel#issue-tabs
// See resource.diff attachment // 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("/")) if (item.isDirectory() && !path.endsWith("/"))
path=addPaths(path,"/"); path=addPaths(path,"/");
path = urlEncode(path); path = encodePath(path);
String icon = toIcon(item); String icon = toIcon(item);
buf.append("<TD class=\"snarkFileIcon\">"); buf.append("<TD class=\"snarkFileIcon\">");
@ -2677,7 +2719,7 @@ public class I2PSnarkServlet extends BasicServlet {
buf.append("</TD><TD class=\"snarkFileName\">"); buf.append("</TD><TD class=\"snarkFileName\">");
if (complete) if (complete)
buf.append("<a href=\"").append(path).append("\">"); buf.append("<a href=\"").append(path).append("\">");
buf.append(item.getName().replace("&", "&amp;")); buf.append(DataHelper.escapeHTML(item.getName()));
if (complete) if (complete)
buf.append("</a>"); buf.append("</a>");
buf.append("</TD><TD ALIGN=right class=\"snarkFileSize\">"); buf.append("</TD><TD ALIGN=right class=\"snarkFileSize\">");
@ -2808,6 +2850,36 @@ public class I2PSnarkServlet extends BasicServlet {
} }
} }
snark.updatePiecePriorities(); 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;
} }
} }

View File

@ -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.
* <P>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<path.length();i++)
{
char c=path.charAt(i);
switch(c)
{
case '%':
case '?':
case ';':
case '#':
case '\'':
case '"':
case '<':
case '>':
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<bytes.length;i++)
{
byte c=bytes[i];
switch(c)
{
case '%':
buf.append("%25");
continue;
case '?':
buf.append("%3F");
continue;
case ';':
buf.append("%3B");
continue;
case '#':
buf.append("%23");
continue;
case '"':
buf.append("%22");
continue;
case '\'':
buf.append("%27");
continue;
case '<':
buf.append("%3C");
continue;
case '>':
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<path.length();i++)
{
char c=path.charAt(i);
switch(c)
{
case '%':
buf.append("%25");
continue;
case '?':
buf.append("%3F");
continue;
case ';':
buf.append("%3B");
continue;
case '#':
buf.append("%23");
continue;
case '"':
buf.append("%22");
continue;
case '\'':
buf.append("%27");
continue;
case '<':
buf.append("%3C");
continue;
case '>':
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));
}
}

View File

@ -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 2014-08-18 zzz
* i2psnark: * i2psnark:
- Don't send HTML-only headers for icons - Don't send HTML-only headers for icons

View File

@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */ /** deprecated */
public final static String ID = "Monotone"; public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION; public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 6; public final static long BUILD = 7;
/** for example "-test" */ /** for example "-test" */
public final static String EXTRA = ""; public final static String EXTRA = "";