diff --git a/Makefile b/Makefile index 16a67a5..3a10a24 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ MultiThread = Yes +HttpTorrent = Yes InstallPrefix = /usr/local/bin PROGNAME = torrent-verify @@ -8,10 +9,15 @@ CPPFLAGS = -DPROGRAM_NAME='"$(PROGNAME)"' -DBUILD_INFO \ -DBUILD_HASH="\"`git rev-parse --abbrev-ref HEAD` -> `git rev-parse --short HEAD`\"" -DBUILD_DATE="\"`date -I`\"" ifeq ($(MultiThread), Yes) -CFLAGS += -lpthread +LDLIBS += -lpthread CPPFLAGS += -DMT endif +ifeq ($(HttpTorrent), Yes) +LDLIBS += -lcurl +CPPFLAGS += -DHTTP_TORRENT=1 +endif + SOURCE = $(wildcard subm/heapless-bencode/*.c) $(wildcard src/*.c) #OBJ = $(addsuffix .o,$(basename $(SOURCE))) OBJS = $(SOURCE:.c=.o) @@ -25,7 +31,8 @@ uninstall: rm -f -- $(InstallPrefix)/$(PROGNAME) $(PROGNAME): $(OBJS) - $(CC) -o $@ $+ $(CFLAGS) $(CPPFLAGS) + $(CC) -o $@ $+ $(CFLAGS) $(CPPFLAGS) $(LDLIBS) + clean: -rm -- $(OBJS) $(PROGNAME) diff --git a/src/main.c b/src/main.c index 1891324..24abfd6 100644 --- a/src/main.c +++ b/src/main.c @@ -42,6 +42,9 @@ BUILD_HASH " (" BUILD_DATE ")\n" #ifdef MT "MultiThread support\n" #endif +#ifdef HTTP_TORRENT +"HTTP Torrent support\n" +#endif #endif ); exit(EXIT_SUCCESS); @@ -55,7 +58,11 @@ int main(int argc, char** argv) { help(); if (optind >= argc) { - fprintf(stderr, "Provide at least one torrent file\n"); + fprintf(stderr, "Provide at least one torrent file" +#ifdef HTTP_TORRENT + " or an http link to a torrent file" +#endif + "\n"); usage(); } diff --git a/src/metainfo.c b/src/metainfo.c index 065a7a9..7b04d59 100644 --- a/src/metainfo.c +++ b/src/metainfo.c @@ -3,18 +3,30 @@ #include #include #include +#include #include "sha1.h" +#ifdef HTTP_TORRENT +#include +#endif + /* 128 MiB */ #define MAX_TORRENT_SIZE 128*1024*1024 +#ifdef HTTP_TORRENT +struct http_metainfo { + int64_t c_size, max_size; + char *data; +}; +#endif + /* * Read the file in memory, and return the pointer to it (which needs to be * freed) in out_contents and the size in out_size. If the file is too big, * fail. Returns 0 on success and an errno on fail. */ -static int metainfo_read(const char* path, char** out_contents, int* out_size) { +static int metainfo_read_file(const char* path, char** out_contents, int* out_size) { int ret = 0; FILE* f = NULL; long size = 0; @@ -67,6 +79,146 @@ end: return ret; } +#ifdef HTTP_TORRENT +static size_t metainfo_read_http_headercb(char *buf, size_t size, size_t n, + void *data) { + struct http_metainfo *h_meta = (struct http_metainfo*)data; + const char *cl_header = "content-length"; + char *sep = memchr(buf, ':', size * n); + + if (!sep || (sep - buf) != strlen(cl_header)) + goto end; + + if (strncmp(cl_header, buf, strlen(cl_header)) == 0) { + char *endp; + int64_t len; + + sep += 2; + errno = 0; + len = strtoll(sep, &endp, 10); + if (sep != endp && errno == 0) + h_meta->max_size = len; + } + +end: + return size * n; +} + +static size_t metainfo_read_http_writecb(char *ptr, size_t size, size_t n, + void *data) { + struct http_metainfo *h_meta = (struct http_metainfo*)data; + size_t bytes = size * n; + + if (h_meta->max_size >= MAX_TORRENT_SIZE) + goto fail; /* Stop processing if too large */ + + if (h_meta->max_size == -1) { + /* If no content-length, make a dinamic array */ + h_meta->max_size = 0; + } else if (!h_meta->data) { + /* We have content-size, and we haven't alloced yet */ + h_meta->data = malloc(h_meta->max_size); + } + + size_t free_space = h_meta->max_size - h_meta->c_size; + + if (bytes > free_space) { + while (bytes > free_space) { + if (h_meta->max_size == 0) + h_meta->max_size = 2048; + + h_meta->max_size *= 2; + free_space = h_meta->max_size - h_meta->c_size; + } + + void *n_data = realloc(h_meta->data, h_meta->max_size); + if (!n_data) + goto fail; + + h_meta->data = n_data; + } + + if (h_meta->c_size + bytes > MAX_TORRENT_SIZE) + goto fail; + + memcpy(&h_meta->data[h_meta->c_size], ptr, bytes); + h_meta->c_size += bytes; + + return bytes; + +fail: + if (h_meta->data) + free(h_meta->data); + return 0; +} +#endif + +/* + * Download the file in memory, and return the pointer to it (which needs to be + * freed) in out_contents and the size in out_size. If the file is too big, + * fail. Returns 0 on success and an errno on fail. + */ +static int metainfo_read_http(const char* url, char** out_contents, int* out_size) { +#ifdef HTTP_TORRENT + char errbuf[CURL_ERROR_SIZE]; + CURL *curl = curl_easy_init(); + CURLcode res; + struct http_metainfo h_meta = { + .c_size = 0, + .max_size = -1, + .data = NULL, + }; + + if (!curl) { + return -1; /* Not errno but eh */ + } + + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &h_meta); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &h_meta); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, metainfo_read_http_headercb); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, metainfo_read_http_writecb); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); + curl_easy_setopt(curl, CURLOPT_URL, url); + + res = curl_easy_perform(curl); + + curl_easy_cleanup(curl); + + if (res) { + fprintf(stderr, "libCurl error: %s\n", errbuf); + return res; + } + + *out_contents = h_meta.data; + *out_size = h_meta.c_size; /* caller will free */ + + return 0; +#else + __builtin_unreachable(); /* Could be better tbh */ +#endif +} + +/* + * Read/download the file in memory, and return the pointer to it (which needs to be + * freed) in out_contents and the size in out_size. If the file is too big, + * fail. Returns 0 on success and an errno on fail. + */ +static int metainfo_read(const char* path, char** out_contents, int* out_size) { +#ifdef HTTP_TORRENT + const int has_http = 1; +#else + const int has_http = 0; +#endif + + if (strncmp(path, "http", 4) == 0) { + if (!has_http) + return ENOPROTOOPT; + return metainfo_read_http(path, out_contents, out_size); + } + + return metainfo_read_file(path, out_contents, out_size); +} + static int len_strcmp(const char* s1, int s1_len, const char* s2, int s2_len) { return (s1_len == s2_len) && (strncmp(s1, s2, s1_len) == 0); } @@ -173,8 +325,8 @@ static int metainfo_parse(metainfo_t* metai, bencode_t* benc) { } int metainfo_create(metainfo_t* metai, const char* path) { - char* bytes; - int size; + char* bytes = NULL; + int size = 0; int ret = metainfo_read(path, &bytes, &size); if (ret) { fprintf(stderr, "Metafile reading failed: %s\n", \