* 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
public long getSavedTorrentTime(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;
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;

View File

@ -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;

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_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("<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)
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)) + ')');
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 {

View File

@ -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<List<String>> files = metainfo.getFiles();
int sz = files != null ? files.size() : 1;
_torrentFiles = new ArrayList<TorrentFile>(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<String> names, boolean areFilesPublic) throws IOException
private File createFileFromNames(File base, List<String> names, boolean areFilesPublic) throws IOException
{
File f = null;
Iterator<String> it = names.iterator();
while (it.hasNext())
{
String name = filterName(it.next());
String name = optFilterName(it.next());
if (it.hasNext())
{
// Another dir in the hierarchy.

View File

@ -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) {

View File

@ -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);
}
/**

View File

@ -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<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"))
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("</td>\n<td>");
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("</a>");
@ -1786,12 +1824,6 @@ public class I2PSnarkServlet extends BasicServlet {
}
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");
// *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");
@ -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(_("Data to seed"));
out.write(":<td>"
+ "<input type=\"text\" name=\"baseFile\" size=\"58\" value=\"" + baseFile
+ "<input type=\"text\" name=\"nofilter_baseFile\" size=\"58\" value=\""
+ "\" spellcheck=\"false\" title=\"");
out.write(_("File or directory to seed (full path or within the directory {0} )",
_manager.getDataDir().getAbsolutePath() + File.separatorChar));
@ -2211,26 +2243,35 @@ public class I2PSnarkServlet extends BasicServlet {
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) {
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) {
StringBuilder buf = new StringBuilder(256);
// browsers seem to work without doing this but let's be strict
String link = urlEncode(s);
String display;
if (s.length() <= max)
display = link;
display = DataHelper.escapeHTML(link);
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>");
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("&", "&amp;").replace(" ", "%20")
.replace("<", "&lt;").replace(">", "&gt;")
@ -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<String, String[]> 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("</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\"");
@ -2376,22 +2418,22 @@ public class I2PSnarkServlet extends BasicServlet {
buf.append("<tr><th><b>")
.append(_("Torrent"))
.append(":</b> ")
.append(snark.getBaseName())
.append(DataHelper.escapeHTML(snark.getBaseName()))
.append("</th></tr>\n");
String fullPath = snark.getName();
String baseName = urlEncode((new File(fullPath)).getName());
String baseName = encodePath((new File(fullPath)).getName());
buf.append("<tr><td>")
.append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>")
.append(_("Torrent file"))
.append(":</b> <a href=\"").append(_contextPath).append('/').append(baseName).append("\">")
.append(fullPath)
.append(DataHelper.escapeHTML(fullPath))
.append("</a></td></tr>\n");
buf.append("<tr><td>")
.append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>")
.append(_("Data location"))
.append(":</b> ")
.append(urlEncode(snark.getStorage().getBase().getPath()))
.append(DataHelper.escapeHTML(snark.getStorage().getBase().getPath()))
.append("</td></tr>\n");
String announce = null;
@ -2593,7 +2635,7 @@ public class I2PSnarkServlet extends BasicServlet {
.append("\"></th>\n");
buf.append("</tr>\n</thead>\n");
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\"> ")
.append(_("Up to higher level directory"))
.append("</A></td></tr>\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("<TD class=\"snarkFileIcon\">");
@ -2677,7 +2719,7 @@ public class I2PSnarkServlet extends BasicServlet {
buf.append("</TD><TD class=\"snarkFileName\">");
if (complete)
buf.append("<a href=\"").append(path).append("\">");
buf.append(item.getName().replace("&", "&amp;"));
buf.append(DataHelper.escapeHTML(item.getName()));
if (complete)
buf.append("</a>");
buf.append("</TD><TD ALIGN=right class=\"snarkFileSize\">");
@ -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;
}
}

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