From 84a3d271df3760cadaa2acb07b7d66397a251ec5 Mon Sep 17 00:00:00 2001 From: Andrew Pamment Date: Tue, 23 Jan 2018 20:57:58 +1000 Subject: [PATCH] first attempt at www downlods WIP --- STRINGS.CHANGES | 28 ++ deps/hashids/hashids.c | 831 +++++++++++++++++++++++++++++++++++++++ deps/hashids/hashids.h | 122 ++++++ dist/magicka.strings | 5 + dist/menus/file.mnu | 3 + src/Makefile.freebsd.WWW | 2 +- src/Makefile.linux.WWW | 2 +- src/Makefile.netbsd.WWW | 2 +- src/Makefile.openbsd.WWW | 2 +- src/Makefile.osx.WWW | 2 +- src/Makefile.sunos.WWW | 2 +- src/bbs.c | 3 + src/bbs.h | 3 + src/files.c | 45 ++- src/menus.c | 8 +- src/users.c | 2 +- src/www.c | 31 ++ src/www_files.c | 237 +++++++++++ 18 files changed, 1318 insertions(+), 12 deletions(-) create mode 100644 STRINGS.CHANGES create mode 100644 deps/hashids/hashids.c create mode 100644 deps/hashids/hashids.h create mode 100644 src/www_files.c diff --git a/STRINGS.CHANGES b/STRINGS.CHANGES new file mode 100644 index 0000000..ef53413 --- /dev/null +++ b/STRINGS.CHANGES @@ -0,0 +1,28 @@ +New / Changed Strings in dist/magicka.strings +-------------------------------------------------------------- +If you are using your own custom strings file, you will need +to add / modify the new string on the line specified. Be sure +to remove the start and end quotation marks. + + +Changes from v0.8-alpha -> v0.9-alpha +-------------------------------------------------------------- +LINE: 255 NEW +OLDSTRING: (NONE) +NEWSTRING: "\r\n\e[1;37mSending file %s...\r\n" + +LINE 256 NEW +OLDSTRING: (NONE) +NEWSTRING: "\r\n\e[1;34mFilename\e[1;30m: \e[1;37m%s\e[0m\r\n" + +LINE 257 NEW +OLDSTRING: (NONE) +NEWSTRING: " \e[1;34mURL\e[1;30m: \e[1;37m%s\e[0m\r\n" + +LINE 258 NEW +OLDSTRING: (NONE) +NEWSTRING: "\r\n\e[1;31mError creating URL!\e[0m\r\n" + +LINE 259 NEW +OLDSTRING: (NONE) +NEWSTRING: "\e[1;33mSorry this BBS does not have the webserver enabled.\e[0m\r\n" \ No newline at end of file diff --git a/deps/hashids/hashids.c b/deps/hashids/hashids.c new file mode 100644 index 0000000..dfbab26 --- /dev/null +++ b/deps/hashids/hashids.c @@ -0,0 +1,831 @@ +#include +#include +#include +#include +#include + +#include "hashids.h" + +/* branch prediction hinting */ +#ifndef __has_builtin +# define __has_builtin(x) (0) +#endif +#if defined(__builtin_expect) || __has_builtin(__builtin_expect) +# define HASHIDS_LIKELY(x) (__builtin_expect(!!(x), 1)) +# define HASHIDS_UNLIKELY(x) (__builtin_expect(!!(x), 0)) +#else +# define HASHIDS_LIKELY(x) (x) +# define HASHIDS_UNLIKELY(x) (x) +#endif + +/* thread-local storage */ +#ifndef TLS +#define TLS +#endif + +/* thread-safe hashids_errno indirection */ +TLS int __hashids_errno_val; +int * +__hashids_errno_addr() +{ + return &__hashids_errno_val; +} + +/* default alloc() implementation */ +static inline void * +hashids_alloc_f(size_t size) +{ + return calloc(size, 1); +} + +/* default free() implementation */ +static inline void +hashids_free_f(void *ptr) +{ + free(ptr); +} + +void *(*_hashids_alloc)(size_t size) = hashids_alloc_f; +void (*_hashids_free)(void *ptr) = hashids_free_f; + +/* fast ceil(x / y) for size_t arguments */ +static inline size_t +hashids_div_ceil_size_t(size_t x, size_t y) +{ + return x / y + !!(x % y); +} + +/* fast ceil(x / y) for unsigned short arguments */ +static inline unsigned short +hashids_div_ceil_unsigned_short(unsigned short x, unsigned short y) { + return x / y + !!(x % y); +} + +/* fast log2(x) for unsigned long long */ +const unsigned short hashids_log2_64_tab[64] = { + 63, 0, 58, 1, 59, 47, 53, 2, + 60, 39, 48, 27, 54, 33, 42, 3, + 61, 51, 37, 40, 49, 18, 28, 20, + 55, 30, 34, 11, 43, 14, 22, 4, + 62, 57, 46, 52, 38, 26, 32, 41, + 50, 36, 17, 19, 29, 10, 13, 21, + 56, 45, 25, 31, 35, 16, 9, 12, + 44, 24, 15, 8, 23, 7, 6, 5 +}; + +static inline unsigned short +hashids_log2_64(unsigned long long x) +{ + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x |= x >> 32; + + /* pure evil : ieee abuse */ + return hashids_log2_64_tab[ + ((unsigned long long)((x - (x >> 1)) * 0x07EDD5E59A4E28C2)) >> 58]; +} + +/* shuffle loop step */ +#define hashids_shuffle_step(iter) \ + if (i == 0) { break; } \ + if (v == salt_length) { v = 0; } \ + p += salt[v]; j = (salt[v] + v + p) % (iter); \ + temp = str[(iter)]; str[(iter)] = str[j]; str[j] = temp; \ + --i; ++v; + +/* consistent shuffle */ +void +hashids_shuffle(char *str, size_t str_length, char *salt, size_t salt_length) +{ + ssize_t i; + size_t j, v, p; + char temp; + + /* meh, meh */ + if (!salt_length) { + return; + } + + /* pure evil : loop unroll */ + for (i = str_length - 1, v = 0, p = 0; i > 0; /* empty */) { + switch (i % 32) { + case 31: hashids_shuffle_step(i); + case 30: hashids_shuffle_step(i); + case 29: hashids_shuffle_step(i); + case 28: hashids_shuffle_step(i); + case 27: hashids_shuffle_step(i); + case 26: hashids_shuffle_step(i); + case 25: hashids_shuffle_step(i); + case 24: hashids_shuffle_step(i); + case 23: hashids_shuffle_step(i); + case 22: hashids_shuffle_step(i); + case 21: hashids_shuffle_step(i); + case 20: hashids_shuffle_step(i); + case 19: hashids_shuffle_step(i); + case 18: hashids_shuffle_step(i); + case 17: hashids_shuffle_step(i); + case 16: hashids_shuffle_step(i); + case 15: hashids_shuffle_step(i); + case 14: hashids_shuffle_step(i); + case 13: hashids_shuffle_step(i); + case 12: hashids_shuffle_step(i); + case 11: hashids_shuffle_step(i); + case 10: hashids_shuffle_step(i); + case 9: hashids_shuffle_step(i); + case 8: hashids_shuffle_step(i); + case 7: hashids_shuffle_step(i); + case 6: hashids_shuffle_step(i); + case 5: hashids_shuffle_step(i); + case 4: hashids_shuffle_step(i); + case 3: hashids_shuffle_step(i); + case 2: hashids_shuffle_step(i); + case 1: hashids_shuffle_step(i); + case 0: hashids_shuffle_step(i); + } + } +} + +/* "destructor" */ +void +hashids_free(hashids_t *hashids) +{ + if (hashids) { + if (hashids->alphabet) { + _hashids_free(hashids->alphabet); + } + if (hashids->alphabet_copy_1) { + _hashids_free(hashids->alphabet_copy_1); + } + if (hashids->alphabet_copy_2) { + _hashids_free(hashids->alphabet_copy_2); + } + if (hashids->salt) { + _hashids_free(hashids->salt); + } + if (hashids->separators) { + _hashids_free(hashids->separators); + } + if (hashids->guards) { + _hashids_free(hashids->guards); + } + + _hashids_free(hashids); + } +} + +/* common init */ +hashids_t * +hashids_init3(const char *salt, size_t min_hash_length, const char *alphabet) +{ + hashids_t *result; + size_t i, j, len; + char ch, *p; + + hashids_errno = HASHIDS_ERROR_OK; + + /* allocate the structure */ + result = _hashids_alloc(sizeof(hashids_t)); + if (HASHIDS_UNLIKELY(!result)) { + hashids_errno = HASHIDS_ERROR_ALLOC; + return NULL; + } + + /* allocate enough space for the alphabet */ + len = strlen(alphabet) + 1; + result->alphabet = _hashids_alloc(len); + + /* extract only the unique characters */ + result->alphabet[0] = '\0'; + for (i = 0, j = 0; i < len; ++i) { + ch = alphabet[i]; + if (!strchr(result->alphabet, ch)) { + result->alphabet[j++] = ch; + } + } + result->alphabet[j] = '\0'; + + /* store alphabet length */ + result->alphabet_length = j; + + /* check length and whitespace */ + if (result->alphabet_length < HASHIDS_MIN_ALPHABET_LENGTH) { + hashids_free(result); + hashids_errno = HASHIDS_ERROR_ALPHABET_LENGTH; + return NULL; + } + if (strchr(result->alphabet, 0x20) || strchr(result->alphabet, 0x09)) { + hashids_free(result); + hashids_errno = HASHIDS_ERROR_ALPHABET_SPACE; + return NULL; + } + + /* copy salt */ + result->salt_length = salt ? strlen(salt) : 0; + result->salt = _hashids_alloc(result->salt_length + 1); + if (HASHIDS_UNLIKELY(!result->salt)) { + hashids_free(result); + hashids_errno = HASHIDS_ERROR_ALLOC; + return NULL; + } + strncpy(result->salt, salt, result->salt_length); + + /* allocate enough space for separators */ + len = strlen(HASHIDS_DEFAULT_SEPARATORS); + j = (size_t) + (ceil((float)result->alphabet_length / HASHIDS_SEPARATOR_DIVISOR) + 1); + if (j < len + 1) { + j = len + 1; + } + + result->separators = _hashids_alloc(j); + if (HASHIDS_UNLIKELY(!result->separators)) { + hashids_free(result); + hashids_errno = HASHIDS_ERROR_ALLOC; + return NULL; + } + + /* take default separators out of the alphabet */ + for (i = 0, j = 0; i < strlen(HASHIDS_DEFAULT_SEPARATORS); ++i) { + ch = HASHIDS_DEFAULT_SEPARATORS[i]; + + /* check if separator is actually in the used alphabet */ + if ((p = strchr(result->alphabet, ch))) { + result->separators[j++] = ch; + + /* remove that separator */ + memmove(p, p + 1, + strlen(result->alphabet) - (p - result->alphabet)); + } + } + + /* store separators length */ + result->separators_count = j; + + /* subtract separators count from alphabet length */ + result->alphabet_length -= result->separators_count; + + /* shuffle the separators */ + if (result->separators_count) { + hashids_shuffle(result->separators, result->separators_count, + result->salt, result->salt_length); + } + + /* check if we have any/enough separators */ + if (!result->separators_count + || (((float)result->alphabet_length / (float)result->separators_count) + > HASHIDS_SEPARATOR_DIVISOR)) { + size_t separators_count = (size_t)ceil( + (float)result->alphabet_length / HASHIDS_SEPARATOR_DIVISOR); + + if (separators_count == 1) { + separators_count = 2; + } + + if (separators_count > result->separators_count) { + /* we need more separators - get some from alphabet */ + size_t diff = separators_count - result->separators_count; + strncat(result->separators, result->alphabet, diff); + memmove(result->alphabet, result->alphabet + diff, + result->alphabet_length - diff + 1); + + result->separators_count += diff; + result->alphabet_length -= diff; + } else { + /* we have more than enough - truncate */ + result->separators[separators_count] = '\0'; + result->separators_count = separators_count; + } + } + + /* shuffle alphabet */ + hashids_shuffle(result->alphabet, result->alphabet_length, + result->salt, result->salt_length); + + /* allocate guards */ + result->guards_count = hashids_div_ceil_size_t(result->alphabet_length, + HASHIDS_GUARD_DIVISOR); + result->guards = _hashids_alloc(result->guards_count + 1); + if (HASHIDS_UNLIKELY(!result->guards)) { + hashids_free(result); + hashids_errno = HASHIDS_ERROR_ALLOC; + return NULL; + } + + if (HASHIDS_UNLIKELY(result->alphabet_length < 3)) { + /* take some from separators */ + strncpy(result->guards, result->separators, result->guards_count); + memmove(result->separators, result->separators + result->guards_count, + result->separators_count - result->guards_count + 1); + + result->separators_count -= result->guards_count; + } else { + /* take them from alphabet */ + strncpy(result->guards, result->alphabet, result->guards_count); + memmove(result->alphabet, result->alphabet + result->guards_count, + result->alphabet_length - result->guards_count + 1); + + result->alphabet_length -= result->guards_count; + } + + /* allocate enough space for the alphabet copies */ + result->alphabet_copy_1 = _hashids_alloc(result->alphabet_length + 1); + result->alphabet_copy_2 = _hashids_alloc(result->alphabet_length + 1); + if (HASHIDS_UNLIKELY(!result->alphabet || !result->alphabet_copy_1 + || !result->alphabet_copy_2)) { + hashids_free(result); + hashids_errno = HASHIDS_ERROR_ALLOC; + return NULL; + } + + /* set min hash length */ + result->min_hash_length = min_hash_length; + + /* return result happily */ + return result; +} + +/* init with salt and minimum hash length */ +hashids_t * +hashids_init2(const char *salt, size_t min_hash_length) +{ + return hashids_init3(salt, min_hash_length, HASHIDS_DEFAULT_ALPHABET); +} + +/* init with hash only */ +hashids_t * +hashids_init(const char *salt) +{ + return hashids_init2(salt, HASHIDS_DEFAULT_MIN_HASH_LENGTH); +} + +/* estimate buffer size (generic) */ +size_t +hashids_estimate_encoded_size(hashids_t *hashids, + size_t numbers_count, unsigned long long *numbers) +{ + int i, result_len; + + for (i = 0, result_len = 1; i < numbers_count; ++i) { + if (numbers[i] == 0) { + result_len += 2; + } else if (numbers[i] == 0xFFFFFFFFFFFFFFFFull) { + result_len += hashids_div_ceil_unsigned_short( + hashids_log2_64(numbers[i]), + hashids_log2_64(hashids->alphabet_length)) - 1; + } else { + result_len += hashids_div_ceil_unsigned_short( + hashids_log2_64(numbers[i] + 1), + hashids_log2_64(hashids->alphabet_length)); + } + } + + if (numbers_count > 1) { + result_len += numbers_count - 1; + } + + if (result_len < hashids->min_hash_length) { + result_len = hashids->min_hash_length; + } + + return result_len + 2 /* fast log2 & ceil sometimes undershoot by 1 */; +} + +/* estimate buffer size (variadic) */ +size_t +hashids_estimate_encoded_size_v(hashids_t *hashids, + size_t numbers_count, ...) +{ + size_t i, result; + unsigned long long *numbers; + va_list ap; + + numbers = _hashids_alloc(numbers_count * sizeof(unsigned long long)); + + if (HASHIDS_UNLIKELY(!numbers)) { + hashids_errno = HASHIDS_ERROR_ALLOC; + return 0; + } + + va_start(ap, numbers_count); + for (i = 0; i < numbers_count; ++i) { + numbers[i] = va_arg(ap, unsigned long long); + } + va_end(ap); + + result = hashids_estimate_encoded_size(hashids, numbers_count, numbers); + _hashids_free(numbers); + + return result; +} + +/* encode many (generic) */ +size_t +hashids_encode(hashids_t *hashids, char *buffer, + size_t numbers_count, unsigned long long *numbers) +{ + /* bail out if no numbers */ + if (HASHIDS_UNLIKELY(!numbers_count)) { + buffer[0] = '\0'; + + return 0; + } + + size_t i, j, result_len, guard_index, half_length_ceil, half_length_floor; + unsigned long long number, number_copy, numbers_hash; + int p_max; + char lottery, ch, temp_ch, *p, *buffer_end, *buffer_temp; + + /* return an estimation if no buffer */ + if (HASHIDS_UNLIKELY(!buffer)) { + return hashids_estimate_encoded_size(hashids, numbers_count, numbers); + } + + /* copy the alphabet into internal buffer 1 */ + strncpy(hashids->alphabet_copy_1, hashids->alphabet, + hashids->alphabet_length); + + /* walk arguments once and generate a hash */ + for (i = 0, numbers_hash = 0; i < numbers_count; ++i) { + number = numbers[i]; + numbers_hash += number % (i + 100); + } + + /* lottery character */ + lottery = hashids->alphabet[numbers_hash % hashids->alphabet_length]; + + /* start output buffer with it (or don't) */ + buffer[0] = lottery; + buffer_end = buffer + 1; + + /* alphabet-like buffer used for salt at each iteration */ + hashids->alphabet_copy_2[0] = lottery; + hashids->alphabet_copy_2[1] = '\0'; + strncat(hashids->alphabet_copy_2, hashids->salt, + hashids->alphabet_length - 1); + p = hashids->alphabet_copy_2 + hashids->salt_length + 1; + p_max = hashids->alphabet_length - 1 - hashids->salt_length; + if (p_max > 0) { + strncat(hashids->alphabet_copy_2, hashids->alphabet, + p_max); + } else { + hashids->alphabet_copy_2[hashids->alphabet_length] = '\0'; + } + + for (i = 0; i < numbers_count; ++i) { + /* take number */ + number = number_copy = numbers[i]; + + /* create a salt for this iteration */ + if (p_max > 0) { + strncpy(p, hashids->alphabet_copy_1, p_max); + } + + /* shuffle the alphabet */ + hashids_shuffle(hashids->alphabet_copy_1, hashids->alphabet_length, + hashids->alphabet_copy_2, hashids->alphabet_length); + + /* hash the number */ + buffer_temp = buffer_end; + do { + ch = hashids->alphabet_copy_1[number % hashids->alphabet_length]; + *buffer_end++ = ch; + number /= hashids->alphabet_length; + } while (number); + + /* reverse the hash we got */ + for (j = 0; j < (buffer_end - buffer_temp) / 2; ++j) { + temp_ch = *(buffer_temp + j); + *(buffer_temp + j) = *(buffer_end - 1 - j); + *(buffer_end - 1 - j) = temp_ch; + } + + if (i + 1 < numbers_count) { + number_copy %= ch + i; + *buffer_end = hashids->separators[number_copy % + hashids->separators_count]; + ++buffer_end; + } + } + + /* intermediate string length */ + result_len = buffer_end - buffer; + + if (result_len < hashids->min_hash_length) { + /* add a guard before the encoded numbers */ + guard_index = (numbers_hash + buffer[0]) % hashids->guards_count; + memmove(buffer + 1, buffer, result_len); + buffer[0] = hashids->guards[guard_index]; + ++result_len; + + if (result_len < hashids->min_hash_length) { + /* add a guard after the encoded numbers */ + guard_index = (numbers_hash + buffer[2]) % hashids->guards_count; + buffer[result_len] = hashids->guards[guard_index]; + ++result_len; + + /* pad with half alphabet before and after */ + half_length_ceil = hashids_div_ceil_size_t( + hashids->alphabet_length, 2); + half_length_floor = floor((float)hashids->alphabet_length / 2); + + /* pad, pad, pad */ + while (result_len < hashids->min_hash_length) { + /* shuffle the alphabet */ + strncpy(hashids->alphabet_copy_2, hashids->alphabet_copy_1, + hashids->alphabet_length); + hashids_shuffle(hashids->alphabet_copy_1, + hashids->alphabet_length, hashids->alphabet_copy_2, + hashids->alphabet_length); + + /* left pad from the end of the alphabet */ + i = hashids_div_ceil_size_t( + hashids->min_hash_length - result_len, 2); + /* right pad from the beginning */ + j = floor((float)(hashids->min_hash_length - result_len) / 2); + + /* check bounds */ + if (i > half_length_ceil) { + i = half_length_ceil; + } + if (j > half_length_floor) { + j = half_length_floor; + } + + /* handle excessively excessive excess */ + if ((i + j) % 2 == 0 && hashids->alphabet_length % 2 == 1) { + ++i; --j; + } + + /* move the current result to "center" */ + memmove(buffer + i, buffer, result_len); + /* pad left */ + memmove(buffer, + hashids->alphabet_copy_1 + hashids->alphabet_length - i, i); + /* pad right */ + memmove(buffer + i + result_len, hashids->alphabet_copy_1, j); + + /* increment result_len */ + result_len += i + j; + } + } + } + + buffer[result_len] = '\0'; + return result_len; +} + +/* encode many (variadic) */ +size_t +hashids_encode_v(hashids_t *hashids, char *buffer, + size_t numbers_count, ...) +{ + int i; + size_t result; + unsigned long long *numbers; + va_list ap; + + numbers = _hashids_alloc(numbers_count * sizeof(unsigned long long)); + + if (HASHIDS_UNLIKELY(!numbers)) { + hashids_errno = HASHIDS_ERROR_ALLOC; + return 0; + } + + va_start(ap, numbers_count); + for (i = 0; i < numbers_count; ++i) { + numbers[i] = va_arg(ap, unsigned long long); + } + va_end(ap); + + result = hashids_encode(hashids, buffer, numbers_count, numbers); + _hashids_free(numbers); + + return result; +} + +/* encode one */ +size_t +hashids_encode_one(hashids_t *hashids, char *buffer, + unsigned long long number) +{ + return hashids_encode(hashids, buffer, 1, &number); +} + +/* numbers count */ +size_t +hashids_numbers_count(hashids_t *hashids, char *str) +{ + size_t numbers_count; + char ch, *p; + + /* skip characters until we find a guard */ + if (hashids->min_hash_length) { + p = str; + while ((ch = *p)) { + if (strchr(hashids->guards, ch)) { + str = p + 1; + break; + } + + p++; + } + } + + /* parse */ + numbers_count = 0; + while ((ch = *str)) { + if (strchr(hashids->guards, ch)) { + break; + } + if (strchr(hashids->separators, ch)) { + numbers_count++; + str++; + continue; + } + if (!strchr(hashids->alphabet, ch)) { + hashids_errno = HASHIDS_ERROR_INVALID_HASH; + return 0; + } + + str++; + } + + /* account for the last number */ + return numbers_count + 1; +} + +/* decode */ +size_t +hashids_decode(hashids_t *hashids, char *str, + unsigned long long *numbers) +{ + size_t numbers_count; + unsigned long long number; + char lottery, ch, *p, *c; + int p_max; + + numbers_count = hashids_numbers_count(hashids, str); + + if (!numbers) { + return numbers_count; + } + + /* skip characters until we find a guard */ + if (hashids->min_hash_length) { + p = str; + while ((ch = *p)) { + if (strchr(hashids->guards, ch)) { + str = p + 1; + break; + } + + p++; + } + } + + /* get the lottery character */ + lottery = *str++; + + /* copy the alphabet into internal buffer 1 */ + strncpy(hashids->alphabet_copy_1, hashids->alphabet, + hashids->alphabet_length); + + /* alphabet-like buffer used for salt at each iteration */ + hashids->alphabet_copy_2[0] = lottery; + hashids->alphabet_copy_2[1] = '\0'; + strncat(hashids->alphabet_copy_2, hashids->salt, + hashids->alphabet_length - 1); + p = hashids->alphabet_copy_2 + hashids->salt_length + 1; + p_max = hashids->alphabet_length - 1 - hashids->salt_length; + if (p_max > 0) { + strncat(hashids->alphabet_copy_2, hashids->alphabet, + p_max); + } else { + hashids->alphabet_copy_2[hashids->alphabet_length] = '\0'; + } + + /* first shuffle */ + hashids_shuffle(hashids->alphabet_copy_1, hashids->alphabet_length, + hashids->alphabet_copy_2, hashids->alphabet_length); + + /* parse */ + number = 0; + while ((ch = *str)) { + if (strchr(hashids->guards, ch)) { + break; + } + if (strchr(hashids->separators, ch)) { + *numbers++ = number; + number = 0; + + /* resalt the alphabet */ + if (p_max > 0) { + strncpy(p, hashids->alphabet_copy_1, p_max); + } + hashids_shuffle(hashids->alphabet_copy_1, hashids->alphabet_length, + hashids->alphabet_copy_2, hashids->alphabet_length); + + str++; + continue; + } + if (!(c = strchr(hashids->alphabet_copy_1, ch))) { + hashids_errno = HASHIDS_ERROR_INVALID_HASH; + return 0; + } + + number *= hashids->alphabet_length; + number += c - hashids->alphabet_copy_1; + + str++; + } + + /* store last number */ + *numbers = number; + + return numbers_count; +} + +/* encode hex */ +size_t +hashids_encode_hex(hashids_t *hashids, char *buffer, + const char *hex_str) +{ + int len; + char *temp, *p; + size_t result; + unsigned long long number; + + len = strlen(hex_str); + temp = _hashids_alloc(len + 2); + + if (!temp) { + hashids_errno = HASHIDS_ERROR_ALLOC; + return 0; + } + + temp[0] = '1'; + strncpy(temp + 1, hex_str, len); + + number = strtoull(temp, &p, 16); + + if (p == temp) { + _hashids_free(temp); + hashids_errno = HASHIDS_ERROR_INVALID_NUMBER; + return 0; + } + + result = hashids_encode(hashids, buffer, 1, &number); + _hashids_free(temp); + + return result; +} + +/* decode hex */ +size_t +hashids_decode_hex(hashids_t *hashids, char *str, char *output) +{ + size_t result, i; + unsigned long long number; + char ch, *temp; + + result = hashids_numbers_count(hashids, str); + + if (result != 1) { + return 0; + } + + result = hashids_decode(hashids, str, &number); + + if (result != 1) { + return 0; + } + + temp = output; + + do { + ch = number % 16; + if (ch > 9) { + ch += 'A' - 10; + } else { + ch += '0'; + } + + *temp++ = (char)ch; + + number /= 16; + } while (number); + + temp--; + *temp = 0; + + for (i = 0; i < (temp - output) / 2; ++i) { + ch = *(output + i); + *(output + i) = *(temp - 1 - i); + *(temp - 1 - i) = ch; + } + + return 1; +} diff --git a/deps/hashids/hashids.h b/deps/hashids/hashids.h new file mode 100644 index 0000000..dd9a575 --- /dev/null +++ b/deps/hashids/hashids.h @@ -0,0 +1,122 @@ +#ifndef HASHIDS_H +#define HASHIDS_H 1 + +#include + +/* version constants */ +#define HASHIDS_VERSION "1.1.5" +#define HASHIDS_VERSION_MAJOR 1 +#define HASHIDS_VERSION_MINOR 1 +#define HASHIDS_VERSION_PATCH 5 + +/* minimal alphabet length */ +#define HASHIDS_MIN_ALPHABET_LENGTH 16u + +/* separator divisor */ +#define HASHIDS_SEPARATOR_DIVISOR 3.5f + +/* guard divisor */ +#define HASHIDS_GUARD_DIVISOR 12u + +/* default salt */ +#define HASHIDS_DEFAULT_SALT "" + +/* default minimal hash length */ +#define HASHIDS_DEFAULT_MIN_HASH_LENGTH 0u + +/* default alphabet */ +#define HASHIDS_DEFAULT_ALPHABET "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "1234567890" + +/* default separators */ +#define HASHIDS_DEFAULT_SEPARATORS "cfhistuCFHISTU" + +/* error codes */ +#define HASHIDS_ERROR_OK 0 +#define HASHIDS_ERROR_ALLOC -1 +#define HASHIDS_ERROR_ALPHABET_LENGTH -2 +#define HASHIDS_ERROR_ALPHABET_SPACE -3 +#define HASHIDS_ERROR_INVALID_HASH -4 +#define HASHIDS_ERROR_INVALID_NUMBER -5 + +/* thread-safe hashids_errno indirection */ +extern int *__hashids_errno_addr(); +#define hashids_errno (*__hashids_errno_addr()) + +/* alloc & free */ +extern void *(*_hashids_alloc)(size_t size); +extern void (*_hashids_free)(void *ptr); + +/* the hashids "object" */ +struct hashids_s { + char *alphabet; + char *alphabet_copy_1; + char *alphabet_copy_2; + size_t alphabet_length; + + char *salt; + size_t salt_length; + + char *separators; + size_t separators_count; + + char *guards; + size_t guards_count; + + size_t min_hash_length; +}; +typedef struct hashids_s hashids_t; + +/* exported function definitions */ +void +hashids_shuffle(char *str, size_t str_length, char *salt, size_t salt_length); + +void +hashids_free(hashids_t *hashids); + +hashids_t * +hashids_init3(const char *salt, size_t min_hash_length, + const char *alphabet); + +hashids_t * +hashids_init2(const char *salt, size_t min_hash_length); + +hashids_t * +hashids_init(const char *salt); + +size_t +hashids_estimate_encoded_size(hashids_t *hashids, + size_t numbers_count, unsigned long long *numbers); + +size_t +hashids_estimate_encoded_size_v(hashids_t *hashids, + size_t numbers_count, ...); + +size_t +hashids_encode(hashids_t *hashids, char *buffer, + size_t numbers_count, unsigned long long *numbers); + +size_t +hashids_encode_v(hashids_t *hashids, char *buffer, + size_t numbers_count, ...); + +size_t +hashids_encode_one(hashids_t *hashids, char *buffer, + unsigned long long number); + +size_t +hashids_numbers_count(hashids_t *hashids, char *str); + +size_t +hashids_decode(hashids_t *hashids, char *str, + unsigned long long *numbers); + +size_t +hashids_encode_hex(hashids_t *hashids, char *buffer, + const char *hex_str); + +size_t +hashids_decode_hex(hashids_t *hashids, char *str, char *output); + +#endif diff --git a/dist/magicka.strings b/dist/magicka.strings index 723d2bf..6415ebb 100644 --- a/dist/magicka.strings +++ b/dist/magicka.strings @@ -252,3 +252,8 @@ File exists!\r\n \e[1;37;44mChoose an Area in %s\e[K \e[1;37;44mChoose an Subdir in %s\e[K \e[1;37;44mChoose a Directory\e[K +\r\n\e[1;37mSending file %s...\r\n +\r\n\e[1;34mFilename\e[1;30m: \e[1;37m%s\e[0m\r\n + \e[1;34mURL\e[1;30m: \e[1;37m%s\e[0m\r\n +\r\n\e[1;31mError creating URL!\e[0m\r\n +\e[1;33mSorry this BBS does not have the webserver enabled.\e[0m\r\n \ No newline at end of file diff --git a/dist/menus/file.mnu b/dist/menus/file.mnu index 12c2c41..ffa9c02 100644 --- a/dist/menus/file.mnu +++ b/dist/menus/file.mnu @@ -16,6 +16,9 @@ COMMAND UPLOAD HOTKEY D COMMAND DOWNLOAD +HOTKEY W +COMMAND GENWWWURLS + HOTKEY C COMMAND CLEARTAGGED diff --git a/src/Makefile.freebsd.WWW b/src/Makefile.freebsd.WWW index 579c49b..648703c 100644 --- a/src/Makefile.freebsd.WWW +++ b/src/Makefile.freebsd.WWW @@ -34,7 +34,7 @@ $(CDK): cd ../deps/cdk-5.0-20161210/ && export CPPFLAGS=-I/usr/local/include && ./configure --with-ncurses cd ../deps/cdk-5.0-20161210/ && $(MAKE) -OBJ = ../deps/aha/aha.o inih/ini.o bbs.o main.o users.o main_menu.o mail_menu.o doors.o bbs_list.o chat_system.o email.o files.o settings.o lua_glue.o strings.o bluewave.o www.o www_email.o www_msgs.o www_last10.o hashmap/hashmap.o menus.o +OBJ = ../deps/aha/aha.o ../deps/hashids/hashids.o inih/ini.o bbs.o main.o users.o main_menu.o mail_menu.o doors.o bbs_list.o chat_system.o email.o files.o settings.o lua_glue.o strings.o bluewave.o www.o www_email.o www_msgs.o www_last10.o www_files.o hashmap/hashmap.o menus.o %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) diff --git a/src/Makefile.linux.WWW b/src/Makefile.linux.WWW index 6ddadc0..850ea3b 100644 --- a/src/Makefile.linux.WWW +++ b/src/Makefile.linux.WWW @@ -34,7 +34,7 @@ $(CDK): cd ../deps/cdk-5.0-20161210/ && ./configure cd ../deps/cdk-5.0-20161210/ && $(MAKE) -OBJ = ../deps/aha/aha.o inih/ini.o bbs.o main.o users.o main_menu.o mail_menu.o doors.o bbs_list.o chat_system.o email.o files.o settings.o lua_glue.o strings.o www.o www_email.o www_msgs.o www_last10.o bluewave.o hashmap/hashmap.o menus.o +OBJ = ../deps/aha/aha.o ../deps/hashids/hashids.o inih/ini.o bbs.o main.o users.o main_menu.o mail_menu.o doors.o bbs_list.o chat_system.o email.o files.o settings.o lua_glue.o strings.o www.o www_email.o www_msgs.o www_last10.o www_files.o bluewave.o hashmap/hashmap.o menus.o %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) diff --git a/src/Makefile.netbsd.WWW b/src/Makefile.netbsd.WWW index c6bf98b..f07c917 100644 --- a/src/Makefile.netbsd.WWW +++ b/src/Makefile.netbsd.WWW @@ -34,7 +34,7 @@ $(CDK): cd ../deps/cdk-5.0-20161210/ && export CPPFLAGS=-I/usr/pkg/include && ./configure --with-ncurses cd ../deps/cdk-5.0-20161210/ && $(MAKE) -OBJ = ../deps/aha/aha.o inih/ini.o bbs.o main.o users.o main_menu.o mail_menu.o doors.o bbs_list.o chat_system.o email.o files.o settings.o lua_glue.o strings.o bluewave.o www.o www_email.o www_msgs.o www_last10.o hashmap/hashmap.o menus.o +OBJ = ../deps/aha/aha.o ../deps/hashids/hashids.o inih/ini.o bbs.o main.o users.o main_menu.o mail_menu.o doors.o bbs_list.o chat_system.o email.o files.o settings.o lua_glue.o strings.o bluewave.o www.o www_email.o www_msgs.o www_last10.o www_files.o hashmap/hashmap.o menus.o %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) diff --git a/src/Makefile.openbsd.WWW b/src/Makefile.openbsd.WWW index abdb526..450250c 100644 --- a/src/Makefile.openbsd.WWW +++ b/src/Makefile.openbsd.WWW @@ -34,7 +34,7 @@ $(CDK): cd ../deps/cdk-5.0-20161210/ && export CPPFLAGS=-I/usr/pkg/include && ./configure --with-ncurses cd ../deps/cdk-5.0-20161210/ && $(MAKE) -OBJ = ../deps/aha/aha.o inih/ini.o bbs.o main.o users.o main_menu.o mail_menu.o doors.o bbs_list.o chat_system.o email.o files.o settings.o lua_glue.o strings.o bluewave.o www.o www_email.o www_msgs.o www_last10.o hashmap/hashmap.o menus.o +OBJ = ../deps/aha/aha.o ../deps/hashids/hashids.o inih/ini.o bbs.o main.o users.o main_menu.o mail_menu.o doors.o bbs_list.o chat_system.o email.o files.o settings.o lua_glue.o strings.o bluewave.o www.o www_email.o www_msgs.o www_last10.o www_files.o hashmap/hashmap.o menus.o %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) diff --git a/src/Makefile.osx.WWW b/src/Makefile.osx.WWW index ba540bc..f7aabfe 100644 --- a/src/Makefile.osx.WWW +++ b/src/Makefile.osx.WWW @@ -34,7 +34,7 @@ $(CDK): cd ../deps/cdk-5.0-20161210/ && ./configure cd ../deps/cdk-5.0-20161210/ && $(MAKE) -OBJ = ../deps/aha/aha.o inih/ini.o bbs.o main.o users.o main_menu.o mail_menu.o doors.o bbs_list.o chat_system.o email.o files.o settings.o lua_glue.o strings.o www.o www_email.o www_msgs.o www_last10.o bluewave.o hashmap/hashmap.o menus.o +OBJ = ../deps/aha/aha.o ../deps/hashids/hashids.o inih/ini.o bbs.o main.o users.o main_menu.o mail_menu.o doors.o bbs_list.o chat_system.o email.o files.o settings.o lua_glue.o strings.o www.o www_email.o www_msgs.o www_last10.o www_files.o bluewave.o hashmap/hashmap.o menus.o %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) diff --git a/src/Makefile.sunos.WWW b/src/Makefile.sunos.WWW index 09eb808..bcbdb2d 100644 --- a/src/Makefile.sunos.WWW +++ b/src/Makefile.sunos.WWW @@ -34,7 +34,7 @@ $(CDK): cd ../deps/cdk-5.0-20161210/ && ./configure cd ../deps/cdk-5.0-20161210/ && $(MAKE) -OBJ = ../deps/aha/aha.o inih/ini.o bbs.o main.o users.o main_menu.o mail_menu.o doors.o bbs_list.o chat_system.o email.o files.o settings.o lua_glue.o strings.o www.o www_email.o www_msgs.o www_last10.o bluewave.o hashmap/hashmap.o menus.o os/sunos.o +OBJ = ../deps/aha/aha.o ../deps/hashids/hashids.o inih/ini.o bbs.o main.o users.o main_menu.o mail_menu.o doors.o bbs_list.o chat_system.o email.o files.o settings.o lua_glue.o strings.o www.o www_email.o www_msgs.o www_last10.o bluewave.o www_files.o hashmap/hashmap.o menus.o os/sunos.o %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) diff --git a/src/bbs.c b/src/bbs.c index 9171a28..092d6c7 100644 --- a/src/bbs.c +++ b/src/bbs.c @@ -895,6 +895,9 @@ tryagain: unlink(buffer); } +#if defined(ENABLE_WWW) + www_expire_old_links(); +#endif // do post-login dolog("%s logged in, on node %d", user->loginname, mynode); broadcast("%s logged in, on node %d", user->loginname, mynode); diff --git a/src/bbs.h b/src/bbs.h index 01758e9..dc38da1 100644 --- a/src/bbs.h +++ b/src/bbs.h @@ -323,6 +323,7 @@ extern void next_file_sub(struct user_record *user); extern void prev_file_sub(struct user_record *user); extern void file_scan(); extern void file_search(); +extern void genurls(); extern void lua_push_cfunctions(lua_State *L); extern void do_lua_script(char *script); @@ -350,6 +351,8 @@ extern char *www_msgs_messageview(struct user_record *user, int conference, int extern int www_send_msg(struct user_record *user, char *to, char *subj, int conference, int area, char *replyid, char *body); extern char *www_new_msg(struct user_record *user, int conference, int area); extern char *www_last10(); +extern void www_expire_old_links(); +extern char *www_create_link(int dir, int sub, int fid); #endif extern int menu_system(char *menufile); #endif diff --git a/src/files.c b/src/files.c index 7a6bd20..bb643e4 100644 --- a/src/files.c +++ b/src/files.c @@ -798,6 +798,42 @@ void download_zmodem(struct user_record *user, char *filename) { } } + +void genurls() { +#if defined(ENABLE_WWW) + int i; + char *url; + for (i=0;idir, tagged_files[i]->sub, tagged_files[i]->fid); + + if (url != NULL) { + s_printf(get_string(255), basename(tagged_files[i]->filename)); + s_printf(get_string(256), url); + free(url); + } else { + s_printf(get_string(257)); + } + } + for (i=0;ifilename); + free(tagged_files[i]); + } + free(tagged_files); + tagged_count = 0; +#else + s_printf(get_string(258)); + s_printf(get_string(6)); + s_getc(); +#endif +} + + void download(struct user_record *user) { int i; char *ssql = "select dlcount from files where filename like ?"; @@ -810,6 +846,7 @@ void download(struct user_record *user) { for (i=0;ifilename)); do_download(user, tagged_files[i]->filename); @@ -1324,15 +1361,15 @@ void list_files(struct user_record *user) { } sqlite3_busy_timeout(db, 5000); rc = sqlite3_prepare_v2(db, sql, -1, &res, 0); - if (sql == nsql) { - sqlite3_bind_int(res, 1, userlaston); - } if (rc != SQLITE_OK) { - sqlite3_finalize(res); sqlite3_close(db); s_printf(get_string(68)); return; } + if (sql == nsql) { + sqlite3_bind_int(res, 1, userlaston); + } + files_c = 0; diff --git a/src/menus.c b/src/menus.c index 12e15a1..e0e8141 100644 --- a/src/menus.c +++ b/src/menus.c @@ -54,6 +54,7 @@ #define MENU_FILESEARCH 44 #define MENU_DISPTXTFILE 45 #define MENU_DISPTXTFILEPAUSE 46 +#define MENU_GENWWWURLS 47 extern struct bbs_config conf; extern struct user_record *gUser; @@ -212,7 +213,9 @@ int menu_system(char *menufile) { menu[menu_items-1]->command = MENU_DISPTXTFILE; } else if (strncasecmp(&buffer[8], "DISPLAYTXTPAUSE", 15) == 0) { menu[menu_items-1]->command = MENU_DISPTXTFILEPAUSE; - } + } else if (strncasecmp(&buffer[8], "GENWWWURLS", 10) == 0) { + menu[menu_items-1]->command = MENU_GENWWWURLS; + } } else if (strncasecmp(buffer, "SECLEVEL", 8) == 0) { menu[menu_items-1]->seclevel = atoi(&buffer[9]); } else if (strncasecmp(buffer, "DATA", 4) == 0) { @@ -531,6 +534,9 @@ int menu_system(char *menufile) { s_printf(get_string(6)); s_getc(); break; + case MENU_GENWWWURLS: + genurls(); + break; default: break; } diff --git a/src/users.c b/src/users.c index 3e716a2..1026d08 100644 --- a/src/users.c +++ b/src/users.c @@ -266,7 +266,7 @@ int inst_user(struct user_record *user) { "exteditor INTEGER," "bwavestyle INTEGER," "signature TEXT," - "autosig INT);"; + "autosig INTEGER);"; char *insert_sql = "INSERT INTO users (loginname, password, salt, firstname," "lastname, email, location, sec_level, last_on, time_left, cur_mail_conf, cur_mail_area, cur_file_dir, cur_file_sub, times_on, bwavepktno, archiver, protocol, nodemsgs, codepage, exteditor, bwavestyle, signature, autosig) VALUES(?,?, ?,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; diff --git a/src/www.c b/src/www.c index 2acd743..214f34d 100644 --- a/src/www.c +++ b/src/www.c @@ -415,6 +415,7 @@ int www_handler(void * cls, struct MHD_Connection * connection, const char * url const char *val; int skip; char *replyid; + char *filename; // char *static_buffer; if (strcmp(method, "GET") == 0) { @@ -826,6 +827,36 @@ int www_handler(void * cls, struct MHD_Connection * connection, const char * url return MHD_YES; } + } else if (strncasecmp(url, "/files/", 7) == 0) { + filename = www_decode_hash(&url[7]); + if (filename != NULL) { + if (stat(filename, &s) == 0 && S_ISREG(s.st_mode)) { + fno = open(buffer, O_RDONLY); + if (fno != -1) { + + response = MHD_create_response_from_fd(s.st_size, fno); + MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, mime); + sprintf(buffer, "%ld", s.st_size); + MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_LENGTH, buffer); + snprintf(buffer, PATH_MAX, "attachment; filename=\"%s\"", basename(filename)); + MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_DISPOSITION, buffer); + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + free(header); + free(footer); + return ret; + } + } + free(filename); + } + if (www_404(header, footer, connection) != 0) { + free(header); + free(footer); + return MHD_NO; + } + free(header); + free(footer); + return MHD_YES; } else { if (www_404(header, footer, connection) != 0) { free(header); diff --git a/src/www_files.c b/src/www_files.c new file mode 100644 index 0000000..cfad512 --- /dev/null +++ b/src/www_files.c @@ -0,0 +1,237 @@ +#if defined(ENABLE_WWW) + +#include +#include +#include +#include +#include "bbs.h" +#include "../deps/hashids/hashids.h" + +extern struct bbs_config conf; +extern struct user_record *gUser; + +void www_expire_old_links() { + char buffer[PATH_MAX]; + sqlite3 *db; + sqlite3_stmt *res; + int rc; + char sql[] = "delete from wwwhash where expire <= ?"; + char *ret; + time_t now = time(NULL); + + snprintf(buffer, PATH_MAX, "%s/www_file_hashes.sq3", conf.bbs_path); + + rc = sqlite3_open(buffer, &db); + if (rc != SQLITE_OK) { + dolog("Cannot open database: %s", sqlite3_errmsg(db)); + return; + } + sqlite3_busy_timeout(db, 5000); + rc = sqlite3_prepare_v2(db, sql, -1, &res, 0); + if (rc != SQLITE_OK) { + sqlite3_close(db); + return; + } + sqlite3_bind_int(res, 1, now); + sqlite3_step(res); + sqlite3_finalize(res); + sqlite3_close(db); +} + +int www_check_hash_expired(char *hash) { + char buffer[PATH_MAX]; + sqlite3 *db; + sqlite3_stmt *res; + int rc; + time_t now = time(NULL); + char sql[] = "select id from wwwhash where hash = ? and expire > ?"; + snprintf(buffer, PATH_MAX, "%s/www_file_hashes.sq3", conf.bbs_path); + rc = sqlite3_open(buffer, &db); + if (rc != SQLITE_OK) { + return 1; + } + sqlite3_busy_timeout(db, 5000); + rc = sqlite3_prepare_v2(db, sql, -1, &res, 0); + if (rc != SQLITE_OK) { + sqlite3_close(db); + return 0; + } + + sqlite3_bind_text(res, 1, hash, -1, 0); + sqlite3_bind_int(res, 2, now); + + if (sqlite3_step(res) == SQLITE_ROW) { + sqlite3_finalize(res); + sqlite3_close(db); + return 0; + } + sqlite3_finalize(res); + sqlite3_close(db); + return 1; +} + +void www_add_hash_to_db(char *hash, time_t expiry) { + char buffer[PATH_MAX]; + sqlite3 *db; + sqlite3_stmt *res; + int rc; + char csql[] = "create table if not exists wwwhash (id INTEGER PRIMARY KEY, hash TEXT, expiry INTEGER)"; + char chsql[] = "select id from wwwhash where hash = ?"; + char usql[] = "update wwwhash SET expiry = ? WHERE hash = ?"; + char isql[] = "insert into wwwhash (hash, expiry) values(?, ?)"; + + char *ret; + char *err_msg = 0; + + snprintf(buffer, PATH_MAX, "%s/www_file_hashes.sq3", conf.bbs_path); + + rc = sqlite3_open(buffer, &db); + if (rc != SQLITE_OK) { + return; + } + sqlite3_busy_timeout(db, 5000); + + rc = sqlite3_exec(db, csql, 0, 0, &err_msg); + if (rc != SQLITE_OK) { + + dolog("SQL error: %s", err_msg); + + sqlite3_free(err_msg); + sqlite3_close(db); + return; + } + + // first check if hash is in database + rc = sqlite3_prepare_v2(db, chsql, -1, &res, 0); + if (rc != SQLITE_OK) { + sqlite3_close(db); + return; + } + sqlite3_bind_text(res, 1, hash, -1, 0); + rc = sqlite3_step(res); + if (rc == SQLITE_ROW) { + // if so, update hash + sqlite3_finalize(res); + rc = sqlite3_prepare_v2(db, usql, -1, &res, 0); + if (rc != SQLITE_OK) { + sqlite3_close(db); + return; + } + sqlite3_bind_int(res, 1, expiry); + sqlite3_bind_text(res, 2, hash, -1, 0); + sqlite3_step(res); + sqlite3_finalize(res); + sqlite3_close(db); + + return; + } + // if not add hash + sqlite3_finalize(res); + rc = sqlite3_prepare_v2(db, isql, -1, &res, 0); + if (rc != SQLITE_OK) { + sqlite3_close(db); + return; + } + sqlite3_bind_text(res, 1, hash, -1, 0); + sqlite3_bind_int(res, 2, expiry); + sqlite3_step(res); + sqlite3_finalize(res); + sqlite3_close(db); +} + +char *www_decode_hash(char *hash) { + long long unsigned numbers[4]; + int dir, sub, fid, uid; + hashids_t *hashids = hashids_init(conf.bbs_name); + char buffer[PATH_MAX]; + sqlite3 *db; + sqlite3_stmt *res; + int rc; + char sql[] = "select filename from files where approved = 1 and id = ?"; + char *ret; + + if (www_check_hash_expired(hash)) { + return NULL; + } + + if (hashids_decode(hashids, hash, numbers) != 4) { + hashids_free(hashids); + return NULL; + } + hashids_free(hashids); + + uid = (int)numbers[0]; + dir = (int)numbers[1]; + sub = (int)numbers[2]; + fid = (int)numbers[3]; + + if (dir >= conf.file_directory_count || sub >= conf.file_directories[dir]->file_sub_count) { + return NULL; + } + +#if 0 + // TODO: check security level... + + if (conf.file_directories[dir]->sec_level < ) +#endif + // get filename from database + snprintf(buffer, PATH_MAX, "%s/%s.sq3", conf.bbs_path, conf.file_directories[dir]->file_subs[sub]->database); + rc = sqlite3_open(buffer, &db); + if (rc != SQLITE_OK) { + dolog("Cannot open database: %s", sqlite3_errmsg(db)); + return NULL; + } + sqlite3_busy_timeout(db, 5000); + rc = sqlite3_prepare_v2(db, sql, -1, &res, 0); + if (rc != SQLITE_OK) { + sqlite3_close(db); + return NULL; + } + sqlite3_bind_int(res, 1, fid); + if (sqlite3_step(res) == SQLITE_ROW) { + ret = strdup(sqlite3_column_text(res, 0)); + sqlite3_finalize(res); + sqlite3_close(db); + + return ret; + } + sqlite3_finalize(res); + sqlite3_close(db); + return NULL; +} + +char *www_create_link(int dir, int sub, int fid) { + char url[PATH_MAX]; + char *ret; + char *hashid; + int sizereq; + time_t expiry; + + hashids_t *hashids = hashids_init(conf.bbs_name); + + sizereq = hashids_estimate_encoded_size_v(hashids, 4, gUser->id, dir, sub, fid); + + hashid = (char *)malloc(sizereq); + + if (hashids_encode_v(hashids, hashid, 4, gUser->id, dir, sub, fid) == 0) { + hashids_free(hashids); + free(hashid); + return NULL; + } + + hashids_free(hashids); + + snprintf(url, PATH_MAX, "%sfiles/%s", conf.www_url, hashid); + + // add link into hash database + expiry = time(NULL) + 86400; + www_add_hash_to_db(hashid, expiry); + + free(hashid); + + ret = strdup(url); + + return ret; +} + +#endif