summaryrefslogtreecommitdiffstats
path: root/libass/ass_filesystem.c
diff options
context:
space:
mode:
Diffstat (limited to 'libass/ass_filesystem.c')
-rw-r--r--libass/ass_filesystem.c412
1 files changed, 412 insertions, 0 deletions
diff --git a/libass/ass_filesystem.c b/libass/ass_filesystem.c
new file mode 100644
index 0000000..fa7bbab
--- /dev/null
+++ b/libass/ass_filesystem.c
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2021 libass contributors
+ *
+ * This file is part of libass.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "config.h"
+#include "ass_compat.h"
+
+#include "ass_filesystem.h"
+#include "ass_utils.h"
+
+
+static inline bool check_add_size(size_t *size, size_t amount)
+{
+ size_t res = *size + amount;
+ if (res < amount)
+ return false;
+ *size = res;
+ return true;
+}
+
+#define NAME_BUF_SIZE 256
+// NAME_BUF_SIZE + 2 <= SIZE_MAX
+
+static bool alloc_path(ASS_Dir *dir, size_t size)
+{
+ if (size <= dir->max_path)
+ return true;
+ if (!check_add_size(&size, NAME_BUF_SIZE))
+ return false;
+ char *path = realloc(dir->path, size);
+ if (!path)
+ return false;
+ dir->path = path;
+ dir->max_path = size;
+ return true;
+}
+
+
+#if !defined(_WIN32) || defined(__CYGWIN__)
+
+#include <dirent.h>
+
+FILE *ass_open_file(const char *filename, FileNameSource hint)
+{
+ return fopen(filename, "rb");
+}
+
+bool ass_open_dir(ASS_Dir *dir, const char *path)
+{
+ dir->handle = NULL;
+ dir->path = NULL;
+ dir->name = NULL;
+
+ size_t len = strlen(path);
+ if (len && path[len - 1] == '/')
+ len--;
+
+ size_t size = NAME_BUF_SIZE + 2;
+ if (!check_add_size(&size, len))
+ return false;
+ dir->path = malloc(size);
+ if (!dir->path)
+ return false;
+ dir->max_path = size;
+ memcpy(dir->path, path, len);
+ dir->path[len] = '/';
+ dir->prefix = len + 1;
+
+ dir->handle = opendir(path);
+ if (dir->handle)
+ return true;
+
+ free(dir->path);
+ dir->path = NULL;
+ return false;
+}
+
+const char *ass_read_dir(ASS_Dir *dir)
+{
+ struct dirent *entry = readdir(dir->handle);
+ return dir->name = entry ? entry->d_name : NULL;
+}
+
+const char *ass_current_file_path(ASS_Dir *dir)
+{
+ size_t size = dir->prefix + 1, len = strlen(dir->name);
+ if (!check_add_size(&size, len) || !alloc_path(dir, size))
+ return NULL;
+ memcpy(dir->path + dir->prefix, dir->name, len + 1);
+ return dir->path;
+}
+
+void ass_close_dir(ASS_Dir *dir)
+{
+ if (dir->handle)
+ closedir(dir->handle);
+ free(dir->path);
+ dir->handle = NULL;
+ dir->path = NULL;
+ dir->name = NULL;
+}
+
+
+#else // Windows
+
+#include <windows.h>
+#include "ass_directwrite.h" // for ASS_WINAPI_DESKTOP
+
+
+static const uint8_t wtf8_len_table[256] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1x
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2x
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3x
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4x
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5x
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6x
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7x
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Ax
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Bx
+
+ 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // Cx
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // Dx
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // Ex
+ 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Fx
+};
+
+static const uint8_t wtf8_len4_range[5][2] = {
+ { 0x90, 0x30 }, { 0x80, 0x40 }, { 0x80, 0x40 }, { 0x80, 0x40 }, { 0x80, 0x10 }
+};
+
+static inline bool check_add_size_wtf8to16(size_t *size, size_t len)
+{
+ return len > SIZE_MAX / sizeof(WCHAR) ? false : check_add_size(size, len * sizeof(WCHAR));
+}
+
+// does not append zero termination implicitly
+// expects preallocated buffer (use check_add_size_wtf8to16() for size)
+static WCHAR *convert_wtf8to16(WCHAR *dst, ASS_StringView src)
+{
+ const char *str = src.str;
+ for (const char *end = str + src.len; str < end; str++) {
+ uint8_t ch = *str;
+ switch(wtf8_len_table[ch]) {
+ case 1: // 1 -> 1w
+ *dst++ = ch;
+ continue;
+
+ case 2: // 2 -> 1w
+ if (str + 1 < end) {
+ uint8_t next = *++str;
+ if ((next & 0xC0) != 0x80)
+ return NULL;
+ *dst++ = (ch & 0x1F) << 6 | (next & 0x3F);
+ continue;
+ }
+ return NULL;
+
+ case 3: // 3 -> 1w
+ if (str + 2 < end) {
+ ch &= 0xF;
+ uint8_t next1 = *++str, next2 = *++str;
+ if (next1 < (ch ? 0x80 : 0xA0) || next1 >= 0xC0 || (next2 & 0xC0) != 0x80)
+ return NULL;
+ *dst++ = ch << 12 | (next1 & 0x3F) << 6 | (next2 & 0x3F);
+ continue;
+ }
+ return NULL;
+
+ case 4: // 4 -> 2w
+ if (str + 3 < end) {
+ ch &= 0x7;
+ uint8_t next1 = *++str, next2 = *++str, next3 = *++str;
+ if ((uint8_t) (next1 - wtf8_len4_range[ch][0]) >= wtf8_len4_range[ch][1] ||
+ (next2 & 0xC0) != 0x80 || (next3 & 0xC0) != 0x80)
+ return NULL;
+ *dst++ = 0xD800 | ((ch << 8 | (next1 & 0x3F) << 2) - 0x40) | (next2 & 0x3F) >> 4;
+ *dst++ = 0xDC00 | (next2 & 0xF) << 6 | (next3 & 0x3F);
+ continue;
+ }
+ return NULL;
+
+ default:
+ return NULL;
+ }
+ }
+ return dst;
+}
+
+static inline bool check_add_size_wtf16to8(size_t *size, size_t wlen)
+{
+ enum { max_bytes_per_wchar = 3 };
+ return wlen > SIZE_MAX / max_bytes_per_wchar ? false :
+ check_add_size(size, max_bytes_per_wchar * wlen);
+}
+
+// does not append zero termination implicitly
+// expects preallocated buffer (use check_add_size_wtf16to8() for size)
+static char *convert_wtf16to8(char *dst, const WCHAR *src, size_t wlen)
+{
+ for (const WCHAR *end = src + wlen; src < end; src++) {
+ uint16_t wch = *src;
+ if (wch < 0x80) {
+ // 1w -> 1
+ *dst++ = wch;
+ continue;
+ }
+ if (wch < 0x800) {
+ // 1w -> 2
+ *dst++ = 0xC0 | wch >> 6;
+ *dst++ = 0x80 | (wch & 0x3F);
+ continue;
+ }
+ if (wch >= 0xD800 && wch < 0xDC00 && src + 1 < end) {
+ uint16_t next = src[1];
+ if (next >= 0xDC00 && next < 0xE000) { // correctly paired surrogates
+ src++; // 2w -> 4
+ uint32_t full = (uint32_t) ((wch & 0x3FF) + 0x40) << 10 | (next & 0x3FF);
+ *dst++ = 0xF0 | full >> 18;
+ *dst++ = 0x80 | ((full >> 12) & 0x3F);
+ *dst++ = 0x80 | ((full >> 6) & 0x3F);
+ *dst++ = 0x80 | (full & 0x3F);
+ continue;
+ }
+ }
+ // unpaired surrogates fall through here
+ // 1w -> 3
+ *dst++ = 0xE0 | wch >> 12;
+ *dst++ = 0x80 | ((wch >> 6) & 0x3F);
+ *dst++ = 0x80 | (wch & 0x3F);
+ }
+ return dst;
+}
+
+static FILE *open_file_wtf8(const char *filename)
+{
+ size_t size = sizeof(WCHAR);
+ ASS_StringView name = { filename, strlen(filename) };
+ if (!check_add_size_wtf8to16(&size, name.len))
+ return NULL;
+ WCHAR *wname = malloc(size);
+ if (!wname)
+ return NULL;
+ WCHAR *end = convert_wtf8to16(wname, name);
+ FILE *fp = NULL;
+ if (end) {
+ *end = L'\0';
+ fp = _wfopen(wname, L"rb");
+ }
+ free(wname);
+ return fp;
+}
+
+FILE *ass_open_file(const char *filename, FileNameSource hint)
+{
+ FILE *fp = open_file_wtf8(filename);
+ if (fp || hint == FN_DIR_LIST)
+ return fp;
+ return fopen(filename, "rb");
+}
+
+
+static const WCHAR dir_tail[] = L"\\*";
+
+static bool append_tail(WCHAR *wpath, size_t wlen)
+{
+ size_t offs = 0;
+ if (wlen == 2 && wpath[1] == L':')
+ offs = 1;
+ else if (wlen && (wpath[wlen - 1] == L'/' || wpath[wlen - 1] == L'\\'))
+ offs = 1;
+ memcpy(wpath + wlen, dir_tail + offs, sizeof(dir_tail) - offs);
+ return !offs;
+}
+
+static bool open_dir_wtf8(ASS_Dir *dir, ASS_StringView path)
+{
+ assert(dir->handle == INVALID_HANDLE_VALUE && !dir->path);
+
+ size_t size = sizeof(dir_tail);
+ if (!check_add_size_wtf8to16(&size, path.len))
+ return false;
+
+ WCHAR *wpath = malloc(size);
+ if (!wpath)
+ return false;
+
+ bool add_separator;
+ WIN32_FIND_DATAW data;
+ WCHAR *end = convert_wtf8to16(wpath, path);
+ if (end) {
+ add_separator = append_tail(wpath, end - wpath);
+ dir->handle = FindFirstFileExW(wpath, FindExInfoBasic, &data, FindExSearchNameMatch, NULL, 0);
+ }
+ free(wpath);
+ if (dir->handle == INVALID_HANDLE_VALUE)
+ return false;
+
+ size = NAME_BUF_SIZE + 2;
+ size_t wlen = wcslen(data.cFileName);
+ if (!check_add_size(&size, path.len) || !check_add_size_wtf16to8(&size, wlen) ||
+ !(dir->path = malloc(size))) {
+ FindClose(dir->handle);
+ return false;
+ }
+ dir->max_path = size;
+ memcpy(dir->path, path.str, path.len);
+ if (add_separator)
+ dir->path[path.len++] = '\\';
+ *convert_wtf16to8(dir->path + path.len, data.cFileName, wlen) = '\0';
+ dir->prefix = path.len;
+ return true;
+}
+
+bool ass_open_dir(ASS_Dir *dir, const char *path)
+{
+ dir->handle = INVALID_HANDLE_VALUE;
+ dir->path = NULL;
+ dir->name = NULL;
+
+ size_t len = strlen(path);
+ if (open_dir_wtf8(dir, (ASS_StringView) { path, len }))
+ return true;
+
+ if (len > INT_MAX)
+ return false;
+
+ UINT cp = CP_ACP;
+#if ASS_WINAPI_DESKTOP
+ if (!AreFileApisANSI())
+ cp = CP_OEMCP;
+#endif
+ size_t wlen = MultiByteToWideChar(cp, 0, path, len, NULL, 0);
+ if (wlen > (SIZE_MAX - sizeof(dir_tail)) / sizeof(WCHAR))
+ return false;
+ WCHAR *wpath = malloc(wlen * sizeof(WCHAR) + sizeof(dir_tail));
+ if (!wpath)
+ return false;
+ MultiByteToWideChar(cp, 0, path, len, wpath, wlen);
+ bool add_separator = append_tail(wpath, wlen);
+
+ WIN32_FIND_DATAW data;
+ dir->handle = FindFirstFileExW(wpath, FindExInfoBasic, &data, FindExSearchNameMatch, NULL, 0);
+ if (dir->handle == INVALID_HANDLE_VALUE) {
+ free(wpath);
+ return false;
+ }
+ size_t size = NAME_BUF_SIZE + 2, wlen1 = wcslen(data.cFileName);
+ if (!check_add_size_wtf16to8(&size, wlen) || !check_add_size_wtf16to8(&size, wlen1) ||
+ !(dir->path = malloc(size))) {
+ FindClose(dir->handle);
+ return false;
+ }
+ dir->max_path = size;
+ char *ptr = convert_wtf16to8(dir->path, wpath, wlen);
+ if (add_separator)
+ *ptr++ = '\\';
+ convert_wtf16to8(ptr, data.cFileName, wlen1 + 1);
+ dir->prefix = ptr - dir->path;
+ return true;
+}
+
+const char *ass_read_dir(ASS_Dir *dir)
+{
+ if (!dir->name) // first invocation
+ return dir->name = dir->path + dir->prefix;
+
+ WIN32_FIND_DATAW data;
+ while (FindNextFileW(dir->handle, &data)) {
+ size_t size = dir->prefix + 1, wlen = wcslen(data.cFileName);
+ if (!check_add_size_wtf16to8(&size, wlen) || !alloc_path(dir, size))
+ continue;
+ convert_wtf16to8(dir->path + dir->prefix, data.cFileName, wlen + 1);
+ return dir->name = dir->path + dir->prefix;
+ }
+ return NULL;
+}
+
+const char *ass_current_file_path(ASS_Dir *dir)
+{
+ return dir->path;
+}
+
+void ass_close_dir(ASS_Dir *dir)
+{
+ if (dir->handle != INVALID_HANDLE_VALUE)
+ FindClose(dir->handle);
+ free(dir->path);
+ dir->handle = INVALID_HANDLE_VALUE;
+ dir->path = NULL;
+ dir->name = NULL;
+}
+
+#endif // Windows