forked from I2P_Developers/i2p.i2p
* 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:
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)) + " 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)) + "…";
|
||||
display = DataHelper.escapeHTML(s.substring(0, max)) + "…";
|
||||
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("&", "&").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<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\" > <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\" > <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("&", "&"));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
206
apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java
Normal file
206
apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java
Normal 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user