transmission/libtransmission/metainfo.c

705 lines
17 KiB
C
Raw Normal View History

/*
* This file Copyright (C) 2007-2014 Mnemosyne LLC
2006-07-16 19:39:23 +00:00
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
2006-07-16 19:39:23 +00:00
*
*/
2006-07-16 19:39:23 +00:00
2007-11-09 20:07:52 +00:00
#include <assert.h>
#include <string.h> /* strlen () */
#include <event2/buffer.h>
2006-07-16 19:39:23 +00:00
#include "transmission.h"
#include "crypto-utils.h" /* tr_sha1 */
#include "file.h"
#include "log.h"
2007-07-09 20:10:42 +00:00
#include "metainfo.h"
#include "platform.h" /* tr_getTorrentDir () */
#include "session.h"
#include "utils.h"
#include "variant.h"
2006-07-16 19:39:23 +00:00
2007-10-20 15:17:36 +00:00
/***
****
***/
#ifdef _WIN32
#define PATH_DELIMITER_CHARS "/\\"
#else
#define PATH_DELIMITER_CHARS "/"
#endif
static inline bool char_is_path_separator(char c)
{
return strchr(PATH_DELIMITER_CHARS, c) != NULL;
}
char* tr_metainfoGetBasename(const tr_info* inf)
{
size_t i;
const char* name = inf->originalName;
const size_t name_len = strlen(name);
char* ret = tr_strdup_printf("%s.%16.16s", name, inf->hashString);
for (i = 0; i < name_len; ++i)
{
if (char_is_path_separator(ret[i]))
{
ret[i] = '_';
}
}
return ret;
}
static char* getTorrentFilename(const tr_session* session, const tr_info* inf)
2008-04-14 11:52:50 +00:00
{
char* base = tr_metainfoGetBasename(inf);
char* filename = tr_strdup_printf("%s" TR_PATH_DELIMITER_STR "%s.torrent", tr_getTorrentDir(session), base);
tr_free(base);
return filename;
2008-04-14 11:52:50 +00:00
}
2008-08-20 21:01:17 +00:00
/***
****
***/
static bool path_component_is_suspicious(const char* component)
{
return (component == NULL) || (strpbrk(component, PATH_DELIMITER_CHARS) != NULL) || (strcmp(component, ".") == 0) ||
(strcmp(component, "..") == 0);
}
static bool getfile(char** setme, const char* root, tr_variant* path, struct evbuffer* buf)
2008-08-20 21:01:17 +00:00
{
bool success = false;
size_t root_len = 0;
2008-08-20 21:01:17 +00:00
*setme = NULL;
/* root's already been checked by caller */
assert(!path_component_is_suspicious(root));
if (tr_variantIsList(path))
2008-08-20 21:01:17 +00:00
{
int i;
const int n = tr_variantListSize(path);
2008-08-20 21:01:17 +00:00
success = true;
evbuffer_drain(buf, evbuffer_get_length(buf));
root_len = strlen(root);
evbuffer_add(buf, root, root_len);
for (i = 0; i < n; i++)
{
size_t len;
const char* str;
if (!tr_variantGetStr(tr_variantListChild(path, i), &str, &len) || path_component_is_suspicious(str))
{
success = false;
break;
2008-08-20 21:01:17 +00:00
}
if (!*str)
{
continue;
}
evbuffer_add(buf, TR_PATH_DELIMITER_STR, 1);
evbuffer_add(buf, str, len);
}
2008-08-20 21:01:17 +00:00
}
if (success && (evbuffer_get_length(buf) <= root_len))
{
success = false;
}
if (success)
{
*setme = tr_utf8clean((char*)evbuffer_pullup(buf, -1), evbuffer_get_length(buf));
/*fprintf (stderr, "[%s]\n", *setme);*/
}
return success;
2008-08-20 21:01:17 +00:00
}
static const char* parseFiles(tr_info* inf, tr_variant* files, const tr_variant* length)
2008-08-20 21:01:17 +00:00
{
int64_t len;
2008-08-20 21:01:17 +00:00
inf->totalSize = 0;
2008-08-20 21:01:17 +00:00
if (tr_variantIsList(files)) /* multi-file mode */
2008-08-20 21:01:17 +00:00
{
tr_file_index_t i;
struct evbuffer* buf;
const char* result;
if (path_component_is_suspicious(inf->name))
{
return "path";
}
buf = evbuffer_new();
result = NULL;
2008-08-21 16:12:17 +00:00
inf->isFolder = true;
inf->fileCount = tr_variantListSize(files);
inf->files = tr_new0(tr_file, inf->fileCount);
2008-08-20 21:01:17 +00:00
for (i = 0; i < inf->fileCount; i++)
2008-08-20 21:01:17 +00:00
{
tr_variant* file;
tr_variant* path;
2008-08-20 21:01:17 +00:00
file = tr_variantListChild(files, i);
if (!tr_variantIsDict(file))
{
result = "files";
break;
}
2008-08-20 21:01:17 +00:00
if (!tr_variantDictFindList(file, TR_KEY_path_utf_8, &path))
{
if (!tr_variantDictFindList(file, TR_KEY_path, &path))
{
result = "path";
break;
}
}
2008-08-20 21:01:17 +00:00
if (!getfile(&inf->files[i].name, inf->name, path, buf))
{
result = "path";
break;
}
2008-08-20 21:01:17 +00:00
if (!tr_variantDictFindInt(file, TR_KEY_length, &len))
{
result = "length";
break;
}
2008-08-20 21:01:17 +00:00
inf->files[i].length = len;
inf->totalSize += len;
2008-08-20 21:01:17 +00:00
}
evbuffer_free(buf);
return result;
2008-08-20 21:01:17 +00:00
}
else if (tr_variantGetInt(length, &len)) /* single-file mode */
2008-08-20 21:01:17 +00:00
{
if (path_component_is_suspicious(inf->name))
{
return "path";
}
inf->isFolder = false;
inf->fileCount = 1;
inf->files = tr_new0(tr_file, 1);
inf->files[0].name = tr_strdup(inf->name);
inf->files[0].length = len;
inf->totalSize += len;
2008-08-20 21:01:17 +00:00
}
else
2008-08-20 21:01:17 +00:00
{
return "length";
2008-08-20 21:01:17 +00:00
}
return NULL;
2008-08-20 21:01:17 +00:00
}
static char* tr_convertAnnounceToScrape(const char* announce)
2008-04-24 03:26:36 +00:00
{
char* scrape = NULL;
const char* s;
/* To derive the scrape URL use the following steps:
* Begin with the announce URL. Find the last '/' in it.
* If the text immediately following that '/' isn't 'announce'
* it will be taken as a sign that that tracker doesn't support
* the scrape convention. If it does, substitute 'scrape' for
* 'announce' to find the scrape page. */
if (((s = strrchr(announce, '/')) != NULL) && strncmp(++s, "announce", 8) == 0)
2008-04-24 03:26:36 +00:00
{
const char* prefix = announce;
const size_t prefix_len = s - announce;
const char* suffix = s + 8;
const size_t suffix_len = strlen(suffix);
const size_t alloc_len = prefix_len + 6 + suffix_len + 1;
char* walk = scrape = tr_new(char, alloc_len);
memcpy(walk, prefix, prefix_len);
walk += prefix_len;
memcpy(walk, "scrape", 6);
walk += 6;
memcpy(walk, suffix, suffix_len);
walk += suffix_len;
*walk++ = '\0';
assert(walk - scrape == (int)alloc_len);
2008-04-24 03:26:36 +00:00
}
/* Some torrents with UDP annouce URLs don't have /announce. */
else if (strncmp(announce, "udp:", 4) == 0)
{
scrape = tr_strdup(announce);
}
2008-04-24 03:26:36 +00:00
return scrape;
2008-04-24 03:26:36 +00:00
}
static const char* getannounce(tr_info* inf, tr_variant* meta)
2008-04-24 03:26:36 +00:00
{
size_t len;
const char* str;
tr_tracker_info* trackers = NULL;
int trackerCount = 0;
tr_variant* tiers;
/* Announce-list */
if (tr_variantDictFindList(meta, TR_KEY_announce_list, &tiers))
2008-04-24 03:26:36 +00:00
{
int n;
int i, j, validTiers;
const int numTiers = tr_variantListSize(tiers);
n = 0;
2008-04-24 03:26:36 +00:00
for (i = 0; i < numTiers; i++)
{
n += tr_variantListSize(tr_variantListChild(tiers, i));
}
2008-04-24 03:26:36 +00:00
trackers = tr_new0(tr_tracker_info, n);
2008-04-24 03:26:36 +00:00
for (i = 0, validTiers = 0; i < numTiers; i++)
{
tr_variant* tier = tr_variantListChild(tiers, i);
const int tierSize = tr_variantListSize(tier);
bool anyAdded = false;
for (j = 0; j < tierSize; j++)
{
if (tr_variantGetStr(tr_variantListChild(tier, j), &str, &len))
{
char* url = tr_strstrip(tr_strndup(str, len));
if (!tr_urlIsValidTracker(url))
{
tr_free(url);
}
else
{
tr_tracker_info* t = trackers + trackerCount;
t->tier = validTiers;
t->announce = url;
t->scrape = tr_convertAnnounceToScrape(url);
t->id = trackerCount;
anyAdded = true;
++trackerCount;
}
2008-04-24 03:26:36 +00:00
}
}
2009-08-10 20:04:08 +00:00
if (anyAdded)
{
++validTiers;
}
2008-04-24 03:26:36 +00:00
}
/* did we use any of the tiers? */
if (!trackerCount)
{
tr_free(trackers);
trackers = NULL;
2008-04-24 03:26:36 +00:00
}
}
/* Regular announce value */
if (!trackerCount && tr_variantDictFindStr(meta, TR_KEY_announce, &str, &len))
2008-04-24 03:26:36 +00:00
{
char* url = tr_strstrip(tr_strndup(str, len));
if (!tr_urlIsValidTracker(url))
{
tr_free(url);
}
else
{
trackers = tr_new0(tr_tracker_info, 1);
trackers[trackerCount].tier = 0;
trackers[trackerCount].announce = url;
trackers[trackerCount].scrape = tr_convertAnnounceToScrape(url);
trackers[trackerCount].id = 0;
trackerCount++;
/*fprintf (stderr, "single announce: [%s]\n", url);*/
}
2008-04-24 03:26:36 +00:00
}
inf->trackers = trackers;
inf->trackerCount = trackerCount;
2008-04-24 03:26:36 +00:00
return NULL;
2008-08-20 21:01:17 +00:00
}
/**
* @brief Ensure that the URLs for multfile torrents end in a slash.
*
* See http://bittorrent.org/beps/bep_0019.html#metadata-extension
* for background on how the trailing slash is used for "url-list"
* fields.
*
* This function is to workaround some .torrent generators, such as
* mktorrent and very old versions of utorrent, that don't add the
* trailing slash for multifile torrents if omitted by the end user.
*/
static char* fix_webseed_url(const tr_info* inf, const char* url_in)
{
size_t len;
char* url;
char* ret = NULL;
url = tr_strdup(url_in);
tr_strstrip(url);
len = strlen(url);
if (tr_urlIsValid(url, len))
{
if ((inf->fileCount > 1) && (len > 0) && (url[len - 1] != '/'))
{
ret = tr_strdup_printf("%*.*s/", (int)len, (int)len, url);
}
else
{
ret = tr_strndup(url, len);
}
}
tr_free(url);
return ret;
}
static void geturllist(tr_info* inf, tr_variant* meta)
2008-08-20 21:01:17 +00:00
{
tr_variant* urls;
const char* url;
2008-08-20 21:01:17 +00:00
if (tr_variantDictFindList(meta, TR_KEY_url_list, &urls))
2008-08-20 21:01:17 +00:00
{
int i;
const int n = tr_variantListSize(urls);
2008-08-20 21:01:17 +00:00
inf->webseedCount = 0;
inf->webseeds = tr_new0(char*, n);
for (i = 0; i < n; i++)
{
if (tr_variantGetStr(tr_variantListChild(urls, i), &url, NULL))
{
char* fixed_url = fix_webseed_url(inf, url);
if (fixed_url != NULL)
{
inf->webseeds[inf->webseedCount++] = fixed_url;
}
}
}
2008-08-20 21:01:17 +00:00
}
else if (tr_variantDictFindStr(meta, TR_KEY_url_list, &url, NULL)) /* handle single items in webseeds */
{
char* fixed_url = fix_webseed_url(inf, url);
if (fixed_url != NULL)
{
inf->webseedCount = 1;
inf->webseeds = tr_new0(char*, 1);
inf->webseeds[0] = fixed_url;
}
}
2008-04-24 03:26:36 +00:00
}
static const char* tr_metainfoParseImpl(const tr_session* session, tr_info* inf, bool* hasInfoDict, size_t* infoDictLength,
const tr_variant* meta_in)
2007-12-21 22:18:40 +00:00
{
int64_t i;
size_t len;
const char* str;
const uint8_t* raw;
tr_variant* d;
tr_variant* infoDict = NULL;
tr_variant* meta = (tr_variant*)meta_in;
bool b;
bool isMagnet = false;
/* info_hash: urlencoded 20-byte SHA1 hash of the value of the info key
* from the Metainfo file. Note that the value will be a bencoded
* dictionary, given the definition of the info key above. */
b = tr_variantDictFindDict(meta, TR_KEY_info, &infoDict);
if (hasInfoDict != NULL)
{
*hasInfoDict = b;
}
if (!b)
{
/* no info dictionary... is this a magnet link? */
if (tr_variantDictFindDict(meta, TR_KEY_magnet_info, &d))
{
isMagnet = true;
/* get the info-hash */
if (!tr_variantDictFindRaw(d, TR_KEY_info_hash, &raw, &len))
{
return "info_hash";
}
if (len != SHA_DIGEST_LENGTH)
{
return "info_hash";
}
memcpy(inf->hash, raw, len);
tr_sha1_to_hex(inf->hashString, inf->hash);
/* maybe get the display name */
if (tr_variantDictFindStr(d, TR_KEY_display_name, &str, &len))
{
tr_free(inf->name);
tr_free(inf->originalName);
inf->name = tr_strndup(str, len);
inf->originalName = tr_strndup(str, len);
}
if (!inf->name)
{
inf->name = tr_strdup(inf->hashString);
}
if (!inf->originalName)
{
inf->originalName = tr_strdup(inf->hashString);
}
}
else /* not a magnet link and has no info dict... */
{
return "info";
}
}
else
{
size_t len;
char* bstr = tr_variantToStr(infoDict, TR_VARIANT_FMT_BENC, &len);
tr_sha1(inf->hash, bstr, (int)len, NULL);
tr_sha1_to_hex(inf->hashString, inf->hash);
if (infoDictLength != NULL)
{
*infoDictLength = len;
}
tr_free(bstr);
}
/* name */
if (!isMagnet)
{
len = 0;
if (!tr_variantDictFindStr(infoDict, TR_KEY_name_utf_8, &str, &len))
{
if (!tr_variantDictFindStr(infoDict, TR_KEY_name, &str, &len))
{
str = "";
}
}
if (!str || !*str)
{
return "name";
}
tr_free(inf->name);
tr_free(inf->originalName);
inf->name = tr_utf8clean(str, len);
inf->originalName = tr_strdup(inf->name);
}
/* comment */
len = 0;
if (!tr_variantDictFindStr(meta, TR_KEY_comment_utf_8, &str, &len))
{
if (!tr_variantDictFindStr(meta, TR_KEY_comment, &str, &len))
{
str = "";
}
}
tr_free(inf->comment);
inf->comment = tr_utf8clean(str, len);
/* created by */
len = 0;
if (!tr_variantDictFindStr(meta, TR_KEY_created_by_utf_8, &str, &len))
{
if (!tr_variantDictFindStr(meta, TR_KEY_created_by, &str, &len))
{
str = "";
}
}
2009-11-24 02:16:31 +00:00
tr_free(inf->creator);
inf->creator = tr_utf8clean(str, len);
2009-11-24 02:16:31 +00:00
/* creation date */
if (!tr_variantDictFindInt(meta, TR_KEY_creation_date, &i))
{
i = 0;
2007-12-21 22:18:40 +00:00
}
inf->dateCreated = i;
/* private */
if (!tr_variantDictFindInt(infoDict, TR_KEY_private, &i))
{
if (!tr_variantDictFindInt(meta, TR_KEY_private, &i))
{
i = 0;
}
}
2006-07-16 19:39:23 +00:00
inf->isPrivate = i != 0;
/* piece length */
if (!isMagnet)
{
if (!tr_variantDictFindInt(infoDict, TR_KEY_piece_length, &i) || (i < 1))
{
return "piece length";
}
inf->pieceSize = i;
}
2008-08-20 21:01:17 +00:00
/* pieces */
if (!isMagnet)
{
if (!tr_variantDictFindRaw(infoDict, TR_KEY_pieces, &raw, &len))
{
return "pieces";
}
if (len % SHA_DIGEST_LENGTH)
{
return "pieces";
}
inf->pieceCount = len / SHA_DIGEST_LENGTH;
inf->pieces = tr_new0(tr_piece, inf->pieceCount);
for (i = 0; i < inf->pieceCount; i++)
{
memcpy(inf->pieces[i].hash, &raw[i * SHA_DIGEST_LENGTH], SHA_DIGEST_LENGTH);
}
}
2008-08-20 19:21:57 +00:00
/* files */
if (!isMagnet)
{
if ((str = parseFiles(inf, tr_variantDictFind(infoDict, TR_KEY_files), tr_variantDictFind(infoDict, TR_KEY_length))))
{
return str;
}
if (!inf->fileCount || !inf->totalSize)
{
return "files";
}
if ((uint64_t)inf->pieceCount != (inf->totalSize + inf->pieceSize - 1) / inf->pieceSize)
{
return "files";
}
}
/* get announce or announce-list */
if ((str = getannounce(inf, meta)))
{
return str;
}
2006-07-16 19:39:23 +00:00
/* get the url-list */
geturllist(inf, meta);
/* filename of Transmission's copy */
tr_free(inf->torrent);
inf->torrent = session ? getTorrentFilename(session, inf) : NULL;
2008-04-14 11:52:50 +00:00
return NULL;
2008-08-20 21:01:17 +00:00
}
bool tr_metainfoParse(const tr_session* session, const tr_variant* meta_in, tr_info* inf, bool* hasInfoDict,
size_t* infoDictLength)
2008-08-20 21:01:17 +00:00
{
const char* badTag = tr_metainfoParseImpl(session, inf, hasInfoDict, infoDictLength, meta_in);
const bool success = badTag == NULL;
if (badTag)
2008-08-20 21:01:17 +00:00
{
tr_logAddNamedError(inf->name, _("Invalid metadata entry \"%s\""), badTag);
tr_metainfoFree(inf);
2008-08-20 21:01:17 +00:00
}
return success;
2006-07-16 19:39:23 +00:00
}
void tr_metainfoFree(tr_info* inf)
{
unsigned int i;
tr_file_index_t ff;
for (i = 0; i < inf->webseedCount; i++)
{
tr_free(inf->webseeds[i]);
}
for (ff = 0; ff < inf->fileCount; ff++)
{
tr_free(inf->files[ff].name);
}
tr_free(inf->webseeds);
tr_free(inf->pieces);
tr_free(inf->files);
tr_free(inf->comment);
tr_free(inf->creator);
tr_free(inf->torrent);
tr_free(inf->originalName);
tr_free(inf->name);
for (i = 0; i < inf->trackerCount; i++)
{
tr_free(inf->trackers[i].announce);
tr_free(inf->trackers[i].scrape);
}
tr_free(inf->trackers);
memset(inf, '\0', sizeof(tr_info));
}
void tr_metainfoRemoveSaved(const tr_session* session, const tr_info* inf)
2006-07-16 19:39:23 +00:00
{
char* filename;
2006-07-16 19:39:23 +00:00
filename = getTorrentFilename(session, inf);
tr_sys_path_remove(filename, NULL);
tr_free(filename);
}